Adding upstream version 1.1.1.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
9c1e264be3
commit
f19547bec9
22 changed files with 4449 additions and 0 deletions
47
.github/workflows/publish.yml
vendored
Normal file
47
.github/workflows/publish.yml
vendored
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
name: Build and Publish Package
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
types:
|
||||||
|
- closed
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish-package:
|
||||||
|
if: ${{ github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release/v') }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Check out harlequin-postgres main branch
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: main
|
||||||
|
- name: Set up Python 3.10
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: "3.10"
|
||||||
|
- name: Install Poetry
|
||||||
|
uses: snok/install-poetry@v1
|
||||||
|
with:
|
||||||
|
version: 1.6.1
|
||||||
|
- name: Configure poetry
|
||||||
|
run: poetry config --no-interaction pypi-token.pypi ${{ secrets.HARLEQUIN_PG_PYPI_TOKEN }}
|
||||||
|
- name: Get harlequin-postgres Version
|
||||||
|
id: harlequin_pg_version
|
||||||
|
run: echo "harlequin_pg_version=$(poetry version --short)" >> $GITHUB_OUTPUT
|
||||||
|
- name: Build package
|
||||||
|
run: poetry build --no-interaction
|
||||||
|
- name: Publish package to PyPI
|
||||||
|
run: poetry publish --no-interaction
|
||||||
|
- name: Create a Github Release
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
with:
|
||||||
|
tag_name: v${{ steps.harlequin_pg_version.outputs.harlequin_pg_version }}
|
||||||
|
target_commitish: main
|
||||||
|
token: ${{ secrets.HARLEQUIN_PG_RELEASE_TOKEN }}
|
||||||
|
body_path: CHANGELOG.md
|
||||||
|
files: |
|
||||||
|
LICENSE
|
||||||
|
dist/*harlequin*.whl
|
||||||
|
dist/*harlequin*.tar.gz
|
57
.github/workflows/release.yml
vendored
Normal file
57
.github/workflows/release.yml
vendored
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
name: Create Release Branch
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
newVersion:
|
||||||
|
description: A version number for this release (e.g., "0.1.0")
|
||||||
|
required: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
prepare-release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Check out harlequin-postgres main branch
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: main
|
||||||
|
- name: Set up Python 3.10
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: "3.10"
|
||||||
|
- name: Install Poetry
|
||||||
|
uses: snok/install-poetry@v1
|
||||||
|
with:
|
||||||
|
version: 1.6.1
|
||||||
|
- name: Create release branch
|
||||||
|
run: |
|
||||||
|
git checkout -b release/v${{ github.event.inputs.newVersion }}
|
||||||
|
git push --set-upstream origin release/v${{ github.event.inputs.newVersion }}
|
||||||
|
- name: Bump version
|
||||||
|
run: poetry version ${{ github.event.inputs.newVersion }} --no-interaction
|
||||||
|
- name: Ensure package can be built
|
||||||
|
run: poetry build --no-interaction
|
||||||
|
- name: Update CHANGELOG
|
||||||
|
uses: thomaseizinger/keep-a-changelog-new-release@v1
|
||||||
|
with:
|
||||||
|
version: ${{ github.event.inputs.newVersion }}
|
||||||
|
- name: Commit Changes
|
||||||
|
uses: stefanzweifel/git-auto-commit-action@v5
|
||||||
|
with:
|
||||||
|
commit_message: Bumps version to ${{ github.event.inputs.newVersion }}
|
||||||
|
- name: Create pull request into main
|
||||||
|
uses: thomaseizinger/create-pull-request@1.3.1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
head: release/v${{ github.event.inputs.newVersion }}
|
||||||
|
base: main
|
||||||
|
title: v${{ github.event.inputs.newVersion }}
|
||||||
|
body: >
|
||||||
|
This PR was automatically generated. It bumps the version number
|
||||||
|
in pyproject.toml and updates CHANGELOG.md. You may have to close
|
||||||
|
this PR and reopen it to get the required checks to run.
|
163
.gitignore
vendored
Normal file
163
.gitignore
vendored
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
profile.html
|
||||||
|
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
Pipfile
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
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/
|
||||||
|
cover/
|
||||||
|
|
||||||
|
# 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
|
||||||
|
.pybuilder/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
# For a library or package, you might want to ignore these files since the code is
|
||||||
|
# intended to run in multiple environments; otherwise, check them in:
|
||||||
|
# .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
|
||||||
|
|
||||||
|
# poetry
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||||
|
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||||
|
# commonly ignored for libraries.
|
||||||
|
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||||
|
#poetry.lock
|
||||||
|
|
||||||
|
# pdm
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||||
|
#pdm.lock
|
||||||
|
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||||
|
# in version control.
|
||||||
|
# https://pdm.fming.dev/#use-with-ide
|
||||||
|
.pdm.toml
|
||||||
|
|
||||||
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||||
|
__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/
|
||||||
|
|
||||||
|
# pytype static type analyzer
|
||||||
|
.pytype/
|
||||||
|
|
||||||
|
# Cython debug symbols
|
||||||
|
cython_debug/
|
||||||
|
|
||||||
|
# PyCharm
|
||||||
|
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||||
|
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||||
|
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||||
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
|
#.idea/
|
95
CHANGELOG.md
Normal file
95
CHANGELOG.md
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
# Harlequin-Postgres CHANGELOG
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [1.1.1] - 2025-02-05
|
||||||
|
|
||||||
|
- This adapter now supports `infinity` and `-infinity` dates and timestamps by loading their values as `date[time].max` or `date[time].min` ([tconbeer/harlequin#690](https://github.com/tconbeer/harlequin/issues/690)).
|
||||||
|
|
||||||
|
## [1.1.0] - 2025-01-27
|
||||||
|
|
||||||
|
- This adapter now lazy-loads the catalog, which will dramatically improve the catalog performance for large databases with thousands of objects.
|
||||||
|
- This adapter now implements interactions for catalog items, like dropping tables, setting the search path, etc.
|
||||||
|
|
||||||
|
## [1.0.0] - 2025-01-07
|
||||||
|
|
||||||
|
- Drops support for Python 3.8
|
||||||
|
- Adds support for Python 3.13
|
||||||
|
- Adds support for Harlequin 2.X
|
||||||
|
|
||||||
|
## [0.4.0] - 2024-08-20
|
||||||
|
|
||||||
|
- Upgrades client library to `psycopg3` (from `psycopg2`).
|
||||||
|
- Adds an implementation of `connection_id` to improve catalog and history persistence.
|
||||||
|
- Implements `cancel()` to interrupt in-flight queries.
|
||||||
|
|
||||||
|
## [0.3.0] - 2024-07-22
|
||||||
|
|
||||||
|
- Adds an implementation of `close` to gracefully close the connection pool on Harlequin shut-down.
|
||||||
|
- Adds support for Harlequin Transaction Modes and manual transactions.
|
||||||
|
|
||||||
|
## [0.2.2] - 2024-01-09
|
||||||
|
|
||||||
|
- Sorts databases, schemas, and relations alphabetically; sorts columns ordinally. ([#10](https://github.com/tconbeer/harlequin-postgres/issues/10) - thank you [@frankbreetz](https://github.com/frankbreetz)!)
|
||||||
|
|
||||||
|
## [0.2.1] - 2023-12-14
|
||||||
|
|
||||||
|
- Lowercases inserted values for keyword completions.
|
||||||
|
|
||||||
|
## [0.2.0] - 2023-12-14
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- Implements get_completions for keywords, functions, and settings.
|
||||||
|
|
||||||
|
## [0.1.3] - 2023-11-28
|
||||||
|
|
||||||
|
### Bug fixes
|
||||||
|
|
||||||
|
- Implements connection pools instead of sharing a connection across threads.
|
||||||
|
|
||||||
|
## [0.1.2] - 2023-11-27
|
||||||
|
|
||||||
|
### Bug fixes
|
||||||
|
|
||||||
|
- Fixes issues with package metadata.
|
||||||
|
|
||||||
|
## [0.1.1] - 2023-11-27
|
||||||
|
|
||||||
|
### Bug fixes
|
||||||
|
|
||||||
|
- Fixes typo in release script.
|
||||||
|
|
||||||
|
## [0.1.0] - 2023-11-27
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- Adds a basic Postgres adapter with most common connection options.
|
||||||
|
|
||||||
|
[Unreleased]: https://github.com/tconbeer/harlequin-postgres/compare/1.1.1...HEAD
|
||||||
|
|
||||||
|
[1.1.1]: https://github.com/tconbeer/harlequin-postgres/compare/1.1.0...1.1.1
|
||||||
|
|
||||||
|
[1.1.0]: https://github.com/tconbeer/harlequin-postgres/compare/1.0.0...1.1.0
|
||||||
|
|
||||||
|
[1.0.0]: https://github.com/tconbeer/harlequin-postgres/compare/0.4.0...1.0.0
|
||||||
|
|
||||||
|
[0.4.0]: https://github.com/tconbeer/harlequin-postgres/compare/0.3.0...0.4.0
|
||||||
|
|
||||||
|
[0.3.0]: https://github.com/tconbeer/harlequin-postgres/compare/0.2.2...0.3.0
|
||||||
|
|
||||||
|
[0.2.2]: https://github.com/tconbeer/harlequin-postgres/compare/0.2.1...0.2.2
|
||||||
|
|
||||||
|
[0.2.1]: https://github.com/tconbeer/harlequin-postgres/compare/0.2.0...0.2.1
|
||||||
|
|
||||||
|
[0.2.0]: https://github.com/tconbeer/harlequin-postgres/compare/0.1.3...0.2.0
|
||||||
|
|
||||||
|
[0.1.3]: https://github.com/tconbeer/harlequin-postgres/compare/0.1.2...0.1.3
|
||||||
|
|
||||||
|
[0.1.2]: https://github.com/tconbeer/harlequin-postgres/compare/0.1.1...0.1.2
|
||||||
|
|
||||||
|
[0.1.1]: https://github.com/tconbeer/harlequin-postgres/compare/0.1.0...0.1.1
|
||||||
|
|
||||||
|
[0.1.0]: https://github.com/tconbeer/harlequin-postgres/compare/8611e628dc9d28b6a24817c761cd8a6da11a87ad...0.1.0
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023 Ted Conbeer
|
||||||
|
|
||||||
|
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.
|
25
Makefile
Normal file
25
Makefile
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
.PHONY: check
|
||||||
|
check:
|
||||||
|
ruff format .
|
||||||
|
ruff check . --fix
|
||||||
|
mypy
|
||||||
|
pytest
|
||||||
|
|
||||||
|
.PHONY: init
|
||||||
|
init:
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
docker-compose down
|
||||||
|
|
||||||
|
.PHONY: serve
|
||||||
|
serve:
|
||||||
|
harlequin -P None -a postgres "postgresql://postgres:for-testing@localhost:5432/postgres"
|
||||||
|
|
||||||
|
.PHONY: psql
|
||||||
|
psql:
|
||||||
|
PGPASSWORD=for-testing psql -h localhost -p 5432 -U postgres
|
||||||
|
|
||||||
|
profile.html: $(wildcard src/**/*.py)
|
||||||
|
pyinstrument -r html -o profile.html --from-path harlequin -a postgres "postgresql://postgres:for-testing@localhost:5432/postgres"
|
77
README.md
Normal file
77
README.md
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
# harlequin-postgres
|
||||||
|
|
||||||
|
This repo provides the Harlequin adapter for Postgres.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
`harlequin-postgres` depends on `harlequin`, so installing this package will also install Harlequin.
|
||||||
|
|
||||||
|
### Using pip
|
||||||
|
|
||||||
|
To install this adapter into an activated virtual environment:
|
||||||
|
```bash
|
||||||
|
pip install harlequin-postgres
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using poetry
|
||||||
|
|
||||||
|
```bash
|
||||||
|
poetry add harlequin-postgres
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using pipx
|
||||||
|
|
||||||
|
If you do not already have Harlequin installed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install harlequin-postgres
|
||||||
|
```
|
||||||
|
|
||||||
|
If you would like to add the Postgres adapter to an existing Harlequin installation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pipx inject harlequin harlequin-postgres
|
||||||
|
```
|
||||||
|
|
||||||
|
### As an Extra
|
||||||
|
Alternatively, you can install Harlequin with the `postgres` extra:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install harlequin[postgres]
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
poetry add harlequin[postgres]
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pipx install harlequin[postgres]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage and Configuration
|
||||||
|
|
||||||
|
You can open Harlequin with the Postgres adapter by selecting it with the `-a` option and passing a [Posgres DSN](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
harlequin -a postgres "postgres://my-user:my-pass@localhost:5432/my-database"
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also pass all or parts of the connection string as separate options. The following is equivalent to the above DSN:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
harlequin -a postgres -h localhost -p 5432 -U my-user --password my-pass -d my-database
|
||||||
|
```
|
||||||
|
|
||||||
|
Many more options are available; to see the full list, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
harlequin --help
|
||||||
|
```
|
||||||
|
|
||||||
|
## Manual Transactions
|
||||||
|
|
||||||
|
To use Manual transaction mode, click on the label in the Run Query Bar to toggle the transaction mode from Auto to Manual.
|
||||||
|
|
||||||
|
## Further Documentation
|
||||||
|
|
||||||
|
For more information, see the [Harlequin Docs](https://harlequin.sh/docs/postgres/index).
|
14
docker-compose.yml
Normal file
14
docker-compose.yml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
services:
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: postgres
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
POSTGRES_PASSWORD: for-testing
|
||||||
|
volumes:
|
||||||
|
- pgdata:/var/lib/postgresql/data
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
pgdata:
|
1561
poetry.lock
generated
Normal file
1561
poetry.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
68
pyproject.toml
Normal file
68
pyproject.toml
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
[tool.poetry]
|
||||||
|
name = "harlequin-postgres"
|
||||||
|
version = "1.1.1"
|
||||||
|
description = "A Harlequin adapter for Postgres."
|
||||||
|
authors = ["Ted Conbeer <tconbeer@users.noreply.github.com>"]
|
||||||
|
license = "MIT"
|
||||||
|
readme = "README.md"
|
||||||
|
packages = [
|
||||||
|
{ include = "harlequin_postgres", from = "src" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.poetry.plugins."harlequin.adapter"]
|
||||||
|
postgres = "harlequin_postgres:HarlequinPostgresAdapter"
|
||||||
|
|
||||||
|
[tool.poetry.dependencies]
|
||||||
|
python = ">=3.9,<3.14"
|
||||||
|
harlequin = ">=1.25,<3"
|
||||||
|
psycopg = { version = "^3.2", extras = ["binary", "pool"]}
|
||||||
|
|
||||||
|
[tool.poetry.group.dev.dependencies]
|
||||||
|
ruff = "^0.5"
|
||||||
|
pytest = "^7.4.3"
|
||||||
|
mypy = "^1.10.0"
|
||||||
|
pre-commit = "^3.5.0"
|
||||||
|
importlib_metadata = { version = ">=4.6.0", python = "<3.10.0" }
|
||||||
|
pyinstrument = "^4.6.1"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["poetry-core"]
|
||||||
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
target-version = "py39"
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
|
select = ["A", "B", "E", "F", "I"]
|
||||||
|
|
||||||
|
[tool.mypy]
|
||||||
|
python_version = "3.9"
|
||||||
|
files = [
|
||||||
|
"src/**/*.py",
|
||||||
|
"tests/**/*.py",
|
||||||
|
]
|
||||||
|
mypy_path = "src:stubs"
|
||||||
|
|
||||||
|
show_column_numbers = true
|
||||||
|
|
||||||
|
# show error messages from unrelated files
|
||||||
|
follow_imports = "normal"
|
||||||
|
|
||||||
|
# be strict
|
||||||
|
disallow_untyped_calls = true
|
||||||
|
disallow_untyped_defs = true
|
||||||
|
check_untyped_defs = true
|
||||||
|
disallow_untyped_decorators = true
|
||||||
|
disallow_incomplete_defs = true
|
||||||
|
disallow_subclassing_any = true
|
||||||
|
strict_optional = true
|
||||||
|
|
||||||
|
warn_return_any = true
|
||||||
|
warn_no_return = true
|
||||||
|
warn_redundant_casts = true
|
||||||
|
warn_unused_ignores = true
|
||||||
|
warn_unused_configs = true
|
||||||
|
|
||||||
|
no_implicit_reexport = true
|
||||||
|
strict_equality = true
|
3
src/harlequin_postgres/__init__.py
Normal file
3
src/harlequin_postgres/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from harlequin_postgres.adapter import HarlequinPostgresAdapter
|
||||||
|
|
||||||
|
__all__ = ["HarlequinPostgresAdapter"]
|
453
src/harlequin_postgres/adapter.py
Normal file
453
src/harlequin_postgres/adapter.py
Normal file
|
@ -0,0 +1,453 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from itertools import cycle
|
||||||
|
from typing import Any, Sequence
|
||||||
|
|
||||||
|
from harlequin import (
|
||||||
|
HarlequinAdapter,
|
||||||
|
HarlequinCompletion,
|
||||||
|
HarlequinConnection,
|
||||||
|
HarlequinCursor,
|
||||||
|
HarlequinTransactionMode,
|
||||||
|
)
|
||||||
|
from harlequin.catalog import Catalog, CatalogItem
|
||||||
|
from harlequin.exception import HarlequinConnectionError, HarlequinQueryError
|
||||||
|
from psycopg import Connection, Cursor, conninfo
|
||||||
|
from psycopg.errors import QueryCanceled
|
||||||
|
from psycopg.pq import TransactionStatus
|
||||||
|
from psycopg_pool import ConnectionPool
|
||||||
|
from textual_fastdatatable.backend import AutoBackendType
|
||||||
|
|
||||||
|
from harlequin_postgres.catalog import DatabaseCatalogItem
|
||||||
|
from harlequin_postgres.cli_options import POSTGRES_OPTIONS
|
||||||
|
from harlequin_postgres.completions import _get_completions
|
||||||
|
from harlequin_postgres.loaders import register_inf_loaders
|
||||||
|
|
||||||
|
|
||||||
|
class HarlequinPostgresCursor(HarlequinCursor):
|
||||||
|
def __init__(self, conn: HarlequinPostgresConnection, cur: Cursor) -> None:
|
||||||
|
self.conn = conn
|
||||||
|
self.cur = cur
|
||||||
|
# we need to copy the description from the cursor in case the results are
|
||||||
|
# fetched and the cursor is closed before columns() is called.
|
||||||
|
assert cur.description is not None
|
||||||
|
self.description = cur.description.copy()
|
||||||
|
self._limit: int | None = None
|
||||||
|
|
||||||
|
def columns(self) -> list[tuple[str, str]]:
|
||||||
|
return [
|
||||||
|
(col.name, self.conn._short_column_type_from_oid(col.type_code))
|
||||||
|
for col in self.description
|
||||||
|
]
|
||||||
|
|
||||||
|
def set_limit(self, limit: int) -> HarlequinPostgresCursor:
|
||||||
|
self._limit = limit
|
||||||
|
return self
|
||||||
|
|
||||||
|
def fetchall(self) -> AutoBackendType:
|
||||||
|
try:
|
||||||
|
if self._limit is None:
|
||||||
|
return self.cur.fetchall()
|
||||||
|
else:
|
||||||
|
return self.cur.fetchmany(self._limit)
|
||||||
|
except QueryCanceled:
|
||||||
|
return []
|
||||||
|
except Exception as e:
|
||||||
|
raise HarlequinQueryError(
|
||||||
|
msg=f"{e.__class__.__name__}: {e}",
|
||||||
|
title="Harlequin encountered an error while executing your query.",
|
||||||
|
) from e
|
||||||
|
finally:
|
||||||
|
self.cur.close()
|
||||||
|
|
||||||
|
|
||||||
|
class HarlequinPostgresConnection(HarlequinConnection):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
conn_str: Sequence[str],
|
||||||
|
*_: Any,
|
||||||
|
init_message: str = "",
|
||||||
|
options: dict[str, Any],
|
||||||
|
) -> None:
|
||||||
|
self.init_message = init_message
|
||||||
|
try:
|
||||||
|
self.conn_info = conninfo.conninfo_to_dict(
|
||||||
|
conninfo=conn_str[0] if conn_str else "", **options
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
raise HarlequinConnectionError(
|
||||||
|
msg=str(e),
|
||||||
|
title=(
|
||||||
|
"Harlequin could not connect to Postgres. "
|
||||||
|
"Invalid connection string."
|
||||||
|
),
|
||||||
|
) from e
|
||||||
|
try:
|
||||||
|
raw_timeout = self.conn_info.get("connect_timeout")
|
||||||
|
timeout = float(raw_timeout) if raw_timeout is not None else 30.0
|
||||||
|
except (TypeError, ValueError) as e:
|
||||||
|
raise HarlequinConnectionError(
|
||||||
|
msg=str(e),
|
||||||
|
title=(
|
||||||
|
"Harlequin could not connect to Postgres. "
|
||||||
|
"Invalid value for connection_timeout."
|
||||||
|
),
|
||||||
|
) from e
|
||||||
|
try:
|
||||||
|
self.pool: ConnectionPool = ConnectionPool(
|
||||||
|
conninfo=conn_str[0] if conn_str and conn_str[0] else "",
|
||||||
|
min_size=2,
|
||||||
|
max_size=5,
|
||||||
|
kwargs=options,
|
||||||
|
open=True,
|
||||||
|
timeout=timeout,
|
||||||
|
)
|
||||||
|
self._main_conn: Connection = self.pool.getconn()
|
||||||
|
except Exception as e:
|
||||||
|
raise HarlequinConnectionError(
|
||||||
|
msg=str(e), title="Harlequin could not connect to Postgres."
|
||||||
|
) from e
|
||||||
|
|
||||||
|
self._transaction_modes = cycle(
|
||||||
|
[
|
||||||
|
HarlequinTransactionMode(label="Auto"),
|
||||||
|
HarlequinTransactionMode(
|
||||||
|
label="Manual",
|
||||||
|
commit=self.commit,
|
||||||
|
rollback=self.rollback,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
self.toggle_transaction_mode()
|
||||||
|
|
||||||
|
def execute(self, query: str) -> HarlequinCursor | None:
|
||||||
|
if (
|
||||||
|
self.transaction_mode.label != "Auto"
|
||||||
|
and self._main_conn.info.transaction_status == TransactionStatus.IDLE
|
||||||
|
):
|
||||||
|
cur = self._main_conn.cursor()
|
||||||
|
cur.execute(query="begin;")
|
||||||
|
cur.close()
|
||||||
|
|
||||||
|
try:
|
||||||
|
cur = self._main_conn.cursor()
|
||||||
|
cur.execute(query=query)
|
||||||
|
except QueryCanceled:
|
||||||
|
cur.close()
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
cur.close()
|
||||||
|
self.rollback()
|
||||||
|
raise HarlequinQueryError(
|
||||||
|
msg=str(e),
|
||||||
|
title="Harlequin encountered an error while executing your query.",
|
||||||
|
) from e
|
||||||
|
else:
|
||||||
|
if cur.description is not None:
|
||||||
|
return HarlequinPostgresCursor(self, cur)
|
||||||
|
else:
|
||||||
|
cur.close()
|
||||||
|
return None
|
||||||
|
|
||||||
|
def cancel(self) -> None:
|
||||||
|
self._main_conn.cancel_safe()
|
||||||
|
|
||||||
|
def commit(self) -> None:
|
||||||
|
self._main_conn.commit()
|
||||||
|
|
||||||
|
def rollback(self) -> None:
|
||||||
|
self._main_conn.rollback()
|
||||||
|
|
||||||
|
def get_catalog(self) -> Catalog:
|
||||||
|
databases = self._get_databases()
|
||||||
|
db_items: list[CatalogItem] = [
|
||||||
|
DatabaseCatalogItem.from_label(label=db, connection=self)
|
||||||
|
for (db,) in databases
|
||||||
|
]
|
||||||
|
return Catalog(items=db_items)
|
||||||
|
|
||||||
|
def get_completions(self) -> list[HarlequinCompletion]:
|
||||||
|
conn: Connection = self.pool.getconn()
|
||||||
|
completions = _get_completions(conn)
|
||||||
|
self.pool.putconn(conn)
|
||||||
|
return completions
|
||||||
|
|
||||||
|
def close(self) -> None:
|
||||||
|
self.pool.putconn(self._main_conn)
|
||||||
|
self.pool.close()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def transaction_mode(self) -> HarlequinTransactionMode:
|
||||||
|
return self._transaction_mode
|
||||||
|
|
||||||
|
def toggle_transaction_mode(self) -> HarlequinTransactionMode:
|
||||||
|
self._transaction_mode = next(self._transaction_modes)
|
||||||
|
self._sync_transaction_mode()
|
||||||
|
return self._transaction_mode
|
||||||
|
|
||||||
|
def _sync_transaction_mode(self) -> None:
|
||||||
|
"""
|
||||||
|
Sync this class's transaction mode with the main connection
|
||||||
|
"""
|
||||||
|
conn = self._main_conn
|
||||||
|
if self.transaction_mode.label == "Auto":
|
||||||
|
conn.autocommit = True
|
||||||
|
conn.commit()
|
||||||
|
else:
|
||||||
|
conn.autocommit = False
|
||||||
|
|
||||||
|
def _get_databases(self) -> list[tuple[str]]:
|
||||||
|
conn: Connection = self.pool.getconn()
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
select datname
|
||||||
|
from pg_database
|
||||||
|
where
|
||||||
|
datistemplate is false
|
||||||
|
and datallowconn is true
|
||||||
|
order by datname asc
|
||||||
|
;"""
|
||||||
|
)
|
||||||
|
results: list[tuple[str]] = cur.fetchall()
|
||||||
|
self.pool.putconn(conn)
|
||||||
|
return results
|
||||||
|
|
||||||
|
def _get_schemas(self, dbname: str) -> list[tuple[str]]:
|
||||||
|
conn: Connection = self.pool.getconn()
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute(
|
||||||
|
f"""
|
||||||
|
select schema_name
|
||||||
|
from information_schema.schemata
|
||||||
|
where
|
||||||
|
catalog_name = '{dbname}'
|
||||||
|
and schema_name != 'information_schema'
|
||||||
|
and schema_name not like 'pg_%'
|
||||||
|
order by schema_name asc
|
||||||
|
;"""
|
||||||
|
)
|
||||||
|
results: list[tuple[str]] = cur.fetchall()
|
||||||
|
self.pool.putconn(conn)
|
||||||
|
return results
|
||||||
|
|
||||||
|
def _get_relations(self, dbname: str, schema: str) -> list[tuple[str, str]]:
|
||||||
|
conn: Connection = self.pool.getconn()
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute(
|
||||||
|
f"""
|
||||||
|
select table_name, table_type
|
||||||
|
from information_schema.tables
|
||||||
|
where
|
||||||
|
table_catalog = '{dbname}'
|
||||||
|
and table_schema = '{schema}'
|
||||||
|
order by table_name asc
|
||||||
|
;"""
|
||||||
|
)
|
||||||
|
results: list[tuple[str, str]] = cur.fetchall()
|
||||||
|
self.pool.putconn(conn)
|
||||||
|
return results
|
||||||
|
|
||||||
|
def _get_columns(
|
||||||
|
self, dbname: str, schema: str, relation: str
|
||||||
|
) -> list[tuple[str, str]]:
|
||||||
|
conn: Connection = self.pool.getconn()
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute(
|
||||||
|
f"""
|
||||||
|
select column_name, data_type
|
||||||
|
from information_schema.columns
|
||||||
|
where
|
||||||
|
table_catalog = '{dbname}'
|
||||||
|
and table_schema = '{schema}'
|
||||||
|
and table_name = '{relation}'
|
||||||
|
order by ordinal_position asc
|
||||||
|
;"""
|
||||||
|
)
|
||||||
|
results: list[tuple[str, str]] = cur.fetchall()
|
||||||
|
self.pool.putconn(conn)
|
||||||
|
return results
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _short_column_type(type_name: str) -> str:
|
||||||
|
MAPPING = {
|
||||||
|
"bigint": "##",
|
||||||
|
"bigserial": "##",
|
||||||
|
"bit": "010",
|
||||||
|
"boolean": "t/f",
|
||||||
|
"box": "□",
|
||||||
|
"bytea": "b",
|
||||||
|
"character": "s",
|
||||||
|
"cidr": "ip",
|
||||||
|
"circle": "○",
|
||||||
|
"date": "d",
|
||||||
|
"double": "#.#",
|
||||||
|
"inet": "ip",
|
||||||
|
"integer": "#",
|
||||||
|
"interval": "|-|",
|
||||||
|
"json": "{}",
|
||||||
|
"jsonb": "b{}",
|
||||||
|
"line": "—",
|
||||||
|
"lseg": "-",
|
||||||
|
"macaddr": "mac",
|
||||||
|
"macaddr8": "mac",
|
||||||
|
"money": "$$",
|
||||||
|
"numeric": "#.#",
|
||||||
|
"path": "╭",
|
||||||
|
"pg_lsn": "lsn",
|
||||||
|
"pg_snapshot": "snp",
|
||||||
|
"point": "•",
|
||||||
|
"polygon": "▽",
|
||||||
|
"real": "#.#",
|
||||||
|
"smallint": "#",
|
||||||
|
"smallserial": "#",
|
||||||
|
"serial": "#",
|
||||||
|
"text": "s",
|
||||||
|
"time": "t",
|
||||||
|
"timestamp": "ts",
|
||||||
|
"tsquery": "tsq",
|
||||||
|
"tsvector": "tsv",
|
||||||
|
"txid_snapshot": "snp",
|
||||||
|
"uuid": "uid",
|
||||||
|
"xml": "xml",
|
||||||
|
"array": "[]",
|
||||||
|
}
|
||||||
|
return MAPPING.get(type_name.split("(")[0].split(" ")[0], "?")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _short_column_type_from_oid(oid: int) -> str:
|
||||||
|
MAPPING = {
|
||||||
|
16: "t/f",
|
||||||
|
17: "b",
|
||||||
|
18: "s",
|
||||||
|
19: "s",
|
||||||
|
20: "##",
|
||||||
|
21: "#",
|
||||||
|
22: "[#]",
|
||||||
|
23: "#",
|
||||||
|
25: "s",
|
||||||
|
26: "oid",
|
||||||
|
114: "{}",
|
||||||
|
142: "xml",
|
||||||
|
600: "•",
|
||||||
|
601: "-",
|
||||||
|
602: "╭",
|
||||||
|
603: "□",
|
||||||
|
604: "▽",
|
||||||
|
628: "—",
|
||||||
|
651: "[ip]",
|
||||||
|
700: "#.#",
|
||||||
|
701: "#.#",
|
||||||
|
704: "|-|",
|
||||||
|
718: "○",
|
||||||
|
790: "$$",
|
||||||
|
829: "mac",
|
||||||
|
869: "ip",
|
||||||
|
650: "ip",
|
||||||
|
774: "mac",
|
||||||
|
1000: "[t/f]",
|
||||||
|
1001: "[b]",
|
||||||
|
1002: "[s]",
|
||||||
|
1003: "[s]",
|
||||||
|
1009: "[s]",
|
||||||
|
1013: "[oid]",
|
||||||
|
1014: "[s]",
|
||||||
|
1015: "[s]",
|
||||||
|
1016: "[#]",
|
||||||
|
1021: "[#.#]",
|
||||||
|
1022: "[#.#]",
|
||||||
|
1028: "[oid]",
|
||||||
|
1040: "[mac]",
|
||||||
|
1041: "[ip]",
|
||||||
|
1042: "s",
|
||||||
|
1043: "s",
|
||||||
|
1082: "d",
|
||||||
|
1083: "t",
|
||||||
|
1114: "ts",
|
||||||
|
1115: "[ts]",
|
||||||
|
1182: "[d]",
|
||||||
|
1183: "[t]",
|
||||||
|
1184: "ts",
|
||||||
|
1185: "[ts]",
|
||||||
|
1186: "|-|",
|
||||||
|
1187: "[|-|]",
|
||||||
|
1231: "[#.#]",
|
||||||
|
1266: "t",
|
||||||
|
1270: "[t]",
|
||||||
|
1560: "010",
|
||||||
|
1562: "010",
|
||||||
|
1700: "#.#",
|
||||||
|
2950: "uid",
|
||||||
|
3614: "tsv",
|
||||||
|
3615: "tsq",
|
||||||
|
3802: "b{}",
|
||||||
|
}
|
||||||
|
return MAPPING.get(oid, "?")
|
||||||
|
|
||||||
|
|
||||||
|
class HarlequinPostgresAdapter(HarlequinAdapter):
|
||||||
|
ADAPTER_OPTIONS = POSTGRES_OPTIONS
|
||||||
|
IMPLEMENTS_CANCEL = True
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
conn_str: Sequence[str],
|
||||||
|
host: str | None = None,
|
||||||
|
port: str | None = None,
|
||||||
|
dbname: str | None = None,
|
||||||
|
user: str | None = None,
|
||||||
|
password: str | None = None,
|
||||||
|
passfile: str | None = None,
|
||||||
|
require_auth: str | None = None,
|
||||||
|
channel_binding: str | None = None,
|
||||||
|
connect_timeout: int | float | None = None,
|
||||||
|
sslmode: str | None = None,
|
||||||
|
sslcert: str | None = None,
|
||||||
|
sslkey: str | None = None,
|
||||||
|
**_: Any,
|
||||||
|
) -> None:
|
||||||
|
self.conn_str = conn_str
|
||||||
|
self.options: dict[str, str | int | None] = {
|
||||||
|
"host": host,
|
||||||
|
"port": port,
|
||||||
|
"dbname": dbname,
|
||||||
|
"user": user,
|
||||||
|
"password": password,
|
||||||
|
"passfile": passfile,
|
||||||
|
"require_auth": require_auth,
|
||||||
|
"channel_binding": channel_binding,
|
||||||
|
"connect_timeout": connect_timeout, # type: ignore[dict-item]
|
||||||
|
"sslmode": sslmode,
|
||||||
|
"sslcert": sslcert,
|
||||||
|
"sslkey": sslkey,
|
||||||
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def connection_id(self) -> str | None:
|
||||||
|
"""
|
||||||
|
Use a simplified connection string, with only the host, port, and database
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
conn_info = conninfo.conninfo_to_dict(
|
||||||
|
conninfo=self.conn_str[0] if self.conn_str else "",
|
||||||
|
**self.options,
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
host = conn_info.get("host", "localhost")
|
||||||
|
port = conn_info.get("port", "5432")
|
||||||
|
dbname = conn_info.get("dbname", "postgres")
|
||||||
|
return f"{host}:{port}/{dbname}"
|
||||||
|
|
||||||
|
def connect(self) -> HarlequinPostgresConnection:
|
||||||
|
if len(self.conn_str) > 1:
|
||||||
|
raise HarlequinConnectionError(
|
||||||
|
"Cannot provide multiple connection strings to the Postgres adapter. "
|
||||||
|
f"{self.conn_str}"
|
||||||
|
)
|
||||||
|
# before creating the connection, register updated type adapters, so
|
||||||
|
# all subsequent connections will use those adapters
|
||||||
|
register_inf_loaders()
|
||||||
|
conn = HarlequinPostgresConnection(self.conn_str, options=self.options)
|
||||||
|
return conn
|
253
src/harlequin_postgres/catalog.py
Normal file
253
src/harlequin_postgres/catalog.py
Normal file
|
@ -0,0 +1,253 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from harlequin.catalog import InteractiveCatalogItem
|
||||||
|
|
||||||
|
from harlequin_postgres.interactions import (
|
||||||
|
execute_drop_database_statement,
|
||||||
|
execute_drop_foreign_table_statement,
|
||||||
|
execute_drop_schema_statement,
|
||||||
|
execute_drop_table_statement,
|
||||||
|
execute_drop_view_statement,
|
||||||
|
execute_use_statement,
|
||||||
|
insert_columns_at_cursor,
|
||||||
|
show_select_star,
|
||||||
|
)
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from harlequin_postgres.adapter import HarlequinPostgresConnection
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ColumnCatalogItem(InteractiveCatalogItem["HarlequinPostgresConnection"]):
|
||||||
|
parent: "RelationCatalogItem" | None = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_parent(
|
||||||
|
cls,
|
||||||
|
parent: "RelationCatalogItem",
|
||||||
|
label: str,
|
||||||
|
type_label: str,
|
||||||
|
) -> "ColumnCatalogItem":
|
||||||
|
column_qualified_identifier = f'{parent.qualified_identifier}."{label}"'
|
||||||
|
column_query_name = f'"{label}"'
|
||||||
|
return cls(
|
||||||
|
qualified_identifier=column_qualified_identifier,
|
||||||
|
query_name=column_query_name,
|
||||||
|
label=label,
|
||||||
|
type_label=type_label,
|
||||||
|
connection=parent.connection,
|
||||||
|
parent=parent,
|
||||||
|
loaded=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class RelationCatalogItem(InteractiveCatalogItem["HarlequinPostgresConnection"]):
|
||||||
|
INTERACTIONS = [
|
||||||
|
("Insert Columns at Cursor", insert_columns_at_cursor),
|
||||||
|
("Preview Data", show_select_star),
|
||||||
|
]
|
||||||
|
parent: "SchemaCatalogItem" | None = None
|
||||||
|
|
||||||
|
def fetch_children(self) -> list[ColumnCatalogItem]:
|
||||||
|
if self.parent is None or self.parent.parent is None or self.connection is None:
|
||||||
|
return []
|
||||||
|
result = self.connection._get_columns(
|
||||||
|
self.parent.parent.label, self.parent.label, self.label
|
||||||
|
)
|
||||||
|
return [
|
||||||
|
ColumnCatalogItem.from_parent(
|
||||||
|
parent=self,
|
||||||
|
label=column_name,
|
||||||
|
type_label=self.connection._short_column_type(column_type),
|
||||||
|
)
|
||||||
|
for column_name, column_type in result
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class ViewCatalogItem(RelationCatalogItem):
|
||||||
|
INTERACTIONS = RelationCatalogItem.INTERACTIONS + [
|
||||||
|
("Drop View", execute_drop_view_statement),
|
||||||
|
]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_parent(
|
||||||
|
cls,
|
||||||
|
parent: "SchemaCatalogItem",
|
||||||
|
label: str,
|
||||||
|
) -> "ViewCatalogItem":
|
||||||
|
relation_query_name = f'"{parent.label}"."{label}"'
|
||||||
|
relation_qualified_identifier = f'{parent.qualified_identifier}."{label}"'
|
||||||
|
return cls(
|
||||||
|
qualified_identifier=relation_qualified_identifier,
|
||||||
|
query_name=relation_query_name,
|
||||||
|
label=label,
|
||||||
|
type_label="v",
|
||||||
|
connection=parent.connection,
|
||||||
|
parent=parent,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TableCatalogItem(RelationCatalogItem):
|
||||||
|
INTERACTIONS = RelationCatalogItem.INTERACTIONS + [
|
||||||
|
("Drop Table", execute_drop_table_statement),
|
||||||
|
]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_parent(
|
||||||
|
cls,
|
||||||
|
parent: "SchemaCatalogItem",
|
||||||
|
label: str,
|
||||||
|
) -> "TableCatalogItem":
|
||||||
|
relation_query_name = f'"{parent.label}"."{label}"'
|
||||||
|
relation_qualified_identifier = f'{parent.qualified_identifier}."{label}"'
|
||||||
|
return cls(
|
||||||
|
qualified_identifier=relation_qualified_identifier,
|
||||||
|
query_name=relation_query_name,
|
||||||
|
label=label,
|
||||||
|
type_label="t",
|
||||||
|
connection=parent.connection,
|
||||||
|
parent=parent,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TempTableCatalogItem(TableCatalogItem):
|
||||||
|
INTERACTIONS = RelationCatalogItem.INTERACTIONS + [
|
||||||
|
("Drop Table", execute_drop_table_statement),
|
||||||
|
]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_parent(
|
||||||
|
cls,
|
||||||
|
parent: "SchemaCatalogItem",
|
||||||
|
label: str,
|
||||||
|
) -> "TempTableCatalogItem":
|
||||||
|
relation_query_name = f'"{parent.label}"."{label}"'
|
||||||
|
relation_qualified_identifier = f'{parent.qualified_identifier}."{label}"'
|
||||||
|
return cls(
|
||||||
|
qualified_identifier=relation_qualified_identifier,
|
||||||
|
query_name=relation_query_name,
|
||||||
|
label=label,
|
||||||
|
type_label="tmp",
|
||||||
|
connection=parent.connection,
|
||||||
|
parent=parent,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ForeignCatalogItem(TableCatalogItem):
|
||||||
|
INTERACTIONS = RelationCatalogItem.INTERACTIONS + [
|
||||||
|
("Drop Table", execute_drop_foreign_table_statement),
|
||||||
|
]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_parent(
|
||||||
|
cls,
|
||||||
|
parent: "SchemaCatalogItem",
|
||||||
|
label: str,
|
||||||
|
) -> "ForeignCatalogItem":
|
||||||
|
relation_query_name = f'"{parent.label}"."{label}"'
|
||||||
|
relation_qualified_identifier = f'{parent.qualified_identifier}."{label}"'
|
||||||
|
return cls(
|
||||||
|
qualified_identifier=relation_qualified_identifier,
|
||||||
|
query_name=relation_query_name,
|
||||||
|
label=label,
|
||||||
|
type_label="f",
|
||||||
|
connection=parent.connection,
|
||||||
|
parent=parent,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SchemaCatalogItem(InteractiveCatalogItem["HarlequinPostgresConnection"]):
|
||||||
|
INTERACTIONS = [
|
||||||
|
("Set Search Path", execute_use_statement),
|
||||||
|
("Drop Schema", execute_drop_schema_statement),
|
||||||
|
]
|
||||||
|
parent: "DatabaseCatalogItem" | None = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_parent(
|
||||||
|
cls,
|
||||||
|
parent: "DatabaseCatalogItem",
|
||||||
|
label: str,
|
||||||
|
) -> "SchemaCatalogItem":
|
||||||
|
schema_identifier = f'"{label}"'
|
||||||
|
return cls(
|
||||||
|
qualified_identifier=schema_identifier,
|
||||||
|
query_name=schema_identifier,
|
||||||
|
label=label,
|
||||||
|
type_label="sch",
|
||||||
|
connection=parent.connection,
|
||||||
|
parent=parent,
|
||||||
|
)
|
||||||
|
|
||||||
|
def fetch_children(self) -> list[RelationCatalogItem]:
|
||||||
|
if self.parent is None or self.connection is None:
|
||||||
|
return []
|
||||||
|
children: list[RelationCatalogItem] = []
|
||||||
|
result = self.connection._get_relations(self.parent.label, self.label)
|
||||||
|
for table_label, table_type in result:
|
||||||
|
if table_type == "VIEW":
|
||||||
|
children.append(
|
||||||
|
ViewCatalogItem.from_parent(
|
||||||
|
parent=self,
|
||||||
|
label=table_label,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif table_type == "LOCAL TEMPORARY":
|
||||||
|
children.append(
|
||||||
|
TempTableCatalogItem.from_parent(
|
||||||
|
parent=self,
|
||||||
|
label=table_label,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif table_type == "FOREIGN":
|
||||||
|
children.append(
|
||||||
|
ForeignCatalogItem.from_parent(
|
||||||
|
parent=self,
|
||||||
|
label=table_label,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
children.append(
|
||||||
|
TableCatalogItem.from_parent(
|
||||||
|
parent=self,
|
||||||
|
label=table_label,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return children
|
||||||
|
|
||||||
|
|
||||||
|
class DatabaseCatalogItem(InteractiveCatalogItem["HarlequinPostgresConnection"]):
|
||||||
|
INTERACTIONS = [
|
||||||
|
("Drop Database", execute_drop_database_statement),
|
||||||
|
]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_label(
|
||||||
|
cls, label: str, connection: "HarlequinPostgresConnection"
|
||||||
|
) -> "DatabaseCatalogItem":
|
||||||
|
database_identifier = f'"{label}"'
|
||||||
|
return cls(
|
||||||
|
qualified_identifier=database_identifier,
|
||||||
|
query_name=database_identifier,
|
||||||
|
label=label,
|
||||||
|
type_label="db",
|
||||||
|
connection=connection,
|
||||||
|
)
|
||||||
|
|
||||||
|
def fetch_children(self) -> list[SchemaCatalogItem]:
|
||||||
|
if self.connection is None:
|
||||||
|
return []
|
||||||
|
schemas = self.connection._get_schemas(self.label)
|
||||||
|
return [
|
||||||
|
SchemaCatalogItem.from_parent(
|
||||||
|
parent=self,
|
||||||
|
label=schema_label,
|
||||||
|
)
|
||||||
|
for (schema_label,) in schemas
|
||||||
|
]
|
157
src/harlequin_postgres/cli_options.py
Normal file
157
src/harlequin_postgres/cli_options.py
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from harlequin.options import (
|
||||||
|
FlagOption, # noqa
|
||||||
|
ListOption, # noqa
|
||||||
|
PathOption, # noqa
|
||||||
|
SelectOption, # noqa
|
||||||
|
TextOption,
|
||||||
|
)
|
||||||
|
|
||||||
|
host = TextOption(
|
||||||
|
name="host",
|
||||||
|
description=(
|
||||||
|
"Specifies the host name of the machine on which the server is running. "
|
||||||
|
"If the value begins with a slash, it is used as the directory for the "
|
||||||
|
"Unix-domain socket."
|
||||||
|
),
|
||||||
|
short_decls=["-h"],
|
||||||
|
default="localhost",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
port = TextOption(
|
||||||
|
name="port",
|
||||||
|
description=(
|
||||||
|
"Port number to connect to at the server host, or socket file name extension "
|
||||||
|
"for Unix-domain connections."
|
||||||
|
),
|
||||||
|
short_decls=["-p"],
|
||||||
|
default="5432",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
dbname = TextOption(
|
||||||
|
name="dbname",
|
||||||
|
description=("The database name to use when connecting with the Postgres server."),
|
||||||
|
short_decls=["-d"],
|
||||||
|
default="postgres",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
user = TextOption(
|
||||||
|
name="user",
|
||||||
|
description=("PostgreSQL user name to connect as."),
|
||||||
|
short_decls=["-u", "--username", "-U"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
password = TextOption(
|
||||||
|
name="password",
|
||||||
|
description=("Password to be used if the server demands password authentication."),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
passfile = PathOption(
|
||||||
|
name="passfile",
|
||||||
|
description=(
|
||||||
|
"Specifies the name of the file used to store passwords. Defaults to "
|
||||||
|
r"~/.pgpass, or %APPDATA%\postgresql\pgpass.conf on Windows. (No error is "
|
||||||
|
"reported if this file does not exist.)"
|
||||||
|
),
|
||||||
|
resolve_path=True,
|
||||||
|
exists=False,
|
||||||
|
file_okay=True,
|
||||||
|
dir_okay=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
require_auth = SelectOption(
|
||||||
|
name="require_auth",
|
||||||
|
description=(
|
||||||
|
"Specifies the authentication method that the client requires from the server. "
|
||||||
|
"If the server does not use the required method to authenticate the client, or "
|
||||||
|
"if the authentication handshake is not fully completed by the server, the "
|
||||||
|
"connection will fail."
|
||||||
|
),
|
||||||
|
choices=["password", "md5", "gss", "sspi", "scram-sha-256", "none"],
|
||||||
|
)
|
||||||
|
|
||||||
|
channel_binding = SelectOption(
|
||||||
|
name="channel_binding",
|
||||||
|
description=(
|
||||||
|
"This option controls the client's use of channel binding. A setting of "
|
||||||
|
"require means that the connection must employ channel binding, prefer "
|
||||||
|
"means that the client will choose channel binding if available, and "
|
||||||
|
"disable prevents the use of channel binding. The default is prefer if "
|
||||||
|
"PostgreSQL is compiled with SSL support; otherwise the default is disable."
|
||||||
|
),
|
||||||
|
choices=["require", "prefer", "disable"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _int_validator(s: str | None) -> tuple[bool, str]:
|
||||||
|
if s is None:
|
||||||
|
return True, ""
|
||||||
|
try:
|
||||||
|
_ = int(s)
|
||||||
|
except ValueError:
|
||||||
|
return False, f"Cannot convert {s} to an int!"
|
||||||
|
else:
|
||||||
|
return True, ""
|
||||||
|
|
||||||
|
|
||||||
|
connect_timeout = TextOption(
|
||||||
|
name="connect_timeout",
|
||||||
|
description=(
|
||||||
|
"Maximum time to wait while connecting, in seconds (write as an integer, "
|
||||||
|
"e.g., 10)."
|
||||||
|
),
|
||||||
|
validator=_int_validator,
|
||||||
|
)
|
||||||
|
|
||||||
|
sslmode = SelectOption(
|
||||||
|
name="sslmode",
|
||||||
|
description=(
|
||||||
|
"Determines whether or with what priority a secure SSL TCP/IP connection will "
|
||||||
|
"be negotiated with the server."
|
||||||
|
),
|
||||||
|
choices=["disable", "allow", "prefer", "require", "verify-ca", "verify-full"],
|
||||||
|
default="prefer",
|
||||||
|
)
|
||||||
|
|
||||||
|
sslcert = PathOption(
|
||||||
|
name="sslcert",
|
||||||
|
description=(
|
||||||
|
"Specifies the file name of the client SSL certificate. "
|
||||||
|
"Ignored if an SSL connection is not made."
|
||||||
|
),
|
||||||
|
default="~/.postgresql/postgresql.crt",
|
||||||
|
)
|
||||||
|
|
||||||
|
sslkey = TextOption(
|
||||||
|
name="sslkey",
|
||||||
|
description=(
|
||||||
|
"Specifies the location for the secret key used for the client certificate. "
|
||||||
|
"It can either specify a file name that will be used instead of the default "
|
||||||
|
"~/.postgresql/postgresql.key, or it can specify a key obtained from an "
|
||||||
|
"external engine. An external engine specification should consist of a "
|
||||||
|
"colon-separated engine name and an engine-specific key identifier. This "
|
||||||
|
"parameter is ignored if an SSL connection is not made."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
POSTGRES_OPTIONS = [
|
||||||
|
host,
|
||||||
|
port,
|
||||||
|
dbname,
|
||||||
|
user,
|
||||||
|
password,
|
||||||
|
passfile,
|
||||||
|
require_auth,
|
||||||
|
channel_binding,
|
||||||
|
connect_timeout,
|
||||||
|
sslmode,
|
||||||
|
sslcert,
|
||||||
|
sslkey,
|
||||||
|
]
|
74
src/harlequin_postgres/completions.py
Normal file
74
src/harlequin_postgres/completions.py
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import csv
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from harlequin import HarlequinCompletion
|
||||||
|
from psycopg import Connection
|
||||||
|
|
||||||
|
|
||||||
|
def _get_completions(conn: Connection) -> list[HarlequinCompletion]:
|
||||||
|
completions: list[HarlequinCompletion] = []
|
||||||
|
|
||||||
|
# source: https://www.postgresql.org/docs/current/sql-keywords-appendix.html
|
||||||
|
keyword_path = Path(__file__).parent / "keywords.tsv"
|
||||||
|
with keyword_path.open("r") as f:
|
||||||
|
keyword_reader = csv.reader(
|
||||||
|
f,
|
||||||
|
delimiter="\t",
|
||||||
|
)
|
||||||
|
_header = next(keyword_reader)
|
||||||
|
for keyword, kind, _, _, _ in keyword_reader:
|
||||||
|
completions.append(
|
||||||
|
HarlequinCompletion(
|
||||||
|
label=keyword.lower(),
|
||||||
|
type_label="kw",
|
||||||
|
value=keyword.lower(),
|
||||||
|
priority=100 if kind.startswith("reserved") else 1000,
|
||||||
|
context=None,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute(
|
||||||
|
r"""
|
||||||
|
select distinct
|
||||||
|
routine_name as label,
|
||||||
|
case when routine_type is null then 'agg' else 'fn' end as type_label,
|
||||||
|
case
|
||||||
|
when routine_schema = 'pg_catalog' --
|
||||||
|
then null
|
||||||
|
else routine_schema
|
||||||
|
end as context
|
||||||
|
from information_schema.routines
|
||||||
|
where
|
||||||
|
length(routine_name) < 37
|
||||||
|
and routine_name not ilike '\_%'
|
||||||
|
and routine_name not ilike 'pg\_%'
|
||||||
|
and routine_name not ilike 'binary\_upgrade\_%'
|
||||||
|
|
||||||
|
;"""
|
||||||
|
)
|
||||||
|
results = cur.fetchall()
|
||||||
|
for label, type_label, context in results:
|
||||||
|
completions.append(
|
||||||
|
HarlequinCompletion(
|
||||||
|
label=label,
|
||||||
|
type_label=type_label,
|
||||||
|
value=label,
|
||||||
|
priority=1000,
|
||||||
|
context=context,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute("""select distinct name as label from pg_settings""")
|
||||||
|
results = cur.fetchall()
|
||||||
|
for (label,) in results:
|
||||||
|
completions.append(
|
||||||
|
HarlequinCompletion(
|
||||||
|
label=label, type_label="set", value=label, priority=2000, context=None
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return sorted(completions)
|
143
src/harlequin_postgres/interactions.py
Normal file
143
src/harlequin_postgres/interactions.py
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from textwrap import dedent
|
||||||
|
from typing import TYPE_CHECKING, Literal, Sequence
|
||||||
|
|
||||||
|
from harlequin.catalog import CatalogItem
|
||||||
|
from harlequin.exception import HarlequinQueryError
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from harlequin.driver import HarlequinDriver
|
||||||
|
from harlequin_duckdb.catalog import (
|
||||||
|
ColumnCatalogItem,
|
||||||
|
DatabaseCatalogItem,
|
||||||
|
RelationCatalogItem,
|
||||||
|
SchemaCatalogItem,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def execute_use_statement(
|
||||||
|
item: "SchemaCatalogItem",
|
||||||
|
driver: "HarlequinDriver",
|
||||||
|
) -> None:
|
||||||
|
if item.connection is None:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
item.connection.execute(f"set search_path to {item.qualified_identifier}")
|
||||||
|
except HarlequinQueryError:
|
||||||
|
driver.notify("Could not switch context", severity="error")
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
driver.notify(f"Editor context switched to {item.label}")
|
||||||
|
|
||||||
|
|
||||||
|
def execute_drop_schema_statement(
|
||||||
|
item: "SchemaCatalogItem",
|
||||||
|
driver: "HarlequinDriver",
|
||||||
|
) -> None:
|
||||||
|
def _drop_schema() -> None:
|
||||||
|
if item.connection is None:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
item.connection.execute(f"drop schema {item.qualified_identifier} cascade")
|
||||||
|
except HarlequinQueryError:
|
||||||
|
driver.notify(f"Could not drop schema {item.label}", severity="error")
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
driver.notify(f"Dropped schema {item.label}")
|
||||||
|
driver.refresh_catalog()
|
||||||
|
|
||||||
|
if item.children or item.fetch_children():
|
||||||
|
driver.confirm_and_execute(callback=_drop_schema)
|
||||||
|
else:
|
||||||
|
_drop_schema()
|
||||||
|
|
||||||
|
|
||||||
|
def execute_drop_database_statement(
|
||||||
|
item: "DatabaseCatalogItem",
|
||||||
|
driver: "HarlequinDriver",
|
||||||
|
) -> None:
|
||||||
|
def _drop_database() -> None:
|
||||||
|
if item.connection is None:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
item.connection.execute(f"drop database {item.qualified_identifier}")
|
||||||
|
except HarlequinQueryError:
|
||||||
|
driver.notify(f"Could not drop database {item.label}", severity="error")
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
driver.notify(f"Dropped database {item.label}")
|
||||||
|
driver.refresh_catalog()
|
||||||
|
|
||||||
|
if item.children or item.fetch_children():
|
||||||
|
driver.confirm_and_execute(callback=_drop_database)
|
||||||
|
else:
|
||||||
|
_drop_database()
|
||||||
|
|
||||||
|
|
||||||
|
def execute_drop_relation_statement(
|
||||||
|
item: "RelationCatalogItem",
|
||||||
|
driver: "HarlequinDriver",
|
||||||
|
relation_type: Literal["view", "table", "foreign table"],
|
||||||
|
) -> None:
|
||||||
|
def _drop_relation() -> None:
|
||||||
|
if item.connection is None:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
item.connection.execute(f"drop {relation_type} {item.qualified_identifier}")
|
||||||
|
except HarlequinQueryError:
|
||||||
|
driver.notify(
|
||||||
|
f"Could not drop {relation_type} {item.label}", severity="error"
|
||||||
|
)
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
driver.notify(f"Dropped {relation_type} {item.label}")
|
||||||
|
driver.refresh_catalog()
|
||||||
|
|
||||||
|
driver.confirm_and_execute(callback=_drop_relation)
|
||||||
|
|
||||||
|
|
||||||
|
def execute_drop_table_statement(
|
||||||
|
item: "RelationCatalogItem", driver: "HarlequinDriver"
|
||||||
|
) -> None:
|
||||||
|
execute_drop_relation_statement(item=item, driver=driver, relation_type="table")
|
||||||
|
|
||||||
|
|
||||||
|
def execute_drop_foreign_table_statement(
|
||||||
|
item: "RelationCatalogItem", driver: "HarlequinDriver"
|
||||||
|
) -> None:
|
||||||
|
execute_drop_relation_statement(
|
||||||
|
item=item, driver=driver, relation_type="foreign table"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def execute_drop_view_statement(
|
||||||
|
item: "RelationCatalogItem", driver: "HarlequinDriver"
|
||||||
|
) -> None:
|
||||||
|
execute_drop_relation_statement(item=item, driver=driver, relation_type="view")
|
||||||
|
|
||||||
|
|
||||||
|
def show_select_star(
|
||||||
|
item: "RelationCatalogItem",
|
||||||
|
driver: "HarlequinDriver",
|
||||||
|
) -> None:
|
||||||
|
driver.insert_text_in_new_buffer(
|
||||||
|
dedent(
|
||||||
|
f"""
|
||||||
|
select *
|
||||||
|
from {item.qualified_identifier}
|
||||||
|
limit 100
|
||||||
|
""".strip("\n")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def insert_columns_at_cursor(
|
||||||
|
item: "RelationCatalogItem",
|
||||||
|
driver: "HarlequinDriver",
|
||||||
|
) -> None:
|
||||||
|
if item.loaded:
|
||||||
|
cols: Sequence["CatalogItem" | "ColumnCatalogItem"] = item.children
|
||||||
|
else:
|
||||||
|
cols = item.fetch_children()
|
||||||
|
driver.insert_text_at_selection(text=",\n".join(c.query_name for c in cols))
|
842
src/harlequin_postgres/keywords.tsv
Normal file
842
src/harlequin_postgres/keywords.tsv
Normal file
|
@ -0,0 +1,842 @@
|
||||||
|
Key Word PostgreSQL SQL:2023 SQL:2016 SQL-92
|
||||||
|
A non-reserved non-reserved
|
||||||
|
ABORT non-reserved
|
||||||
|
ABS reserved reserved
|
||||||
|
ABSENT non-reserved reserved reserved
|
||||||
|
ABSOLUTE non-reserved non-reserved non-reserved reserved
|
||||||
|
ACCESS non-reserved
|
||||||
|
ACCORDING non-reserved non-reserved
|
||||||
|
ACOS reserved reserved
|
||||||
|
ACTION non-reserved non-reserved non-reserved reserved
|
||||||
|
ADA non-reserved non-reserved non-reserved
|
||||||
|
ADD non-reserved non-reserved non-reserved reserved
|
||||||
|
ADMIN non-reserved non-reserved non-reserved
|
||||||
|
AFTER non-reserved non-reserved non-reserved
|
||||||
|
AGGREGATE non-reserved
|
||||||
|
ALL reserved reserved reserved reserved
|
||||||
|
ALLOCATE reserved reserved reserved
|
||||||
|
ALSO non-reserved
|
||||||
|
ALTER non-reserved reserved reserved reserved
|
||||||
|
ALWAYS non-reserved non-reserved non-reserved
|
||||||
|
ANALYSE reserved
|
||||||
|
ANALYZE reserved
|
||||||
|
AND reserved reserved reserved reserved
|
||||||
|
ANY reserved reserved reserved reserved
|
||||||
|
ANY_VALUE reserved
|
||||||
|
ARE reserved reserved reserved
|
||||||
|
ARRAY reserved, requires AS reserved reserved
|
||||||
|
ARRAY_AGG reserved reserved
|
||||||
|
ARRAY_MAX_CARDINALITY reserved reserved
|
||||||
|
AS reserved, requires AS reserved reserved reserved
|
||||||
|
ASC reserved non-reserved non-reserved reserved
|
||||||
|
ASENSITIVE non-reserved reserved reserved
|
||||||
|
ASIN reserved reserved
|
||||||
|
ASSERTION non-reserved non-reserved non-reserved reserved
|
||||||
|
ASSIGNMENT non-reserved non-reserved non-reserved
|
||||||
|
ASYMMETRIC reserved reserved reserved
|
||||||
|
AT non-reserved reserved reserved reserved
|
||||||
|
ATAN reserved reserved
|
||||||
|
ATOMIC non-reserved reserved reserved
|
||||||
|
ATTACH non-reserved
|
||||||
|
ATTRIBUTE non-reserved non-reserved non-reserved
|
||||||
|
ATTRIBUTES non-reserved non-reserved
|
||||||
|
AUTHORIZATION reserved (can be function or type) reserved reserved reserved
|
||||||
|
AVG reserved reserved reserved
|
||||||
|
BACKWARD non-reserved
|
||||||
|
BASE64 non-reserved non-reserved
|
||||||
|
BEFORE non-reserved non-reserved non-reserved
|
||||||
|
BEGIN non-reserved reserved reserved reserved
|
||||||
|
BEGIN_FRAME reserved reserved
|
||||||
|
BEGIN_PARTITION reserved reserved
|
||||||
|
BERNOULLI non-reserved non-reserved
|
||||||
|
BETWEEN non-reserved (cannot be function or type) reserved reserved reserved
|
||||||
|
BIGINT non-reserved (cannot be function or type) reserved reserved
|
||||||
|
BINARY reserved (can be function or type) reserved reserved
|
||||||
|
BIT non-reserved (cannot be function or type) reserved
|
||||||
|
BIT_LENGTH reserved
|
||||||
|
BLOB reserved reserved
|
||||||
|
BLOCKED non-reserved non-reserved
|
||||||
|
BOM non-reserved non-reserved
|
||||||
|
BOOLEAN non-reserved (cannot be function or type) reserved reserved
|
||||||
|
BOTH reserved reserved reserved reserved
|
||||||
|
BREADTH non-reserved non-reserved non-reserved
|
||||||
|
BTRIM reserved
|
||||||
|
BY non-reserved reserved reserved reserved
|
||||||
|
C non-reserved non-reserved non-reserved
|
||||||
|
CACHE non-reserved
|
||||||
|
CALL non-reserved reserved reserved
|
||||||
|
CALLED non-reserved reserved reserved
|
||||||
|
CARDINALITY reserved reserved
|
||||||
|
CASCADE non-reserved non-reserved non-reserved reserved
|
||||||
|
CASCADED non-reserved reserved reserved reserved
|
||||||
|
CASE reserved reserved reserved reserved
|
||||||
|
CAST reserved reserved reserved reserved
|
||||||
|
CATALOG non-reserved non-reserved non-reserved reserved
|
||||||
|
CATALOG_NAME non-reserved non-reserved non-reserved
|
||||||
|
CEIL reserved reserved
|
||||||
|
CEILING reserved reserved
|
||||||
|
CHAIN non-reserved non-reserved non-reserved
|
||||||
|
CHAINING non-reserved non-reserved
|
||||||
|
CHAR non-reserved (cannot be function or type), requires AS reserved reserved reserved
|
||||||
|
CHARACTER non-reserved (cannot be function or type), requires AS reserved reserved reserved
|
||||||
|
CHARACTERISTICS non-reserved non-reserved non-reserved
|
||||||
|
CHARACTERS non-reserved non-reserved
|
||||||
|
CHARACTER_LENGTH reserved reserved reserved
|
||||||
|
CHARACTER_SET_CATALOG non-reserved non-reserved non-reserved
|
||||||
|
CHARACTER_SET_NAME non-reserved non-reserved non-reserved
|
||||||
|
CHARACTER_SET_SCHEMA non-reserved non-reserved non-reserved
|
||||||
|
CHAR_LENGTH reserved reserved reserved
|
||||||
|
CHECK reserved reserved reserved reserved
|
||||||
|
CHECKPOINT non-reserved
|
||||||
|
CLASS non-reserved
|
||||||
|
CLASSIFIER reserved reserved
|
||||||
|
CLASS_ORIGIN non-reserved non-reserved non-reserved
|
||||||
|
CLOB reserved reserved
|
||||||
|
CLOSE non-reserved reserved reserved reserved
|
||||||
|
CLUSTER non-reserved
|
||||||
|
COALESCE non-reserved (cannot be function or type) reserved reserved reserved
|
||||||
|
COBOL non-reserved non-reserved non-reserved
|
||||||
|
COLLATE reserved reserved reserved reserved
|
||||||
|
COLLATION reserved (can be function or type) non-reserved non-reserved reserved
|
||||||
|
COLLATION_CATALOG non-reserved non-reserved non-reserved
|
||||||
|
COLLATION_NAME non-reserved non-reserved non-reserved
|
||||||
|
COLLATION_SCHEMA non-reserved non-reserved non-reserved
|
||||||
|
COLLECT reserved reserved
|
||||||
|
COLUMN reserved reserved reserved reserved
|
||||||
|
COLUMNS non-reserved non-reserved non-reserved
|
||||||
|
COLUMN_NAME non-reserved non-reserved non-reserved
|
||||||
|
COMMAND_FUNCTION non-reserved non-reserved non-reserved
|
||||||
|
COMMAND_FUNCTION_CODE non-reserved non-reserved
|
||||||
|
COMMENT non-reserved
|
||||||
|
COMMENTS non-reserved
|
||||||
|
COMMIT non-reserved reserved reserved reserved
|
||||||
|
COMMITTED non-reserved non-reserved non-reserved non-reserved
|
||||||
|
COMPRESSION non-reserved
|
||||||
|
CONCURRENTLY reserved (can be function or type)
|
||||||
|
CONDITION reserved reserved
|
||||||
|
CONDITIONAL non-reserved non-reserved
|
||||||
|
CONDITION_NUMBER non-reserved non-reserved non-reserved
|
||||||
|
CONFIGURATION non-reserved
|
||||||
|
CONFLICT non-reserved
|
||||||
|
CONNECT reserved reserved reserved
|
||||||
|
CONNECTION non-reserved non-reserved non-reserved reserved
|
||||||
|
CONNECTION_NAME non-reserved non-reserved non-reserved
|
||||||
|
CONSTRAINT reserved reserved reserved reserved
|
||||||
|
CONSTRAINTS non-reserved non-reserved non-reserved reserved
|
||||||
|
CONSTRAINT_CATALOG non-reserved non-reserved non-reserved
|
||||||
|
CONSTRAINT_NAME non-reserved non-reserved non-reserved
|
||||||
|
CONSTRAINT_SCHEMA non-reserved non-reserved non-reserved
|
||||||
|
CONSTRUCTOR non-reserved non-reserved
|
||||||
|
CONTAINS reserved reserved
|
||||||
|
CONTENT non-reserved non-reserved non-reserved
|
||||||
|
CONTINUE non-reserved non-reserved non-reserved reserved
|
||||||
|
CONTROL non-reserved non-reserved
|
||||||
|
CONVERSION non-reserved
|
||||||
|
CONVERT reserved reserved reserved
|
||||||
|
COPARTITION non-reserved
|
||||||
|
COPY non-reserved reserved reserved
|
||||||
|
CORR reserved reserved
|
||||||
|
CORRESPONDING reserved reserved reserved
|
||||||
|
COS reserved reserved
|
||||||
|
COSH reserved reserved
|
||||||
|
COST non-reserved
|
||||||
|
COUNT reserved reserved reserved
|
||||||
|
COVAR_POP reserved reserved
|
||||||
|
COVAR_SAMP reserved reserved
|
||||||
|
CREATE reserved, requires AS reserved reserved reserved
|
||||||
|
CROSS reserved (can be function or type) reserved reserved reserved
|
||||||
|
CSV non-reserved
|
||||||
|
CUBE non-reserved reserved reserved
|
||||||
|
CUME_DIST reserved reserved
|
||||||
|
CURRENT non-reserved reserved reserved reserved
|
||||||
|
CURRENT_CATALOG reserved reserved reserved
|
||||||
|
CURRENT_DATE reserved reserved reserved reserved
|
||||||
|
CURRENT_DEFAULT_TRANSFORM_GROUP reserved reserved
|
||||||
|
CURRENT_PATH reserved reserved
|
||||||
|
CURRENT_ROLE reserved reserved reserved
|
||||||
|
CURRENT_ROW reserved reserved
|
||||||
|
CURRENT_SCHEMA reserved (can be function or type) reserved reserved
|
||||||
|
CURRENT_TIME reserved reserved reserved reserved
|
||||||
|
CURRENT_TIMESTAMP reserved reserved reserved reserved
|
||||||
|
CURRENT_TRANSFORM_GROUP_FOR_TYPE reserved reserved
|
||||||
|
CURRENT_USER reserved reserved reserved reserved
|
||||||
|
CURSOR non-reserved reserved reserved reserved
|
||||||
|
CURSOR_NAME non-reserved non-reserved non-reserved
|
||||||
|
CYCLE non-reserved reserved reserved
|
||||||
|
DATA non-reserved non-reserved non-reserved non-reserved
|
||||||
|
DATABASE non-reserved
|
||||||
|
DATALINK reserved reserved
|
||||||
|
DATE reserved reserved reserved
|
||||||
|
DATETIME_INTERVAL_CODE non-reserved non-reserved non-reserved
|
||||||
|
DATETIME_INTERVAL_PRECISION non-reserved non-reserved non-reserved
|
||||||
|
DAY non-reserved, requires AS reserved reserved reserved
|
||||||
|
DB non-reserved non-reserved
|
||||||
|
DEALLOCATE non-reserved reserved reserved reserved
|
||||||
|
DEC non-reserved (cannot be function or type) reserved reserved reserved
|
||||||
|
DECFLOAT reserved reserved
|
||||||
|
DECIMAL non-reserved (cannot be function or type) reserved reserved reserved
|
||||||
|
DECLARE non-reserved reserved reserved reserved
|
||||||
|
DEFAULT reserved reserved reserved reserved
|
||||||
|
DEFAULTS non-reserved non-reserved non-reserved
|
||||||
|
DEFERRABLE reserved non-reserved non-reserved reserved
|
||||||
|
DEFERRED non-reserved non-reserved non-reserved reserved
|
||||||
|
DEFINE reserved reserved
|
||||||
|
DEFINED non-reserved non-reserved
|
||||||
|
DEFINER non-reserved non-reserved non-reserved
|
||||||
|
DEGREE non-reserved non-reserved
|
||||||
|
DELETE non-reserved reserved reserved reserved
|
||||||
|
DELIMITER non-reserved
|
||||||
|
DELIMITERS non-reserved
|
||||||
|
DENSE_RANK reserved reserved
|
||||||
|
DEPENDS non-reserved
|
||||||
|
DEPTH non-reserved non-reserved non-reserved
|
||||||
|
DEREF reserved reserved
|
||||||
|
DERIVED non-reserved non-reserved
|
||||||
|
DESC reserved non-reserved non-reserved reserved
|
||||||
|
DESCRIBE reserved reserved reserved
|
||||||
|
DESCRIPTOR non-reserved non-reserved reserved
|
||||||
|
DETACH non-reserved
|
||||||
|
DETERMINISTIC reserved reserved
|
||||||
|
DIAGNOSTICS non-reserved non-reserved reserved
|
||||||
|
DICTIONARY non-reserved
|
||||||
|
DISABLE non-reserved
|
||||||
|
DISCARD non-reserved
|
||||||
|
DISCONNECT reserved reserved reserved
|
||||||
|
DISPATCH non-reserved non-reserved
|
||||||
|
DISTINCT reserved reserved reserved reserved
|
||||||
|
DLNEWCOPY reserved reserved
|
||||||
|
DLPREVIOUSCOPY reserved reserved
|
||||||
|
DLURLCOMPLETE reserved reserved
|
||||||
|
DLURLCOMPLETEONLY reserved reserved
|
||||||
|
DLURLCOMPLETEWRITE reserved reserved
|
||||||
|
DLURLPATH reserved reserved
|
||||||
|
DLURLPATHONLY reserved reserved
|
||||||
|
DLURLPATHWRITE reserved reserved
|
||||||
|
DLURLSCHEME reserved reserved
|
||||||
|
DLURLSERVER reserved reserved
|
||||||
|
DLVALUE reserved reserved
|
||||||
|
DO reserved
|
||||||
|
DOCUMENT non-reserved non-reserved non-reserved
|
||||||
|
DOMAIN non-reserved non-reserved non-reserved reserved
|
||||||
|
DOUBLE non-reserved reserved reserved reserved
|
||||||
|
DROP non-reserved reserved reserved reserved
|
||||||
|
DYNAMIC reserved reserved
|
||||||
|
DYNAMIC_FUNCTION non-reserved non-reserved non-reserved
|
||||||
|
DYNAMIC_FUNCTION_CODE non-reserved non-reserved
|
||||||
|
EACH non-reserved reserved reserved
|
||||||
|
ELEMENT reserved reserved
|
||||||
|
ELSE reserved reserved reserved reserved
|
||||||
|
EMPTY reserved reserved
|
||||||
|
ENABLE non-reserved
|
||||||
|
ENCODING non-reserved non-reserved non-reserved
|
||||||
|
ENCRYPTED non-reserved
|
||||||
|
END reserved reserved reserved reserved
|
||||||
|
END-EXEC reserved reserved reserved
|
||||||
|
END_FRAME reserved reserved
|
||||||
|
END_PARTITION reserved reserved
|
||||||
|
ENFORCED non-reserved non-reserved
|
||||||
|
ENUM non-reserved
|
||||||
|
EQUALS reserved reserved
|
||||||
|
ERROR non-reserved non-reserved
|
||||||
|
ESCAPE non-reserved reserved reserved reserved
|
||||||
|
EVENT non-reserved
|
||||||
|
EVERY reserved reserved
|
||||||
|
EXCEPT reserved, requires AS reserved reserved reserved
|
||||||
|
EXCEPTION reserved
|
||||||
|
EXCLUDE non-reserved non-reserved non-reserved
|
||||||
|
EXCLUDING non-reserved non-reserved non-reserved
|
||||||
|
EXCLUSIVE non-reserved
|
||||||
|
EXEC reserved reserved reserved
|
||||||
|
EXECUTE non-reserved reserved reserved reserved
|
||||||
|
EXISTS non-reserved (cannot be function or type) reserved reserved reserved
|
||||||
|
EXP reserved reserved
|
||||||
|
EXPLAIN non-reserved
|
||||||
|
EXPRESSION non-reserved non-reserved non-reserved
|
||||||
|
EXTENSION non-reserved
|
||||||
|
EXTERNAL non-reserved reserved reserved reserved
|
||||||
|
EXTRACT non-reserved (cannot be function or type) reserved reserved reserved
|
||||||
|
FALSE reserved reserved reserved reserved
|
||||||
|
FAMILY non-reserved
|
||||||
|
FETCH reserved, requires AS reserved reserved reserved
|
||||||
|
FILE non-reserved non-reserved
|
||||||
|
FILTER non-reserved, requires AS reserved reserved
|
||||||
|
FINAL non-reserved non-reserved
|
||||||
|
FINALIZE non-reserved
|
||||||
|
FINISH non-reserved non-reserved
|
||||||
|
FIRST non-reserved non-reserved non-reserved reserved
|
||||||
|
FIRST_VALUE reserved reserved
|
||||||
|
FLAG non-reserved non-reserved
|
||||||
|
FLOAT non-reserved (cannot be function or type) reserved reserved reserved
|
||||||
|
FLOOR reserved reserved
|
||||||
|
FOLLOWING non-reserved non-reserved non-reserved
|
||||||
|
FOR reserved, requires AS reserved reserved reserved
|
||||||
|
FORCE non-reserved
|
||||||
|
FOREIGN reserved reserved reserved reserved
|
||||||
|
FORMAT non-reserved non-reserved non-reserved
|
||||||
|
FORTRAN non-reserved non-reserved non-reserved
|
||||||
|
FORWARD non-reserved
|
||||||
|
FOUND non-reserved non-reserved reserved
|
||||||
|
FRAME_ROW reserved reserved
|
||||||
|
FREE reserved reserved
|
||||||
|
FREEZE reserved (can be function or type)
|
||||||
|
FROM reserved, requires AS reserved reserved reserved
|
||||||
|
FS non-reserved non-reserved
|
||||||
|
FULFILL non-reserved non-reserved
|
||||||
|
FULL reserved (can be function or type) reserved reserved reserved
|
||||||
|
FUNCTION non-reserved reserved reserved
|
||||||
|
FUNCTIONS non-reserved
|
||||||
|
FUSION reserved reserved
|
||||||
|
G non-reserved non-reserved
|
||||||
|
GENERAL non-reserved non-reserved
|
||||||
|
GENERATED non-reserved non-reserved non-reserved
|
||||||
|
GET reserved reserved reserved
|
||||||
|
GLOBAL non-reserved reserved reserved reserved
|
||||||
|
GO non-reserved non-reserved reserved
|
||||||
|
GOTO non-reserved non-reserved reserved
|
||||||
|
GRANT reserved, requires AS reserved reserved reserved
|
||||||
|
GRANTED non-reserved non-reserved non-reserved
|
||||||
|
GREATEST non-reserved (cannot be function or type) reserved
|
||||||
|
GROUP reserved, requires AS reserved reserved reserved
|
||||||
|
GROUPING non-reserved (cannot be function or type) reserved reserved
|
||||||
|
GROUPS non-reserved reserved reserved
|
||||||
|
HANDLER non-reserved
|
||||||
|
HAVING reserved, requires AS reserved reserved reserved
|
||||||
|
HEADER non-reserved
|
||||||
|
HEX non-reserved non-reserved
|
||||||
|
HIERARCHY non-reserved non-reserved
|
||||||
|
HOLD non-reserved reserved reserved
|
||||||
|
HOUR non-reserved, requires AS reserved reserved reserved
|
||||||
|
ID non-reserved non-reserved
|
||||||
|
IDENTITY non-reserved reserved reserved reserved
|
||||||
|
IF non-reserved
|
||||||
|
IGNORE non-reserved non-reserved
|
||||||
|
ILIKE reserved (can be function or type)
|
||||||
|
IMMEDIATE non-reserved non-reserved non-reserved reserved
|
||||||
|
IMMEDIATELY non-reserved non-reserved
|
||||||
|
IMMUTABLE non-reserved
|
||||||
|
IMPLEMENTATION non-reserved non-reserved
|
||||||
|
IMPLICIT non-reserved
|
||||||
|
IMPORT non-reserved reserved reserved
|
||||||
|
IN reserved reserved reserved reserved
|
||||||
|
INCLUDE non-reserved
|
||||||
|
INCLUDING non-reserved non-reserved non-reserved
|
||||||
|
INCREMENT non-reserved non-reserved non-reserved
|
||||||
|
INDENT non-reserved non-reserved non-reserved
|
||||||
|
INDEX non-reserved
|
||||||
|
INDEXES non-reserved
|
||||||
|
INDICATOR reserved reserved reserved
|
||||||
|
INHERIT non-reserved
|
||||||
|
INHERITS non-reserved
|
||||||
|
INITIAL reserved reserved
|
||||||
|
INITIALLY reserved non-reserved non-reserved reserved
|
||||||
|
INLINE non-reserved
|
||||||
|
INNER reserved (can be function or type) reserved reserved reserved
|
||||||
|
INOUT non-reserved (cannot be function or type) reserved reserved
|
||||||
|
INPUT non-reserved non-reserved non-reserved reserved
|
||||||
|
INSENSITIVE non-reserved reserved reserved reserved
|
||||||
|
INSERT non-reserved reserved reserved reserved
|
||||||
|
INSTANCE non-reserved non-reserved
|
||||||
|
INSTANTIABLE non-reserved non-reserved
|
||||||
|
INSTEAD non-reserved non-reserved non-reserved
|
||||||
|
INT non-reserved (cannot be function or type) reserved reserved reserved
|
||||||
|
INTEGER non-reserved (cannot be function or type) reserved reserved reserved
|
||||||
|
INTEGRITY non-reserved non-reserved
|
||||||
|
INTERSECT reserved, requires AS reserved reserved reserved
|
||||||
|
INTERSECTION reserved reserved
|
||||||
|
INTERVAL non-reserved (cannot be function or type) reserved reserved reserved
|
||||||
|
INTO reserved, requires AS reserved reserved reserved
|
||||||
|
INVOKER non-reserved non-reserved non-reserved
|
||||||
|
IS reserved (can be function or type) reserved reserved reserved
|
||||||
|
ISNULL reserved (can be function or type), requires AS
|
||||||
|
ISOLATION non-reserved non-reserved non-reserved reserved
|
||||||
|
JOIN reserved (can be function or type) reserved reserved reserved
|
||||||
|
JSON non-reserved reserved
|
||||||
|
JSON_ARRAY non-reserved (cannot be function or type) reserved reserved
|
||||||
|
JSON_ARRAYAGG non-reserved (cannot be function or type) reserved reserved
|
||||||
|
JSON_EXISTS reserved reserved
|
||||||
|
JSON_OBJECT non-reserved (cannot be function or type) reserved reserved
|
||||||
|
JSON_OBJECTAGG non-reserved (cannot be function or type) reserved reserved
|
||||||
|
JSON_QUERY reserved reserved
|
||||||
|
JSON_SCALAR reserved
|
||||||
|
JSON_SERIALIZE reserved
|
||||||
|
JSON_TABLE reserved reserved
|
||||||
|
JSON_TABLE_PRIMITIVE reserved reserved
|
||||||
|
JSON_VALUE reserved reserved
|
||||||
|
K non-reserved non-reserved
|
||||||
|
KEEP non-reserved non-reserved
|
||||||
|
KEY non-reserved non-reserved non-reserved reserved
|
||||||
|
KEYS non-reserved non-reserved non-reserved
|
||||||
|
KEY_MEMBER non-reserved non-reserved
|
||||||
|
KEY_TYPE non-reserved non-reserved
|
||||||
|
LABEL non-reserved
|
||||||
|
LAG reserved reserved
|
||||||
|
LANGUAGE non-reserved reserved reserved reserved
|
||||||
|
LARGE non-reserved reserved reserved
|
||||||
|
LAST non-reserved non-reserved non-reserved reserved
|
||||||
|
LAST_VALUE reserved reserved
|
||||||
|
LATERAL reserved reserved reserved
|
||||||
|
LEAD reserved reserved
|
||||||
|
LEADING reserved reserved reserved reserved
|
||||||
|
LEAKPROOF non-reserved
|
||||||
|
LEAST non-reserved (cannot be function or type) reserved
|
||||||
|
LEFT reserved (can be function or type) reserved reserved reserved
|
||||||
|
LENGTH non-reserved non-reserved non-reserved
|
||||||
|
LEVEL non-reserved non-reserved non-reserved reserved
|
||||||
|
LIBRARY non-reserved non-reserved
|
||||||
|
LIKE reserved (can be function or type) reserved reserved reserved
|
||||||
|
LIKE_REGEX reserved reserved
|
||||||
|
LIMIT reserved, requires AS non-reserved non-reserved
|
||||||
|
LINK non-reserved non-reserved
|
||||||
|
LISTAGG reserved reserved
|
||||||
|
LISTEN non-reserved
|
||||||
|
LN reserved reserved
|
||||||
|
LOAD non-reserved
|
||||||
|
LOCAL non-reserved reserved reserved reserved
|
||||||
|
LOCALTIME reserved reserved reserved
|
||||||
|
LOCALTIMESTAMP reserved reserved reserved
|
||||||
|
LOCATION non-reserved non-reserved non-reserved
|
||||||
|
LOCATOR non-reserved non-reserved
|
||||||
|
LOCK non-reserved
|
||||||
|
LOCKED non-reserved
|
||||||
|
LOG reserved reserved
|
||||||
|
LOG10 reserved reserved
|
||||||
|
LOGGED non-reserved
|
||||||
|
LOWER reserved reserved reserved
|
||||||
|
LPAD reserved
|
||||||
|
LTRIM reserved
|
||||||
|
M non-reserved non-reserved
|
||||||
|
MAP non-reserved non-reserved
|
||||||
|
MAPPING non-reserved non-reserved non-reserved
|
||||||
|
MATCH non-reserved reserved reserved reserved
|
||||||
|
MATCHED non-reserved non-reserved non-reserved
|
||||||
|
MATCHES reserved reserved
|
||||||
|
MATCH_NUMBER reserved reserved
|
||||||
|
MATCH_RECOGNIZE reserved reserved
|
||||||
|
MATERIALIZED non-reserved
|
||||||
|
MAX reserved reserved reserved
|
||||||
|
MAXVALUE non-reserved non-reserved non-reserved
|
||||||
|
MEASURES non-reserved non-reserved
|
||||||
|
MEMBER reserved reserved
|
||||||
|
MERGE non-reserved reserved reserved
|
||||||
|
MESSAGE_LENGTH non-reserved non-reserved non-reserved
|
||||||
|
MESSAGE_OCTET_LENGTH non-reserved non-reserved non-reserved
|
||||||
|
MESSAGE_TEXT non-reserved non-reserved non-reserved
|
||||||
|
METHOD non-reserved reserved reserved
|
||||||
|
MIN reserved reserved reserved
|
||||||
|
MINUTE non-reserved, requires AS reserved reserved reserved
|
||||||
|
MINVALUE non-reserved non-reserved non-reserved
|
||||||
|
MOD reserved reserved
|
||||||
|
MODE non-reserved
|
||||||
|
MODIFIES reserved reserved
|
||||||
|
MODULE reserved reserved reserved
|
||||||
|
MONTH non-reserved, requires AS reserved reserved reserved
|
||||||
|
MORE non-reserved non-reserved non-reserved
|
||||||
|
MOVE non-reserved
|
||||||
|
MULTISET reserved reserved
|
||||||
|
MUMPS non-reserved non-reserved non-reserved
|
||||||
|
NAME non-reserved non-reserved non-reserved non-reserved
|
||||||
|
NAMES non-reserved non-reserved non-reserved reserved
|
||||||
|
NAMESPACE non-reserved non-reserved
|
||||||
|
NATIONAL non-reserved (cannot be function or type) reserved reserved reserved
|
||||||
|
NATURAL reserved (can be function or type) reserved reserved reserved
|
||||||
|
NCHAR non-reserved (cannot be function or type) reserved reserved reserved
|
||||||
|
NCLOB reserved reserved
|
||||||
|
NESTED non-reserved non-reserved
|
||||||
|
NESTING non-reserved non-reserved
|
||||||
|
NEW non-reserved reserved reserved
|
||||||
|
NEXT non-reserved non-reserved non-reserved reserved
|
||||||
|
NFC non-reserved non-reserved non-reserved
|
||||||
|
NFD non-reserved non-reserved non-reserved
|
||||||
|
NFKC non-reserved non-reserved non-reserved
|
||||||
|
NFKD non-reserved non-reserved non-reserved
|
||||||
|
NIL non-reserved non-reserved
|
||||||
|
NO non-reserved reserved reserved reserved
|
||||||
|
NONE non-reserved (cannot be function or type) reserved reserved
|
||||||
|
NORMALIZE non-reserved (cannot be function or type) reserved reserved
|
||||||
|
NORMALIZED non-reserved non-reserved non-reserved
|
||||||
|
NOT reserved reserved reserved reserved
|
||||||
|
NOTHING non-reserved
|
||||||
|
NOTIFY non-reserved
|
||||||
|
NOTNULL reserved (can be function or type), requires AS
|
||||||
|
NOWAIT non-reserved
|
||||||
|
NTH_VALUE reserved reserved
|
||||||
|
NTILE reserved reserved
|
||||||
|
NULL reserved reserved reserved reserved
|
||||||
|
NULLABLE non-reserved non-reserved non-reserved
|
||||||
|
NULLIF non-reserved (cannot be function or type) reserved reserved reserved
|
||||||
|
NULLS non-reserved non-reserved non-reserved
|
||||||
|
NULL_ORDERING non-reserved non-reserved
|
||||||
|
NUMBER non-reserved non-reserved non-reserved
|
||||||
|
NUMERIC non-reserved (cannot be function or type) reserved reserved reserved
|
||||||
|
OBJECT non-reserved non-reserved non-reserved
|
||||||
|
OCCURRENCE non-reserved non-reserved
|
||||||
|
OCCURRENCES_REGEX reserved reserved
|
||||||
|
OCTETS non-reserved non-reserved
|
||||||
|
OCTET_LENGTH reserved reserved reserved
|
||||||
|
OF non-reserved reserved reserved reserved
|
||||||
|
OFF non-reserved non-reserved non-reserved
|
||||||
|
OFFSET reserved, requires AS reserved reserved
|
||||||
|
OIDS non-reserved
|
||||||
|
OLD non-reserved reserved reserved
|
||||||
|
OMIT reserved reserved
|
||||||
|
ON reserved, requires AS reserved reserved reserved
|
||||||
|
ONE reserved reserved
|
||||||
|
ONLY reserved reserved reserved reserved
|
||||||
|
OPEN reserved reserved reserved
|
||||||
|
OPERATOR non-reserved
|
||||||
|
OPTION non-reserved non-reserved non-reserved reserved
|
||||||
|
OPTIONS non-reserved non-reserved non-reserved
|
||||||
|
OR reserved reserved reserved reserved
|
||||||
|
ORDER reserved, requires AS reserved reserved reserved
|
||||||
|
ORDERING non-reserved non-reserved
|
||||||
|
ORDINALITY non-reserved non-reserved non-reserved
|
||||||
|
OTHERS non-reserved non-reserved non-reserved
|
||||||
|
OUT non-reserved (cannot be function or type) reserved reserved
|
||||||
|
OUTER reserved (can be function or type) reserved reserved reserved
|
||||||
|
OUTPUT non-reserved non-reserved reserved
|
||||||
|
OVER non-reserved, requires AS reserved reserved
|
||||||
|
OVERFLOW non-reserved non-reserved
|
||||||
|
OVERLAPS reserved (can be function or type), requires AS reserved reserved reserved
|
||||||
|
OVERLAY non-reserved (cannot be function or type) reserved reserved
|
||||||
|
OVERRIDING non-reserved non-reserved non-reserved
|
||||||
|
OWNED non-reserved
|
||||||
|
OWNER non-reserved
|
||||||
|
P non-reserved non-reserved
|
||||||
|
PAD non-reserved non-reserved reserved
|
||||||
|
PARALLEL non-reserved
|
||||||
|
PARAMETER non-reserved reserved reserved
|
||||||
|
PARAMETER_MODE non-reserved non-reserved
|
||||||
|
PARAMETER_NAME non-reserved non-reserved
|
||||||
|
PARAMETER_ORDINAL_POSITION non-reserved non-reserved
|
||||||
|
PARAMETER_SPECIFIC_CATALOG non-reserved non-reserved
|
||||||
|
PARAMETER_SPECIFIC_NAME non-reserved non-reserved
|
||||||
|
PARAMETER_SPECIFIC_SCHEMA non-reserved non-reserved
|
||||||
|
PARSER non-reserved
|
||||||
|
PARTIAL non-reserved non-reserved non-reserved reserved
|
||||||
|
PARTITION non-reserved reserved reserved
|
||||||
|
PASCAL non-reserved non-reserved non-reserved
|
||||||
|
PASS non-reserved non-reserved
|
||||||
|
PASSING non-reserved non-reserved non-reserved
|
||||||
|
PASSTHROUGH non-reserved non-reserved
|
||||||
|
PASSWORD non-reserved
|
||||||
|
PAST non-reserved non-reserved
|
||||||
|
PATH non-reserved non-reserved
|
||||||
|
PATTERN reserved reserved
|
||||||
|
PER reserved reserved
|
||||||
|
PERCENT reserved reserved
|
||||||
|
PERCENTILE_CONT reserved reserved
|
||||||
|
PERCENTILE_DISC reserved reserved
|
||||||
|
PERCENT_RANK reserved reserved
|
||||||
|
PERIOD reserved reserved
|
||||||
|
PERMISSION non-reserved non-reserved
|
||||||
|
PERMUTE non-reserved non-reserved
|
||||||
|
PIPE non-reserved non-reserved
|
||||||
|
PLACING reserved non-reserved non-reserved
|
||||||
|
PLAN non-reserved non-reserved
|
||||||
|
PLANS non-reserved
|
||||||
|
PLI non-reserved non-reserved non-reserved
|
||||||
|
POLICY non-reserved
|
||||||
|
PORTION reserved reserved
|
||||||
|
POSITION non-reserved (cannot be function or type) reserved reserved reserved
|
||||||
|
POSITION_REGEX reserved reserved
|
||||||
|
POWER reserved reserved
|
||||||
|
PRECEDES reserved reserved
|
||||||
|
PRECEDING non-reserved non-reserved non-reserved
|
||||||
|
PRECISION non-reserved (cannot be function or type), requires AS reserved reserved reserved
|
||||||
|
PREPARE non-reserved reserved reserved reserved
|
||||||
|
PREPARED non-reserved
|
||||||
|
PRESERVE non-reserved non-reserved non-reserved reserved
|
||||||
|
PREV non-reserved non-reserved
|
||||||
|
PRIMARY reserved reserved reserved reserved
|
||||||
|
PRIOR non-reserved non-reserved non-reserved reserved
|
||||||
|
PRIVATE non-reserved non-reserved
|
||||||
|
PRIVILEGES non-reserved non-reserved non-reserved reserved
|
||||||
|
PROCEDURAL non-reserved
|
||||||
|
PROCEDURE non-reserved reserved reserved reserved
|
||||||
|
PROCEDURES non-reserved
|
||||||
|
PROGRAM non-reserved
|
||||||
|
PRUNE non-reserved non-reserved
|
||||||
|
PTF reserved reserved
|
||||||
|
PUBLIC non-reserved non-reserved reserved
|
||||||
|
PUBLICATION non-reserved
|
||||||
|
QUOTE non-reserved
|
||||||
|
QUOTES non-reserved non-reserved
|
||||||
|
RANGE non-reserved reserved reserved
|
||||||
|
RANK reserved reserved
|
||||||
|
READ non-reserved non-reserved non-reserved reserved
|
||||||
|
READS reserved reserved
|
||||||
|
REAL non-reserved (cannot be function or type) reserved reserved reserved
|
||||||
|
REASSIGN non-reserved
|
||||||
|
RECHECK non-reserved
|
||||||
|
RECOVERY non-reserved non-reserved
|
||||||
|
RECURSIVE non-reserved reserved reserved
|
||||||
|
REF non-reserved reserved reserved
|
||||||
|
REFERENCES reserved reserved reserved reserved
|
||||||
|
REFERENCING non-reserved reserved reserved
|
||||||
|
REFRESH non-reserved
|
||||||
|
REGR_AVGX reserved reserved
|
||||||
|
REGR_AVGY reserved reserved
|
||||||
|
REGR_COUNT reserved reserved
|
||||||
|
REGR_INTERCEPT reserved reserved
|
||||||
|
REGR_R2 reserved reserved
|
||||||
|
REGR_SLOPE reserved reserved
|
||||||
|
REGR_SXX reserved reserved
|
||||||
|
REGR_SXY reserved reserved
|
||||||
|
REGR_SYY reserved reserved
|
||||||
|
REINDEX non-reserved
|
||||||
|
RELATIVE non-reserved non-reserved non-reserved reserved
|
||||||
|
RELEASE non-reserved reserved reserved
|
||||||
|
RENAME non-reserved
|
||||||
|
REPEATABLE non-reserved non-reserved non-reserved non-reserved
|
||||||
|
REPLACE non-reserved
|
||||||
|
REPLICA non-reserved
|
||||||
|
REQUIRING non-reserved non-reserved
|
||||||
|
RESET non-reserved
|
||||||
|
RESPECT non-reserved non-reserved
|
||||||
|
RESTART non-reserved non-reserved non-reserved
|
||||||
|
RESTORE non-reserved non-reserved
|
||||||
|
RESTRICT non-reserved non-reserved non-reserved reserved
|
||||||
|
RESULT reserved reserved
|
||||||
|
RETURN non-reserved reserved reserved
|
||||||
|
RETURNED_CARDINALITY non-reserved non-reserved
|
||||||
|
RETURNED_LENGTH non-reserved non-reserved non-reserved
|
||||||
|
RETURNED_OCTET_LENGTH non-reserved non-reserved non-reserved
|
||||||
|
RETURNED_SQLSTATE non-reserved non-reserved non-reserved
|
||||||
|
RETURNING reserved, requires AS non-reserved non-reserved
|
||||||
|
RETURNS non-reserved reserved reserved
|
||||||
|
REVOKE non-reserved reserved reserved reserved
|
||||||
|
RIGHT reserved (can be function or type) reserved reserved reserved
|
||||||
|
ROLE non-reserved non-reserved non-reserved
|
||||||
|
ROLLBACK non-reserved reserved reserved reserved
|
||||||
|
ROLLUP non-reserved reserved reserved
|
||||||
|
ROUTINE non-reserved non-reserved non-reserved
|
||||||
|
ROUTINES non-reserved
|
||||||
|
ROUTINE_CATALOG non-reserved non-reserved
|
||||||
|
ROUTINE_NAME non-reserved non-reserved
|
||||||
|
ROUTINE_SCHEMA non-reserved non-reserved
|
||||||
|
ROW non-reserved (cannot be function or type) reserved reserved
|
||||||
|
ROWS non-reserved reserved reserved reserved
|
||||||
|
ROW_COUNT non-reserved non-reserved non-reserved
|
||||||
|
ROW_NUMBER reserved reserved
|
||||||
|
RPAD reserved
|
||||||
|
RTRIM reserved
|
||||||
|
RULE non-reserved
|
||||||
|
RUNNING reserved reserved
|
||||||
|
SAVEPOINT non-reserved reserved reserved
|
||||||
|
SCALAR non-reserved non-reserved non-reserved
|
||||||
|
SCALE non-reserved non-reserved non-reserved
|
||||||
|
SCHEMA non-reserved non-reserved non-reserved reserved
|
||||||
|
SCHEMAS non-reserved
|
||||||
|
SCHEMA_NAME non-reserved non-reserved non-reserved
|
||||||
|
SCOPE reserved reserved
|
||||||
|
SCOPE_CATALOG non-reserved non-reserved
|
||||||
|
SCOPE_NAME non-reserved non-reserved
|
||||||
|
SCOPE_SCHEMA non-reserved non-reserved
|
||||||
|
SCROLL non-reserved reserved reserved reserved
|
||||||
|
SEARCH non-reserved reserved reserved
|
||||||
|
SECOND non-reserved, requires AS reserved reserved reserved
|
||||||
|
SECTION non-reserved non-reserved reserved
|
||||||
|
SECURITY non-reserved non-reserved non-reserved
|
||||||
|
SEEK reserved reserved
|
||||||
|
SELECT reserved reserved reserved reserved
|
||||||
|
SELECTIVE non-reserved non-reserved
|
||||||
|
SELF non-reserved non-reserved
|
||||||
|
SEMANTICS non-reserved non-reserved
|
||||||
|
SENSITIVE reserved reserved
|
||||||
|
SEQUENCE non-reserved non-reserved non-reserved
|
||||||
|
SEQUENCES non-reserved
|
||||||
|
SERIALIZABLE non-reserved non-reserved non-reserved non-reserved
|
||||||
|
SERVER non-reserved non-reserved non-reserved
|
||||||
|
SERVER_NAME non-reserved non-reserved non-reserved
|
||||||
|
SESSION non-reserved non-reserved non-reserved reserved
|
||||||
|
SESSION_USER reserved reserved reserved reserved
|
||||||
|
SET non-reserved reserved reserved reserved
|
||||||
|
SETOF non-reserved (cannot be function or type)
|
||||||
|
SETS non-reserved non-reserved non-reserved
|
||||||
|
SHARE non-reserved
|
||||||
|
SHOW non-reserved reserved reserved
|
||||||
|
SIMILAR reserved (can be function or type) reserved reserved
|
||||||
|
SIMPLE non-reserved non-reserved non-reserved
|
||||||
|
SIN reserved reserved
|
||||||
|
SINH reserved reserved
|
||||||
|
SIZE non-reserved non-reserved reserved
|
||||||
|
SKIP non-reserved reserved reserved
|
||||||
|
SMALLINT non-reserved (cannot be function or type) reserved reserved reserved
|
||||||
|
SNAPSHOT non-reserved
|
||||||
|
SOME reserved reserved reserved reserved
|
||||||
|
SORT_DIRECTION non-reserved non-reserved
|
||||||
|
SOURCE non-reserved non-reserved
|
||||||
|
SPACE non-reserved non-reserved reserved
|
||||||
|
SPECIFIC reserved reserved
|
||||||
|
SPECIFICTYPE reserved reserved
|
||||||
|
SPECIFIC_NAME non-reserved non-reserved
|
||||||
|
SQL non-reserved reserved reserved reserved
|
||||||
|
SQLCODE reserved
|
||||||
|
SQLERROR reserved
|
||||||
|
SQLEXCEPTION reserved reserved
|
||||||
|
SQLSTATE reserved reserved reserved
|
||||||
|
SQLWARNING reserved reserved
|
||||||
|
SQRT reserved reserved
|
||||||
|
STABLE non-reserved
|
||||||
|
STANDALONE non-reserved non-reserved non-reserved
|
||||||
|
START non-reserved reserved reserved
|
||||||
|
STATE non-reserved non-reserved
|
||||||
|
STATEMENT non-reserved non-reserved non-reserved
|
||||||
|
STATIC reserved reserved
|
||||||
|
STATISTICS non-reserved
|
||||||
|
STDDEV_POP reserved reserved
|
||||||
|
STDDEV_SAMP reserved reserved
|
||||||
|
STDIN non-reserved
|
||||||
|
STDOUT non-reserved
|
||||||
|
STORAGE non-reserved
|
||||||
|
STORED non-reserved
|
||||||
|
STRICT non-reserved
|
||||||
|
STRING non-reserved non-reserved
|
||||||
|
STRIP non-reserved non-reserved non-reserved
|
||||||
|
STRUCTURE non-reserved non-reserved
|
||||||
|
STYLE non-reserved non-reserved
|
||||||
|
SUBCLASS_ORIGIN non-reserved non-reserved non-reserved
|
||||||
|
SUBMULTISET reserved reserved
|
||||||
|
SUBSCRIPTION non-reserved
|
||||||
|
SUBSET reserved reserved
|
||||||
|
SUBSTRING non-reserved (cannot be function or type) reserved reserved reserved
|
||||||
|
SUBSTRING_REGEX reserved reserved
|
||||||
|
SUCCEEDS reserved reserved
|
||||||
|
SUM reserved reserved reserved
|
||||||
|
SUPPORT non-reserved
|
||||||
|
SYMMETRIC reserved reserved reserved
|
||||||
|
SYSID non-reserved
|
||||||
|
SYSTEM non-reserved reserved reserved
|
||||||
|
SYSTEM_TIME reserved reserved
|
||||||
|
SYSTEM_USER reserved reserved reserved reserved
|
||||||
|
T non-reserved non-reserved
|
||||||
|
TABLE reserved reserved reserved reserved
|
||||||
|
TABLES non-reserved
|
||||||
|
TABLESAMPLE reserved (can be function or type) reserved reserved
|
||||||
|
TABLESPACE non-reserved
|
||||||
|
TABLE_NAME non-reserved non-reserved non-reserved
|
||||||
|
TAN reserved reserved
|
||||||
|
TANH reserved reserved
|
||||||
|
TEMP non-reserved
|
||||||
|
TEMPLATE non-reserved
|
||||||
|
TEMPORARY non-reserved non-reserved non-reserved reserved
|
||||||
|
TEXT non-reserved
|
||||||
|
THEN reserved reserved reserved reserved
|
||||||
|
THROUGH non-reserved non-reserved
|
||||||
|
TIES non-reserved non-reserved non-reserved
|
||||||
|
TIME non-reserved (cannot be function or type) reserved reserved reserved
|
||||||
|
TIMESTAMP non-reserved (cannot be function or type) reserved reserved reserved
|
||||||
|
TIMEZONE_HOUR reserved reserved reserved
|
||||||
|
TIMEZONE_MINUTE reserved reserved reserved
|
||||||
|
TO reserved, requires AS reserved reserved reserved
|
||||||
|
TOKEN non-reserved non-reserved
|
||||||
|
TOP_LEVEL_COUNT non-reserved non-reserved
|
||||||
|
TRAILING reserved reserved reserved reserved
|
||||||
|
TRANSACTION non-reserved non-reserved non-reserved reserved
|
||||||
|
TRANSACTIONS_COMMITTED non-reserved non-reserved
|
||||||
|
TRANSACTIONS_ROLLED_BACK non-reserved non-reserved
|
||||||
|
TRANSACTION_ACTIVE non-reserved non-reserved
|
||||||
|
TRANSFORM non-reserved non-reserved non-reserved
|
||||||
|
TRANSFORMS non-reserved non-reserved
|
||||||
|
TRANSLATE reserved reserved reserved
|
||||||
|
TRANSLATE_REGEX reserved reserved
|
||||||
|
TRANSLATION reserved reserved reserved
|
||||||
|
TREAT non-reserved (cannot be function or type) reserved reserved
|
||||||
|
TRIGGER non-reserved reserved reserved
|
||||||
|
TRIGGER_CATALOG non-reserved non-reserved
|
||||||
|
TRIGGER_NAME non-reserved non-reserved
|
||||||
|
TRIGGER_SCHEMA non-reserved non-reserved
|
||||||
|
TRIM non-reserved (cannot be function or type) reserved reserved reserved
|
||||||
|
TRIM_ARRAY reserved reserved
|
||||||
|
TRUE reserved reserved reserved reserved
|
||||||
|
TRUNCATE non-reserved reserved reserved
|
||||||
|
TRUSTED non-reserved
|
||||||
|
TYPE non-reserved non-reserved non-reserved non-reserved
|
||||||
|
TYPES non-reserved
|
||||||
|
UESCAPE non-reserved reserved reserved
|
||||||
|
UNBOUNDED non-reserved non-reserved non-reserved
|
||||||
|
UNCOMMITTED non-reserved non-reserved non-reserved non-reserved
|
||||||
|
UNCONDITIONAL non-reserved non-reserved
|
||||||
|
UNDER non-reserved non-reserved
|
||||||
|
UNENCRYPTED non-reserved
|
||||||
|
UNION reserved, requires AS reserved reserved reserved
|
||||||
|
UNIQUE reserved reserved reserved reserved
|
||||||
|
UNKNOWN non-reserved reserved reserved reserved
|
||||||
|
UNLINK non-reserved non-reserved
|
||||||
|
UNLISTEN non-reserved
|
||||||
|
UNLOGGED non-reserved
|
||||||
|
UNMATCHED non-reserved non-reserved
|
||||||
|
UNNAMED non-reserved non-reserved non-reserved
|
||||||
|
UNNEST reserved reserved
|
||||||
|
UNTIL non-reserved
|
||||||
|
UNTYPED non-reserved non-reserved
|
||||||
|
UPDATE non-reserved reserved reserved reserved
|
||||||
|
UPPER reserved reserved reserved
|
||||||
|
URI non-reserved non-reserved
|
||||||
|
USAGE non-reserved non-reserved reserved
|
||||||
|
USER reserved reserved reserved reserved
|
||||||
|
USER_DEFINED_TYPE_CATALOG non-reserved non-reserved
|
||||||
|
USER_DEFINED_TYPE_CODE non-reserved non-reserved
|
||||||
|
USER_DEFINED_TYPE_NAME non-reserved non-reserved
|
||||||
|
USER_DEFINED_TYPE_SCHEMA non-reserved non-reserved
|
||||||
|
USING reserved reserved reserved reserved
|
||||||
|
UTF16 non-reserved non-reserved
|
||||||
|
UTF32 non-reserved non-reserved
|
||||||
|
UTF8 non-reserved non-reserved
|
||||||
|
VACUUM non-reserved
|
||||||
|
VALID non-reserved non-reserved non-reserved
|
||||||
|
VALIDATE non-reserved
|
||||||
|
VALIDATOR non-reserved
|
||||||
|
VALUE non-reserved reserved reserved reserved
|
||||||
|
VALUES non-reserved (cannot be function or type) reserved reserved reserved
|
||||||
|
VALUE_OF reserved reserved
|
||||||
|
VARBINARY reserved reserved
|
||||||
|
VARCHAR non-reserved (cannot be function or type) reserved reserved reserved
|
||||||
|
VARIADIC reserved
|
||||||
|
VARYING non-reserved, requires AS reserved reserved reserved
|
||||||
|
VAR_POP reserved reserved
|
||||||
|
VAR_SAMP reserved reserved
|
||||||
|
VERBOSE reserved (can be function or type)
|
||||||
|
VERSION non-reserved non-reserved non-reserved
|
||||||
|
VERSIONING reserved reserved
|
||||||
|
VIEW non-reserved non-reserved non-reserved reserved
|
||||||
|
VIEWS non-reserved
|
||||||
|
VOLATILE non-reserved
|
||||||
|
WHEN reserved reserved reserved reserved
|
||||||
|
WHENEVER reserved reserved reserved
|
||||||
|
WHERE reserved, requires AS reserved reserved reserved
|
||||||
|
WHITESPACE non-reserved non-reserved non-reserved
|
||||||
|
WIDTH_BUCKET reserved reserved
|
||||||
|
WINDOW reserved, requires AS reserved reserved
|
||||||
|
WITH reserved, requires AS reserved reserved reserved
|
||||||
|
WITHIN non-reserved, requires AS reserved reserved
|
||||||
|
WITHOUT non-reserved, requires AS reserved reserved
|
||||||
|
WORK non-reserved non-reserved non-reserved reserved
|
||||||
|
WRAPPER non-reserved non-reserved non-reserved
|
||||||
|
WRITE non-reserved non-reserved non-reserved reserved
|
||||||
|
XML non-reserved reserved reserved
|
||||||
|
XMLAGG reserved reserved
|
||||||
|
XMLATTRIBUTES non-reserved (cannot be function or type) reserved reserved
|
||||||
|
XMLBINARY reserved reserved
|
||||||
|
XMLCAST reserved reserved
|
||||||
|
XMLCOMMENT reserved reserved
|
||||||
|
XMLCONCAT non-reserved (cannot be function or type) reserved reserved
|
||||||
|
XMLDECLARATION non-reserved non-reserved
|
||||||
|
XMLDOCUMENT reserved reserved
|
||||||
|
XMLELEMENT non-reserved (cannot be function or type) reserved reserved
|
||||||
|
XMLEXISTS non-reserved (cannot be function or type) reserved reserved
|
||||||
|
XMLFOREST non-reserved (cannot be function or type) reserved reserved
|
||||||
|
XMLITERATE reserved reserved
|
||||||
|
XMLNAMESPACES non-reserved (cannot be function or type) reserved reserved
|
||||||
|
XMLPARSE non-reserved (cannot be function or type) reserved reserved
|
||||||
|
XMLPI non-reserved (cannot be function or type) reserved reserved
|
||||||
|
XMLQUERY reserved reserved
|
||||||
|
XMLROOT non-reserved (cannot be function or type)
|
||||||
|
XMLSCHEMA non-reserved non-reserved
|
||||||
|
XMLSERIALIZE non-reserved (cannot be function or type) reserved reserved
|
||||||
|
XMLTABLE non-reserved (cannot be function or type) reserved reserved
|
||||||
|
XMLTEXT reserved reserved
|
||||||
|
XMLVALIDATE reserved reserved
|
||||||
|
YEAR non-reserved, requires AS reserved reserved reserved
|
||||||
|
YES non-reserved non-reserved non-reserved
|
||||||
|
ZONE non-reserved non-reserved non-reserved reserved
|
|
106
src/harlequin_postgres/loaders.py
Normal file
106
src/harlequin_postgres/loaders.py
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import date, datetime
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
import psycopg
|
||||||
|
from psycopg.errors import DataError
|
||||||
|
|
||||||
|
# Subclass existing adapters so that the base case is handled normally.
|
||||||
|
from psycopg.types.datetime import (
|
||||||
|
DateBinaryLoader,
|
||||||
|
DateLoader,
|
||||||
|
TimestampBinaryLoader,
|
||||||
|
TimestampLoader,
|
||||||
|
TimestamptzBinaryLoader,
|
||||||
|
TimestamptzLoader,
|
||||||
|
)
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from psycopg.adapt import Buffer, Loader
|
||||||
|
|
||||||
|
|
||||||
|
class InfDateLoader(DateLoader):
|
||||||
|
def load(self, data: "Buffer") -> date:
|
||||||
|
if data == b"infinity":
|
||||||
|
return date.max
|
||||||
|
elif data == b"-infinity":
|
||||||
|
return date.min
|
||||||
|
else:
|
||||||
|
return super().load(data)
|
||||||
|
|
||||||
|
|
||||||
|
class InfDateBinaryLoader(DateBinaryLoader):
|
||||||
|
def load(self, data: "Buffer") -> date:
|
||||||
|
try:
|
||||||
|
return super().load(data)
|
||||||
|
except DataError as e:
|
||||||
|
if "date too small" in str(e):
|
||||||
|
return date.min
|
||||||
|
elif "date too large" in str(e):
|
||||||
|
return date.max
|
||||||
|
raise e
|
||||||
|
|
||||||
|
|
||||||
|
class InfTimestampLoader(TimestampLoader):
|
||||||
|
def load(self, data: "Buffer") -> datetime:
|
||||||
|
if data == b"infinity":
|
||||||
|
return datetime.max
|
||||||
|
elif data == b"-infinity":
|
||||||
|
return datetime.min
|
||||||
|
else:
|
||||||
|
return super().load(data)
|
||||||
|
|
||||||
|
|
||||||
|
class InfTimestampBinaryLoader(TimestampBinaryLoader):
|
||||||
|
def load(self, data: "Buffer") -> datetime:
|
||||||
|
try:
|
||||||
|
return super().load(data)
|
||||||
|
except DataError as e:
|
||||||
|
if "timestamp too small" in str(e):
|
||||||
|
return datetime.min
|
||||||
|
elif "timestamp too large" in str(e):
|
||||||
|
return datetime.max
|
||||||
|
raise e
|
||||||
|
|
||||||
|
|
||||||
|
class InfTimestamptzLoader(TimestamptzLoader):
|
||||||
|
def load(self, data: "Buffer") -> datetime:
|
||||||
|
if data == b"infinity":
|
||||||
|
return datetime.max
|
||||||
|
elif data == b"-infinity":
|
||||||
|
return datetime.min
|
||||||
|
else:
|
||||||
|
return super().load(data)
|
||||||
|
|
||||||
|
|
||||||
|
class InfTimestamptzBinaryLoader(TimestamptzBinaryLoader):
|
||||||
|
def load(self, data: "Buffer") -> datetime:
|
||||||
|
try:
|
||||||
|
return super().load(data)
|
||||||
|
except DataError as e:
|
||||||
|
if "timestamp too small" in str(e):
|
||||||
|
return datetime.min
|
||||||
|
elif "timestamp too large" in str(e):
|
||||||
|
return datetime.max
|
||||||
|
raise e
|
||||||
|
|
||||||
|
|
||||||
|
INF_LOADERS: list[tuple[str, type["Loader"]]] = [
|
||||||
|
("date", InfDateLoader),
|
||||||
|
("date", InfDateBinaryLoader),
|
||||||
|
("timestamp", InfTimestampLoader),
|
||||||
|
("timestamp", InfTimestampBinaryLoader),
|
||||||
|
("timestamptz", InfTimestamptzLoader),
|
||||||
|
("timestamptz", InfTimestamptzBinaryLoader),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def register_inf_loaders() -> None:
|
||||||
|
"""
|
||||||
|
Register updated date/datetime loaders in the global types
|
||||||
|
registry, so that any connections created afterwards will use
|
||||||
|
the updated loaders (that allow infinity date/timestamps)
|
||||||
|
"""
|
||||||
|
for type_name, loader in INF_LOADERS:
|
||||||
|
psycopg.adapters.register_loader(type_name, loader)
|
0
src/harlequin_postgres/py.typed
Normal file
0
src/harlequin_postgres/py.typed
Normal file
40
tests/conftest.py
Normal file
40
tests/conftest.py
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from typing import Generator
|
||||||
|
|
||||||
|
import psycopg
|
||||||
|
import pytest
|
||||||
|
from harlequin_postgres.adapter import (
|
||||||
|
HarlequinPostgresAdapter,
|
||||||
|
HarlequinPostgresConnection,
|
||||||
|
)
|
||||||
|
|
||||||
|
if sys.version_info < (3, 10):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
|
||||||
|
TEST_DB_CONN = "postgresql://postgres:for-testing@localhost:5432"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def connection() -> Generator[HarlequinPostgresConnection, None, None]:
|
||||||
|
pgconn = psycopg.connect(conninfo=TEST_DB_CONN, dbname="postgres")
|
||||||
|
pgconn.autocommit = True
|
||||||
|
cur = pgconn.cursor()
|
||||||
|
cur.execute("drop database if exists test;")
|
||||||
|
cur.execute("create database test;")
|
||||||
|
cur.close()
|
||||||
|
pgconn.close()
|
||||||
|
conn = HarlequinPostgresAdapter(
|
||||||
|
conn_str=(f"{TEST_DB_CONN}",), dbname="test"
|
||||||
|
).connect()
|
||||||
|
yield conn
|
||||||
|
conn.close()
|
||||||
|
pgconn = psycopg.connect(conninfo=TEST_DB_CONN, dbname="postgres")
|
||||||
|
pgconn.autocommit = True
|
||||||
|
cur = pgconn.cursor()
|
||||||
|
cur.execute("drop database if exists test;")
|
||||||
|
cur.close()
|
||||||
|
pgconn.close()
|
158
tests/test_adapter.py
Normal file
158
tests/test_adapter.py
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from datetime import date, datetime
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from harlequin.adapter import HarlequinAdapter, HarlequinConnection, HarlequinCursor
|
||||||
|
from harlequin.catalog import Catalog, CatalogItem
|
||||||
|
from harlequin.exception import HarlequinConnectionError, HarlequinQueryError
|
||||||
|
from harlequin_postgres.adapter import (
|
||||||
|
HarlequinPostgresAdapter,
|
||||||
|
HarlequinPostgresConnection,
|
||||||
|
)
|
||||||
|
from textual_fastdatatable.backend import create_backend
|
||||||
|
|
||||||
|
if sys.version_info < (3, 10):
|
||||||
|
from importlib_metadata import entry_points
|
||||||
|
else:
|
||||||
|
from importlib.metadata import entry_points
|
||||||
|
|
||||||
|
TEST_DB_CONN = "postgresql://postgres:for-testing@localhost:5432"
|
||||||
|
|
||||||
|
|
||||||
|
def test_plugin_discovery() -> None:
|
||||||
|
PLUGIN_NAME = "postgres"
|
||||||
|
eps = entry_points(group="harlequin.adapter")
|
||||||
|
assert eps[PLUGIN_NAME]
|
||||||
|
adapter_cls = eps[PLUGIN_NAME].load()
|
||||||
|
assert issubclass(adapter_cls, HarlequinAdapter)
|
||||||
|
assert adapter_cls == HarlequinPostgresAdapter
|
||||||
|
|
||||||
|
|
||||||
|
def test_connect() -> None:
|
||||||
|
conn = HarlequinPostgresAdapter(conn_str=(TEST_DB_CONN,)).connect()
|
||||||
|
assert isinstance(conn, HarlequinConnection)
|
||||||
|
|
||||||
|
|
||||||
|
def test_init_extra_kwargs() -> None:
|
||||||
|
assert HarlequinPostgresAdapter(
|
||||||
|
conn_str=(TEST_DB_CONN,), foo=1, bar="baz"
|
||||||
|
).connect()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"conn_str",
|
||||||
|
[
|
||||||
|
("foo",),
|
||||||
|
("host=foo",),
|
||||||
|
("postgresql://admin:pass@foo:5432/db",),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_connect_raises_connection_error(conn_str: tuple[str]) -> None:
|
||||||
|
with pytest.raises(HarlequinConnectionError):
|
||||||
|
_ = HarlequinPostgresAdapter(conn_str=conn_str, connect_timeout=0.1).connect()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"conn_str,options,expected",
|
||||||
|
[
|
||||||
|
(("",), {}, "localhost:5432/postgres"),
|
||||||
|
(("host=foo",), {}, "foo:5432/postgres"),
|
||||||
|
(("postgresql://foo",), {}, "foo:5432/postgres"),
|
||||||
|
(("postgresql://foo",), {"port": 5431}, "foo:5431/postgres"),
|
||||||
|
(("postgresql://foo/mydb",), {"port": 5431}, "foo:5431/mydb"),
|
||||||
|
(("postgresql://admin:pass@foo/mydb",), {"port": 5431}, "foo:5431/mydb"),
|
||||||
|
(("postgresql://admin:pass@foo:5431/mydb",), {}, "foo:5431/mydb"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_connection_id(
|
||||||
|
conn_str: tuple[str], options: dict[str, int | float | str | None], expected: str
|
||||||
|
) -> None:
|
||||||
|
adapter = HarlequinPostgresAdapter(
|
||||||
|
conn_str=conn_str,
|
||||||
|
**options, # type: ignore[arg-type]
|
||||||
|
)
|
||||||
|
assert adapter.connection_id == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_catalog(connection: HarlequinPostgresConnection) -> None:
|
||||||
|
catalog = connection.get_catalog()
|
||||||
|
assert isinstance(catalog, Catalog)
|
||||||
|
assert catalog.items
|
||||||
|
assert isinstance(catalog.items[0], CatalogItem)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_completions(connection: HarlequinPostgresConnection) -> None:
|
||||||
|
completions = connection.get_completions()
|
||||||
|
test_labels = ["atomic", "greatest", "point_right", "autovacuum"]
|
||||||
|
filtered = list(filter(lambda x: x.label in test_labels, completions))
|
||||||
|
assert len(filtered) == 4
|
||||||
|
value_filtered = list(filter(lambda x: x.value in test_labels, completions))
|
||||||
|
assert len(value_filtered) == 4
|
||||||
|
|
||||||
|
|
||||||
|
def test_execute_ddl(connection: HarlequinPostgresConnection) -> None:
|
||||||
|
cur = connection.execute("create table foo (a int)")
|
||||||
|
assert cur is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_execute_select(connection: HarlequinPostgresConnection) -> None:
|
||||||
|
cur = connection.execute("select 1 as a")
|
||||||
|
assert isinstance(cur, HarlequinCursor)
|
||||||
|
assert cur.columns() == [("a", "#")]
|
||||||
|
data = cur.fetchall()
|
||||||
|
backend = create_backend(data)
|
||||||
|
assert backend.column_count == 1
|
||||||
|
assert backend.row_count == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_execute_select_dupe_cols(connection: HarlequinPostgresConnection) -> None:
|
||||||
|
cur = connection.execute("select 1 as a, 2 as a, 3 as a")
|
||||||
|
assert isinstance(cur, HarlequinCursor)
|
||||||
|
assert len(cur.columns()) == 3
|
||||||
|
data = cur.fetchall()
|
||||||
|
backend = create_backend(data)
|
||||||
|
assert backend.column_count == 3
|
||||||
|
assert backend.row_count == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_set_limit(connection: HarlequinPostgresConnection) -> None:
|
||||||
|
cur = connection.execute("select 1 as a union all select 2 union all select 3")
|
||||||
|
assert isinstance(cur, HarlequinCursor)
|
||||||
|
cur = cur.set_limit(2)
|
||||||
|
assert isinstance(cur, HarlequinCursor)
|
||||||
|
data = cur.fetchall()
|
||||||
|
backend = create_backend(data)
|
||||||
|
assert backend.column_count == 1
|
||||||
|
assert backend.row_count == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_execute_raises_query_error(connection: HarlequinPostgresConnection) -> None:
|
||||||
|
with pytest.raises(HarlequinQueryError):
|
||||||
|
_ = connection.execute("sel;")
|
||||||
|
|
||||||
|
|
||||||
|
def test_inf_timestamps(connection: HarlequinPostgresConnection) -> None:
|
||||||
|
cur = connection.execute(
|
||||||
|
"""select
|
||||||
|
'infinity'::date,
|
||||||
|
'infinity'::timestamp,
|
||||||
|
'infinity'::timestamptz,
|
||||||
|
'-infinity'::date,
|
||||||
|
'-infinity'::timestamp,
|
||||||
|
'-infinity'::timestamptz
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
assert cur is not None
|
||||||
|
data = cur.fetchall()
|
||||||
|
assert data == [
|
||||||
|
(
|
||||||
|
date.max,
|
||||||
|
datetime.max,
|
||||||
|
datetime.max,
|
||||||
|
date.min,
|
||||||
|
datetime.min,
|
||||||
|
datetime.min,
|
||||||
|
)
|
||||||
|
]
|
92
tests/test_catalog.py
Normal file
92
tests/test_catalog.py
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
import pytest
|
||||||
|
from harlequin.catalog import InteractiveCatalogItem
|
||||||
|
from harlequin_postgres.adapter import HarlequinPostgresConnection
|
||||||
|
from harlequin_postgres.catalog import (
|
||||||
|
ColumnCatalogItem,
|
||||||
|
DatabaseCatalogItem,
|
||||||
|
RelationCatalogItem,
|
||||||
|
SchemaCatalogItem,
|
||||||
|
TableCatalogItem,
|
||||||
|
ViewCatalogItem,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def connection_with_objects(
|
||||||
|
connection: HarlequinPostgresConnection,
|
||||||
|
) -> HarlequinPostgresConnection:
|
||||||
|
connection.execute("create schema one")
|
||||||
|
connection.execute("create table one.foo as select 1 as a, '2' as b")
|
||||||
|
connection.execute("create table one.bar as select 1 as a, '2' as b")
|
||||||
|
connection.execute("create table one.baz as select 1 as a, '2' as b")
|
||||||
|
connection.execute("create schema two")
|
||||||
|
connection.execute("create view two.qux as select * from one.foo")
|
||||||
|
connection.execute("create schema three")
|
||||||
|
# the original connection fixture will clean this up.
|
||||||
|
return connection
|
||||||
|
|
||||||
|
|
||||||
|
def test_catalog(connection_with_objects: HarlequinPostgresConnection) -> None:
|
||||||
|
conn = connection_with_objects
|
||||||
|
|
||||||
|
catalog = conn.get_catalog()
|
||||||
|
|
||||||
|
# at least two databases, postgres and test
|
||||||
|
assert len(catalog.items) >= 2
|
||||||
|
|
||||||
|
[test_db_item] = filter(lambda item: item.label == "test", catalog.items)
|
||||||
|
assert isinstance(test_db_item, InteractiveCatalogItem)
|
||||||
|
assert isinstance(test_db_item, DatabaseCatalogItem)
|
||||||
|
assert not test_db_item.children
|
||||||
|
assert not test_db_item.loaded
|
||||||
|
|
||||||
|
schema_items = test_db_item.fetch_children()
|
||||||
|
assert all(isinstance(item, SchemaCatalogItem) for item in schema_items)
|
||||||
|
|
||||||
|
[schema_one_item] = filter(lambda item: item.label == "one", schema_items)
|
||||||
|
assert isinstance(schema_one_item, SchemaCatalogItem)
|
||||||
|
assert not schema_one_item.children
|
||||||
|
assert not schema_one_item.loaded
|
||||||
|
|
||||||
|
table_items = schema_one_item.fetch_children()
|
||||||
|
assert all(isinstance(item, RelationCatalogItem) for item in table_items)
|
||||||
|
|
||||||
|
[foo_item] = filter(lambda item: item.label == "foo", table_items)
|
||||||
|
assert isinstance(foo_item, TableCatalogItem)
|
||||||
|
assert not foo_item.children
|
||||||
|
assert not foo_item.loaded
|
||||||
|
|
||||||
|
foo_column_items = foo_item.fetch_children()
|
||||||
|
assert all(isinstance(item, ColumnCatalogItem) for item in foo_column_items)
|
||||||
|
|
||||||
|
[schema_two_item] = filter(lambda item: item.label == "two", schema_items)
|
||||||
|
assert isinstance(schema_two_item, SchemaCatalogItem)
|
||||||
|
assert not schema_two_item.children
|
||||||
|
assert not schema_two_item.loaded
|
||||||
|
|
||||||
|
view_items = schema_two_item.fetch_children()
|
||||||
|
assert all(isinstance(item, ViewCatalogItem) for item in view_items)
|
||||||
|
|
||||||
|
[qux_item] = filter(lambda item: item.label == "qux", view_items)
|
||||||
|
assert isinstance(qux_item, ViewCatalogItem)
|
||||||
|
assert not qux_item.children
|
||||||
|
assert not qux_item.loaded
|
||||||
|
|
||||||
|
qux_column_items = qux_item.fetch_children()
|
||||||
|
assert all(isinstance(item, ColumnCatalogItem) for item in qux_column_items)
|
||||||
|
|
||||||
|
assert [item.label for item in foo_column_items] == [
|
||||||
|
item.label for item in qux_column_items
|
||||||
|
]
|
||||||
|
|
||||||
|
# ensure calling fetch_children on cols doesn't raise
|
||||||
|
children_items = foo_column_items[0].fetch_children()
|
||||||
|
assert not children_items
|
||||||
|
|
||||||
|
[schema_three_item] = filter(lambda item: item.label == "three", schema_items)
|
||||||
|
assert isinstance(schema_two_item, SchemaCatalogItem)
|
||||||
|
assert not schema_two_item.children
|
||||||
|
assert not schema_two_item.loaded
|
||||||
|
|
||||||
|
three_children = schema_three_item.fetch_children()
|
||||||
|
assert not three_children
|
Loading…
Add table
Add a link
Reference in a new issue