Adding upstream version 1.1.0.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
a7c3cb7344
commit
7269eaf22a
23 changed files with 4597 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 the 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.PYPI_TOKEN }}
|
||||||
|
- name: Get this package's Version
|
||||||
|
id: package_version
|
||||||
|
run: echo "package_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.package_version.outputs.package_version }}
|
||||||
|
target_commitish: main
|
||||||
|
token: ${{ secrets.GH_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 the 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.
|
161
.gitignore
vendored
Normal file
161
.gitignore
vendored
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
Pipfile
|
||||||
|
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/
|
10
.harlequin.toml
Normal file
10
.harlequin.toml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
default_profile = "azure"
|
||||||
|
[profiles.azure]
|
||||||
|
adapter = "mysql"
|
||||||
|
theme = "harlequin"
|
||||||
|
limit = 10000
|
||||||
|
host = "harlequin-mysql-dev.mysql.database.azure.com"
|
||||||
|
port = "3306"
|
||||||
|
database = "dev"
|
||||||
|
user = "harlequin"
|
||||||
|
password = "roEj5cN9Jdyqbyqf"
|
79
CHANGELOG.md
Normal file
79
CHANGELOG.md
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
# harlequin-mysql CHANGELOG
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [1.1.0] - 2025-01-28
|
||||||
|
|
||||||
|
- Bumps the MySQL Connector Python version to >=9.1
|
||||||
|
- Bumps the required Harlequin version to >= 1.25.0
|
||||||
|
- Adds support for the `openid_token_file` connection option introduced with MySQL Connector 9.1
|
||||||
|
- 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, inserting columns at the cursor, 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.3.0] - 2024-08-20
|
||||||
|
|
||||||
|
- Implements `connection_id` for better persistence.
|
||||||
|
- Implements the `cancel()` protocol to cancel in-flight queries.
|
||||||
|
- Implements `close()`
|
||||||
|
- Fixes a bug where a race condition could cause a crash with an `AssertionError` ([#14](https://github.com/tconbeer/harlequin-mysql/issues/14) - thank you [@blasferna](https://github.com/blasferna)!).
|
||||||
|
|
||||||
|
## [0.2.0] - 2024-04-11
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- Adds a `pool-size` CLI option to set the size of the MySQL connection pool. Defaults to 5.
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- Updates the connection pool config to keep all connections in sync after running a `use database` command ([#11](https://github.com/tconbeer/harlequin-mysql/issues/11) - thank you [@mlopezgva](https://github.com/mlopezgva)!).
|
||||||
|
- Handles several issues caused by running too many concurrent queries and not fetching results.
|
||||||
|
|
||||||
|
## [0.1.3] - 2024-01-29
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- Fixes a typo in the help text for the `--user` option (thank you [@alexmalins](https://github.com/alexmalins)!).
|
||||||
|
|
||||||
|
## [0.1.2] - 2024-01-25
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- Sets the `pool_name` property on the MySQL connection to prevent auto-generated pool names from being too long ([#6](https://github.com/tconbeer/harlequin-mysql/issues/6) - thank you [sondeokhyeon](https://github.com/sondeokhyeon)!).
|
||||||
|
|
||||||
|
## [0.1.1] - 2024-01-09
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- Sorts relation names alphabetically and columns by ordinal position.
|
||||||
|
|
||||||
|
## [0.1.0] - 2023-12-14
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- Adds a basic MySQL adapter with most common connection options.
|
||||||
|
|
||||||
|
[Unreleased]: https://github.com/tconbeer/harlequin-mysql/compare/1.1.0...HEAD
|
||||||
|
|
||||||
|
[1.1.0]: https://github.com/tconbeer/harlequin-mysql/compare/1.0.0...1.1.0
|
||||||
|
|
||||||
|
[1.0.0]: https://github.com/tconbeer/harlequin-mysql/compare/0.3.0...1.0.0
|
||||||
|
|
||||||
|
[0.3.0]: https://github.com/tconbeer/harlequin-mysql/compare/0.2.0...0.3.0
|
||||||
|
|
||||||
|
[0.2.0]: https://github.com/tconbeer/harlequin-mysql/compare/0.1.3...0.2.0
|
||||||
|
|
||||||
|
[0.1.3]: https://github.com/tconbeer/harlequin-mysql/compare/0.1.2...0.1.3
|
||||||
|
|
||||||
|
[0.1.2]: https://github.com/tconbeer/harlequin-mysql/compare/0.1.1...0.1.2
|
||||||
|
|
||||||
|
[0.1.1]: https://github.com/tconbeer/harlequin-mysql/compare/0.1.0...0.1.1
|
||||||
|
|
||||||
|
[0.1.0]: https://github.com/tconbeer/harlequin-mysql/compare/f2caef7de11e68bb2b9798fb597c3fc05044b71e...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.
|
18
Makefile
Normal file
18
Makefile
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
.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 mysql -h localhost -U root --password example --database dev
|
67
README.md
Normal file
67
README.md
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
# harlequin-mysql
|
||||||
|
|
||||||
|
This repo provides the Harlequin adapter for MySQL.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
`harlequin-mysql` 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-mysql
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using poetry
|
||||||
|
|
||||||
|
```bash
|
||||||
|
poetry add harlequin-mysql
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using pipx
|
||||||
|
|
||||||
|
If you do not already have Harlequin installed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install harlequin-mysql
|
||||||
|
```
|
||||||
|
|
||||||
|
If you would like to add the Postgres adapter to an existing Harlequin installation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pipx inject harlequin harlequin-mysql
|
||||||
|
```
|
||||||
|
|
||||||
|
### As an Extra
|
||||||
|
Alternatively, you can install Harlequin with the `mysql` extra:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install harlequin[mysql]
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
poetry add harlequin[mysql]
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pipx install harlequin[mysql]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage and Configuration
|
||||||
|
|
||||||
|
You can open Harlequin with the MySQL adapter by selecting it with the `-a` option and passing connection parameters as CLI options:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
harlequin -a mysql -h localhost -p 3306 -U root --password example --database dev
|
||||||
|
```
|
||||||
|
|
||||||
|
The MySQL adapter does not accept a connection string or DSN.
|
||||||
|
|
||||||
|
Many more options are available; to see the full list, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
harlequin --help
|
||||||
|
```
|
||||||
|
|
||||||
|
For more information, see the [Harlequin Docs](https://harlequin.sh/docs/mysql/index).
|
9
docker-compose.yml
Normal file
9
docker-compose.yml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
services:
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: mysql
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
MYSQL_ROOT_PASSWORD: example
|
||||||
|
ports:
|
||||||
|
- 3306:3306
|
1483
poetry.lock
generated
Normal file
1483
poetry.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
67
pyproject.toml
Normal file
67
pyproject.toml
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
[tool.poetry]
|
||||||
|
name = "harlequin-mysql"
|
||||||
|
version = "1.1.0"
|
||||||
|
description = "A Harlequin adapter for MySQL."
|
||||||
|
authors = ["Ted Conbeer <tconbeer@users.noreply.github.com>"]
|
||||||
|
license = "MIT"
|
||||||
|
readme = "README.md"
|
||||||
|
packages = [
|
||||||
|
{ include = "harlequin_mysql", from = "src" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.poetry.plugins."harlequin.adapter"]
|
||||||
|
mysql = "harlequin_mysql:HarlequinMySQLAdapter"
|
||||||
|
|
||||||
|
[tool.poetry.dependencies]
|
||||||
|
python = ">=3.9,<3.14"
|
||||||
|
harlequin = ">=1.25.0,<3"
|
||||||
|
mysql-connector-python = "^9.1.0"
|
||||||
|
|
||||||
|
[tool.poetry.group.dev.dependencies]
|
||||||
|
ruff = "^0.6"
|
||||||
|
pytest = "^8"
|
||||||
|
mypy = "^1.11"
|
||||||
|
pre-commit = "^3.5.0"
|
||||||
|
importlib_metadata = { version = ">=4.6.0", python = "<3.10.0" }
|
||||||
|
|
||||||
|
[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_mysql/__init__.py
Normal file
3
src/harlequin_mysql/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from harlequin_mysql.adapter import HarlequinMySQLAdapter
|
||||||
|
|
||||||
|
__all__ = ["HarlequinMySQLAdapter"]
|
428
src/harlequin_mysql/adapter.py
Normal file
428
src/harlequin_mysql/adapter.py
Normal file
|
@ -0,0 +1,428 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
|
from contextlib import suppress
|
||||||
|
from typing import Any, Sequence
|
||||||
|
|
||||||
|
from harlequin import (
|
||||||
|
HarlequinAdapter,
|
||||||
|
HarlequinConnection,
|
||||||
|
HarlequinCursor,
|
||||||
|
)
|
||||||
|
from harlequin.autocomplete.completion import HarlequinCompletion
|
||||||
|
from harlequin.catalog import Catalog, CatalogItem
|
||||||
|
from harlequin.exception import (
|
||||||
|
HarlequinConfigError,
|
||||||
|
HarlequinConnectionError,
|
||||||
|
HarlequinQueryError,
|
||||||
|
)
|
||||||
|
from mysql.connector import FieldType
|
||||||
|
from mysql.connector.cursor import MySQLCursor
|
||||||
|
from mysql.connector.errors import InternalError, PoolError
|
||||||
|
from mysql.connector.pooling import (
|
||||||
|
MySQLConnectionPool,
|
||||||
|
PooledMySQLConnection,
|
||||||
|
)
|
||||||
|
from textual_fastdatatable.backend import AutoBackendType
|
||||||
|
|
||||||
|
from harlequin_mysql.catalog import DatabaseCatalogItem
|
||||||
|
from harlequin_mysql.cli_options import MYSQLADAPTER_OPTIONS
|
||||||
|
from harlequin_mysql.completions import load_completions
|
||||||
|
|
||||||
|
USE_DATABASE_PROG = re.compile(
|
||||||
|
r"\s*use\s+([^\\/?%*:|\"<>.]{1,64})", flags=re.IGNORECASE
|
||||||
|
)
|
||||||
|
QUERY_INTERRUPT_MSG = "1317 (70100): Query execution was interrupted"
|
||||||
|
|
||||||
|
|
||||||
|
class HarlequinMySQLCursor(HarlequinCursor):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
cur: MySQLCursor,
|
||||||
|
conn: PooledMySQLConnection,
|
||||||
|
harlequin_conn: HarlequinMySQLConnection,
|
||||||
|
*_: Any,
|
||||||
|
**__: Any,
|
||||||
|
) -> None:
|
||||||
|
self.cur = cur
|
||||||
|
|
||||||
|
# copy description in case the cursor is closed before columns() is called
|
||||||
|
assert cur.description is not None
|
||||||
|
self.description = cur.description.copy()
|
||||||
|
|
||||||
|
self.conn = conn
|
||||||
|
self.harlequin_conn = harlequin_conn
|
||||||
|
self.connection_id = conn._cnx.connection_id
|
||||||
|
self._limit: int | None = None
|
||||||
|
|
||||||
|
def columns(self) -> list[tuple[str, str]]:
|
||||||
|
return [(col[0], self._get_short_type(col[1])) for col in self.description]
|
||||||
|
|
||||||
|
def set_limit(self, limit: int) -> "HarlequinMySQLCursor":
|
||||||
|
self._limit = limit
|
||||||
|
return self
|
||||||
|
|
||||||
|
def fetchall(self) -> AutoBackendType:
|
||||||
|
try:
|
||||||
|
if self._limit is None:
|
||||||
|
results = self.cur.fetchall()
|
||||||
|
else:
|
||||||
|
results = self.cur.fetchmany(self._limit)
|
||||||
|
return results
|
||||||
|
except Exception as e:
|
||||||
|
if str(e) == QUERY_INTERRUPT_MSG:
|
||||||
|
return []
|
||||||
|
else:
|
||||||
|
raise HarlequinQueryError(
|
||||||
|
msg=str(e),
|
||||||
|
title="Harlequin encountered an error while executing your query.",
|
||||||
|
) from e
|
||||||
|
finally:
|
||||||
|
self.conn.consume_results()
|
||||||
|
self.cur.close()
|
||||||
|
self.conn.close()
|
||||||
|
if self.connection_id:
|
||||||
|
self.harlequin_conn._in_use_connections.discard(self.connection_id)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_short_type(type_id: int) -> str:
|
||||||
|
mapping = {
|
||||||
|
FieldType.BIT: "010",
|
||||||
|
FieldType.BLOB: "0b",
|
||||||
|
FieldType.DATE: "d",
|
||||||
|
FieldType.DATETIME: "dt",
|
||||||
|
FieldType.DECIMAL: "#.#",
|
||||||
|
FieldType.DOUBLE: "#.#",
|
||||||
|
FieldType.ENUM: "enum",
|
||||||
|
FieldType.FLOAT: "#.#",
|
||||||
|
FieldType.GEOMETRY: "▽□",
|
||||||
|
FieldType.INT24: "###",
|
||||||
|
FieldType.JSON: "{}",
|
||||||
|
FieldType.LONG: "##",
|
||||||
|
FieldType.LONGLONG: "##",
|
||||||
|
FieldType.LONG_BLOB: "00b",
|
||||||
|
FieldType.MEDIUM_BLOB: "00b",
|
||||||
|
FieldType.NEWDATE: "d",
|
||||||
|
FieldType.NEWDECIMAL: "#.#",
|
||||||
|
FieldType.NULL: "∅",
|
||||||
|
FieldType.SET: "set",
|
||||||
|
FieldType.SHORT: "#",
|
||||||
|
FieldType.STRING: "s",
|
||||||
|
FieldType.TIME: "t",
|
||||||
|
FieldType.TIMESTAMP: "#ts",
|
||||||
|
FieldType.TINY: "#",
|
||||||
|
FieldType.TINY_BLOB: "b",
|
||||||
|
FieldType.VARCHAR: "s",
|
||||||
|
FieldType.VAR_STRING: "s",
|
||||||
|
FieldType.YEAR: "y",
|
||||||
|
}
|
||||||
|
return mapping.get(type_id, "?")
|
||||||
|
|
||||||
|
|
||||||
|
class HarlequinMySQLConnection(HarlequinConnection):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
conn_str: Sequence[str],
|
||||||
|
*_: Any,
|
||||||
|
init_message: str = "",
|
||||||
|
options: dict[str, Any],
|
||||||
|
) -> None:
|
||||||
|
self.init_message = init_message
|
||||||
|
self._in_use_connections: set[int] = set()
|
||||||
|
try:
|
||||||
|
self._pool: MySQLConnectionPool = MySQLConnectionPool(
|
||||||
|
pool_name="harlequin",
|
||||||
|
pool_reset_session=False,
|
||||||
|
autocommit=True,
|
||||||
|
**options,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
raise HarlequinConnectionError(
|
||||||
|
msg=str(e), title="Harlequin could not connect to your database."
|
||||||
|
) from e
|
||||||
|
|
||||||
|
def safe_get_mysql_cursor(
|
||||||
|
self, buffered: bool = False
|
||||||
|
) -> tuple[PooledMySQLConnection | None, MySQLCursor | None]:
|
||||||
|
"""
|
||||||
|
Return None if the connection pool is exhausted, to avoid getting
|
||||||
|
in an unrecoverable state.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
conn = self._pool.get_connection()
|
||||||
|
except (InternalError, PoolError):
|
||||||
|
# if we're out of connections, we can't raise a query error,
|
||||||
|
# or we get in a state where we have cursors without fetched
|
||||||
|
# results, which requires a restart of Harlequin. Instead,
|
||||||
|
# just return None and silently fail (there isn't a sensible
|
||||||
|
# way to show an error to the user without aborting processing
|
||||||
|
# all the other cursors).
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
try:
|
||||||
|
cur: MySQLCursor = conn.cursor(buffered=buffered)
|
||||||
|
except InternalError:
|
||||||
|
# cursor has an unread result. Try to consume the results,
|
||||||
|
# and try again.
|
||||||
|
conn.consume_results()
|
||||||
|
cur = conn.cursor(buffered=buffered)
|
||||||
|
|
||||||
|
return conn, cur
|
||||||
|
|
||||||
|
def set_pool_config(self, **config: Any) -> None:
|
||||||
|
"""
|
||||||
|
Updates the config of the MySQL connection pool.
|
||||||
|
"""
|
||||||
|
self._pool.set_config(**config)
|
||||||
|
|
||||||
|
def execute(self, query: str) -> HarlequinCursor | None:
|
||||||
|
retval: HarlequinCursor | None = None
|
||||||
|
|
||||||
|
conn, cur = self.safe_get_mysql_cursor()
|
||||||
|
if conn is None or cur is None:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
connection_id = conn._cnx.connection_id
|
||||||
|
if connection_id:
|
||||||
|
self._in_use_connections.add(connection_id)
|
||||||
|
|
||||||
|
try:
|
||||||
|
cur.execute(query)
|
||||||
|
except Exception as e:
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
if connection_id:
|
||||||
|
self._in_use_connections.discard(connection_id)
|
||||||
|
if str(e) == QUERY_INTERRUPT_MSG:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
raise HarlequinQueryError(
|
||||||
|
msg=str(e),
|
||||||
|
title="Harlequin encountered an error while executing your query.",
|
||||||
|
) from e
|
||||||
|
else:
|
||||||
|
if cur.description is not None:
|
||||||
|
retval = HarlequinMySQLCursor(cur, conn=conn, harlequin_conn=self)
|
||||||
|
else:
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
if connection_id:
|
||||||
|
self._in_use_connections.discard(connection_id)
|
||||||
|
|
||||||
|
# this is a hack to update all connections in the pool if the user
|
||||||
|
# changes the database for the active connection.
|
||||||
|
# it is impossible to check the database or other config
|
||||||
|
# of a connection with an open cursor, and we can't use a dedicated
|
||||||
|
# connection for user queries, since mysql only supports a single
|
||||||
|
# (unfetched) cursor per connection.
|
||||||
|
if match := USE_DATABASE_PROG.match(query):
|
||||||
|
new_db = match.group(1)
|
||||||
|
self.set_pool_config(database=new_db)
|
||||||
|
return retval
|
||||||
|
|
||||||
|
def cancel(self) -> None:
|
||||||
|
# get a new cursor to execute the KILL statements
|
||||||
|
conn, cur = self.safe_get_mysql_cursor()
|
||||||
|
if conn is None or cur is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# loop through in-use connections and kill each of them
|
||||||
|
for connection_id in self._in_use_connections:
|
||||||
|
try:
|
||||||
|
cur.execute("KILL QUERY %s", (connection_id,))
|
||||||
|
except BaseException:
|
||||||
|
continue
|
||||||
|
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
self._in_use_connections = set()
|
||||||
|
|
||||||
|
def close(self) -> None:
|
||||||
|
with suppress(PoolError):
|
||||||
|
self._pool._remove_connections()
|
||||||
|
|
||||||
|
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]:
|
||||||
|
return load_completions()
|
||||||
|
|
||||||
|
def _get_databases(self) -> list[tuple[str]]:
|
||||||
|
conn, cur = self.safe_get_mysql_cursor(buffered=True)
|
||||||
|
if conn is None or cur is None:
|
||||||
|
raise HarlequinConnectionError(
|
||||||
|
title="Connection pool exhausted",
|
||||||
|
msg=(
|
||||||
|
"Connection pool exhausted. Try restarting Harlequin "
|
||||||
|
"with a larger pool or running fewer queries at once."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
show databases
|
||||||
|
where `Database` not in (
|
||||||
|
'sys', 'information_schema', 'performance_schema', 'mysql'
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
results: list[tuple[str]] = cur.fetchall() # type: ignore
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
return results
|
||||||
|
|
||||||
|
def _get_relations(self, db_name: str) -> list[tuple[str, str]]:
|
||||||
|
conn, cur = self.safe_get_mysql_cursor(buffered=True)
|
||||||
|
if conn is None or cur is None:
|
||||||
|
raise HarlequinConnectionError(
|
||||||
|
title="Connection pool exhausted",
|
||||||
|
msg=(
|
||||||
|
"Connection pool exhausted. Try restarting Harlequin "
|
||||||
|
"with a larger pool or running fewer queries at once."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
cur.execute(
|
||||||
|
f"""
|
||||||
|
select
|
||||||
|
table_name,
|
||||||
|
table_type
|
||||||
|
from information_schema.tables
|
||||||
|
where table_schema = '{db_name}'
|
||||||
|
and table_type != 'SYSTEM VIEW'
|
||||||
|
order by table_name asc
|
||||||
|
;"""
|
||||||
|
)
|
||||||
|
results: list[tuple[str, str]] = cur.fetchall() # type: ignore
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
return results
|
||||||
|
|
||||||
|
def _get_columns(self, db_name: str, rel_name: str) -> list[tuple[str, str]]:
|
||||||
|
conn, cur = self.safe_get_mysql_cursor(buffered=True)
|
||||||
|
if conn is None or cur is None:
|
||||||
|
raise HarlequinConnectionError(
|
||||||
|
title="Connection pool exhausted",
|
||||||
|
msg=(
|
||||||
|
"Connection pool exhausted. Try restarting Harlequin "
|
||||||
|
"with a larger pool or running fewer queries at once."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
cur.execute(
|
||||||
|
f"""
|
||||||
|
select column_name, data_type
|
||||||
|
from information_schema.columns
|
||||||
|
where
|
||||||
|
table_schema = '{db_name}'
|
||||||
|
and table_name = '{rel_name}'
|
||||||
|
and extra not like '%INVISIBLE%'
|
||||||
|
order by ordinal_position asc
|
||||||
|
;"""
|
||||||
|
)
|
||||||
|
results: list[tuple[str, str]] = cur.fetchall() # type: ignore
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
return results
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _short_column_type(info_schema_type: str) -> str:
|
||||||
|
mapping = {
|
||||||
|
"bigint": "###",
|
||||||
|
"binary": "010",
|
||||||
|
"blob": "0b",
|
||||||
|
"char": "c",
|
||||||
|
"datetime": "dt",
|
||||||
|
"decimal": "#.#",
|
||||||
|
"double": "#.#",
|
||||||
|
"enum": "enum",
|
||||||
|
"float": "#.#",
|
||||||
|
"int": "##",
|
||||||
|
"json": "{}",
|
||||||
|
"longblob": "00b",
|
||||||
|
"longtext": "ss",
|
||||||
|
"mediumblob": "00b",
|
||||||
|
"mediumint": "##",
|
||||||
|
"mediumtext": "s",
|
||||||
|
"set": "set",
|
||||||
|
"smallint": "#",
|
||||||
|
"text": "s",
|
||||||
|
"time": "t",
|
||||||
|
"timestamp": "ts",
|
||||||
|
"tinyint": "#",
|
||||||
|
"varbinary": "010",
|
||||||
|
"varchar": "s",
|
||||||
|
}
|
||||||
|
return mapping.get(info_schema_type, "?")
|
||||||
|
|
||||||
|
|
||||||
|
class HarlequinMySQLAdapter(HarlequinAdapter):
|
||||||
|
ADAPTER_OPTIONS = MYSQLADAPTER_OPTIONS
|
||||||
|
IMPLEMENTS_CANCEL = True
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
conn_str: Sequence[str],
|
||||||
|
host: str | None = None,
|
||||||
|
port: str | int | None = 3306,
|
||||||
|
unix_socket: str | None = None,
|
||||||
|
database: str | None = None,
|
||||||
|
user: str | None = None,
|
||||||
|
password: str | None = None,
|
||||||
|
password2: str | None = None,
|
||||||
|
password3: str | None = None,
|
||||||
|
connection_timeout: str | int | None = None,
|
||||||
|
ssl_ca: str | None = None,
|
||||||
|
ssl_cert: str | None = None,
|
||||||
|
ssl_disabled: str | bool | None = False,
|
||||||
|
ssl_key: str | None = None,
|
||||||
|
openid_token_file: str | None = None,
|
||||||
|
pool_size: str | int | None = 5,
|
||||||
|
**_: Any,
|
||||||
|
) -> None:
|
||||||
|
if conn_str:
|
||||||
|
raise HarlequinConnectionError(
|
||||||
|
f"Cannot provide a DSN to the MySQL adapter. Got:\n{conn_str}"
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
self.options = {
|
||||||
|
"host": host,
|
||||||
|
"port": int(port) if port is not None else 3306,
|
||||||
|
"unix_socket": unix_socket,
|
||||||
|
"database": database,
|
||||||
|
"user": user,
|
||||||
|
"password": password,
|
||||||
|
"password2": password2,
|
||||||
|
"password3": password3,
|
||||||
|
"connection_timeout": int(connection_timeout)
|
||||||
|
if connection_timeout is not None
|
||||||
|
else None,
|
||||||
|
"ssl_ca": ssl_ca,
|
||||||
|
"ssl_cert": ssl_cert,
|
||||||
|
"ssl_disabled": ssl_disabled if ssl_disabled is not None else False,
|
||||||
|
"ssl_key": ssl_key,
|
||||||
|
"openid_token_file": openid_token_file,
|
||||||
|
"pool_size": int(pool_size) if pool_size is not None else 5,
|
||||||
|
}
|
||||||
|
except (ValueError, TypeError) as e:
|
||||||
|
raise HarlequinConfigError(
|
||||||
|
msg=f"MySQL adapter received bad config value: {e}",
|
||||||
|
title="Harlequin could not initialize the selected adapter.",
|
||||||
|
) from e
|
||||||
|
|
||||||
|
@property
|
||||||
|
def connection_id(self) -> str | None:
|
||||||
|
host = self.options.get("host", "") or ""
|
||||||
|
sock = self.options.get("unix_socket", "") or ""
|
||||||
|
host = host if host or sock else "127.0.0.1"
|
||||||
|
|
||||||
|
port = self.options.get("port", 3306)
|
||||||
|
database = self.options.get("database", "") or ""
|
||||||
|
|
||||||
|
return f"{host}{sock}:{port}/{database}"
|
||||||
|
|
||||||
|
def connect(self) -> HarlequinMySQLConnection:
|
||||||
|
conn = HarlequinMySQLConnection(conn_str=tuple(), options=self.options)
|
||||||
|
return conn
|
154
src/harlequin_mysql/catalog.py
Normal file
154
src/harlequin_mysql/catalog.py
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from harlequin.catalog import InteractiveCatalogItem
|
||||||
|
|
||||||
|
from harlequin_mysql.interactions import (
|
||||||
|
execute_drop_database_statement,
|
||||||
|
execute_drop_table_statement,
|
||||||
|
execute_drop_view_statement,
|
||||||
|
execute_use_statement,
|
||||||
|
insert_columns_at_cursor,
|
||||||
|
show_select_star,
|
||||||
|
)
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from harlequin_mysql.adapter import HarlequinMySQLConnection
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ColumnCatalogItem(InteractiveCatalogItem["HarlequinMySQLConnection"]):
|
||||||
|
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["HarlequinMySQLConnection"]):
|
||||||
|
INTERACTIONS = [
|
||||||
|
("Insert Columns at Cursor", insert_columns_at_cursor),
|
||||||
|
("Preview Data", show_select_star),
|
||||||
|
]
|
||||||
|
parent: "DatabaseCatalogItem" | None = None
|
||||||
|
|
||||||
|
def fetch_children(self) -> list[ColumnCatalogItem]:
|
||||||
|
if self.parent is None or self.connection is None:
|
||||||
|
return []
|
||||||
|
result = self.connection._get_columns(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: "DatabaseCatalogItem",
|
||||||
|
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: "DatabaseCatalogItem",
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class DatabaseCatalogItem(InteractiveCatalogItem["HarlequinMySQLConnection"]):
|
||||||
|
INTERACTIONS = [
|
||||||
|
("Set Editor Context (USE)", execute_use_statement),
|
||||||
|
("Drop Database", execute_drop_database_statement),
|
||||||
|
]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_label(
|
||||||
|
cls, label: str, connection: "HarlequinMySQLConnection"
|
||||||
|
) -> "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[RelationCatalogItem]:
|
||||||
|
if self.connection is None:
|
||||||
|
return []
|
||||||
|
children: list[RelationCatalogItem] = []
|
||||||
|
result = self.connection._get_relations(self.label)
|
||||||
|
for table_label, table_type in result:
|
||||||
|
if table_type == "VIEW":
|
||||||
|
children.append(
|
||||||
|
ViewCatalogItem.from_parent(
|
||||||
|
parent=self,
|
||||||
|
label=table_label,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
children.append(
|
||||||
|
TableCatalogItem.from_parent(
|
||||||
|
parent=self,
|
||||||
|
label=table_label,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return children
|
151
src/harlequin_mysql/cli_options.py
Normal file
151
src/harlequin_mysql/cli_options.py
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from harlequin.options import (
|
||||||
|
FlagOption,
|
||||||
|
PathOption,
|
||||||
|
TextOption,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
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, ""
|
||||||
|
|
||||||
|
|
||||||
|
host = TextOption(
|
||||||
|
name="host",
|
||||||
|
description=("The host name or IP address of the MySQL server."),
|
||||||
|
short_decls=["-h"],
|
||||||
|
default="localhost",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
port = TextOption(
|
||||||
|
name="port",
|
||||||
|
description=("The TCP/IP port of the MySQL server. Must be an integer."),
|
||||||
|
short_decls=["-p"],
|
||||||
|
default="3306",
|
||||||
|
validator=_int_validator,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
unix_socket = TextOption(
|
||||||
|
name="unix_socket",
|
||||||
|
description=("The location of the Unix socket file."),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
database = TextOption(
|
||||||
|
name="database",
|
||||||
|
description=("The database name to use when connecting with the MySQL server."),
|
||||||
|
short_decls=["-d", "-db"],
|
||||||
|
default="postgres",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
user = TextOption(
|
||||||
|
name="user",
|
||||||
|
description=("The user name used to authenticate with the MySQL server."),
|
||||||
|
short_decls=["-u", "--username", "-U"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
password = TextOption(
|
||||||
|
name="password",
|
||||||
|
description=("The password to authenticate the user with the MySQL server."),
|
||||||
|
short_decls=["--password1"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
password2 = TextOption(
|
||||||
|
name="password2",
|
||||||
|
description=("For Multi-Factor Authentication (MFA); Added in 8.0.28."),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
password3 = TextOption(
|
||||||
|
name="password3",
|
||||||
|
description=("For Multi-Factor Authentication (MFA); Added in 8.0.28."),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
connect_timeout = TextOption(
|
||||||
|
name="connection_timeout",
|
||||||
|
description="Timeout for the TCP and Unix socket connections. Must be an integer.",
|
||||||
|
short_decls=["--connect_timeout"],
|
||||||
|
validator=_int_validator,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
ssl_ca = PathOption(
|
||||||
|
name="ssl-ca",
|
||||||
|
description="File containing the SSL certificate authority.",
|
||||||
|
exists=True,
|
||||||
|
dir_okay=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
ssl_cert = PathOption(
|
||||||
|
name="ssl-cert",
|
||||||
|
description="File containing the SSL certificate file.",
|
||||||
|
exists=True,
|
||||||
|
dir_okay=False,
|
||||||
|
short_decls=["--sslcert"],
|
||||||
|
)
|
||||||
|
|
||||||
|
ssl_disabled = FlagOption(
|
||||||
|
name="ssl-disabled",
|
||||||
|
description="True disables SSL/TLS usage.",
|
||||||
|
)
|
||||||
|
|
||||||
|
ssl_key = PathOption(
|
||||||
|
name="ssl-key",
|
||||||
|
description="File containing the SSL key.",
|
||||||
|
exists=True,
|
||||||
|
dir_okay=False,
|
||||||
|
short_decls=["--sslkey"],
|
||||||
|
)
|
||||||
|
|
||||||
|
openid_token_file = PathOption(
|
||||||
|
name="openid-token-file",
|
||||||
|
description="File containing the OpenID Token.",
|
||||||
|
exists=True,
|
||||||
|
dir_okay=False,
|
||||||
|
short_decls=["--oid"],
|
||||||
|
)
|
||||||
|
|
||||||
|
pool_size = TextOption(
|
||||||
|
name="pool-size",
|
||||||
|
description=(
|
||||||
|
"The number of concurrent connections maintained by Harlequin. MySQL "
|
||||||
|
"only allows one cursor per connection, so this sets the number of queries "
|
||||||
|
"that can be executed at once in Harlequin."
|
||||||
|
),
|
||||||
|
short_decls=["-n"],
|
||||||
|
default="5",
|
||||||
|
validator=_int_validator,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
MYSQLADAPTER_OPTIONS = [
|
||||||
|
host,
|
||||||
|
port,
|
||||||
|
unix_socket,
|
||||||
|
database,
|
||||||
|
user,
|
||||||
|
password,
|
||||||
|
password2,
|
||||||
|
password3,
|
||||||
|
connect_timeout,
|
||||||
|
ssl_ca,
|
||||||
|
ssl_cert,
|
||||||
|
ssl_disabled,
|
||||||
|
ssl_key,
|
||||||
|
openid_token_file,
|
||||||
|
pool_size,
|
||||||
|
]
|
48
src/harlequin_mysql/completions.py
Normal file
48
src/harlequin_mysql/completions.py
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import csv
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from harlequin import HarlequinCompletion
|
||||||
|
|
||||||
|
WORD = re.compile(r"\w+")
|
||||||
|
|
||||||
|
|
||||||
|
def load_completions() -> list[HarlequinCompletion]:
|
||||||
|
completions: list[HarlequinCompletion] = []
|
||||||
|
|
||||||
|
keywords_path = Path(__file__).parent / "keywords.csv"
|
||||||
|
with keywords_path.open("r") as f:
|
||||||
|
reader = csv.reader(f, dialect="unix")
|
||||||
|
for name, reserved, removed in reader:
|
||||||
|
if removed == "False":
|
||||||
|
completions.append(
|
||||||
|
HarlequinCompletion(
|
||||||
|
label=name.lower(),
|
||||||
|
type_label="kw",
|
||||||
|
value=name.lower(),
|
||||||
|
priority=100 if reserved else 1000,
|
||||||
|
context=None,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
functions_path = Path(__file__).parent / "functions.tsv"
|
||||||
|
with functions_path.open("r") as f:
|
||||||
|
reader = csv.reader(f, dialect="unix", delimiter="\t")
|
||||||
|
for name, _, _, deprecated in reader:
|
||||||
|
if deprecated:
|
||||||
|
continue
|
||||||
|
for alias in name.split(", "):
|
||||||
|
if WORD.match(alias):
|
||||||
|
completions.append(
|
||||||
|
HarlequinCompletion(
|
||||||
|
label=alias.split("...")[0].split("(")[0].lower(),
|
||||||
|
type_label="fn",
|
||||||
|
value=alias.split("...")[0].split("(")[0].lower(),
|
||||||
|
priority=1000,
|
||||||
|
context=None,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return completions
|
441
src/harlequin_mysql/functions.tsv
Normal file
441
src/harlequin_mysql/functions.tsv
Normal file
|
@ -0,0 +1,441 @@
|
||||||
|
Name Description Introduced Deprecated
|
||||||
|
ABS() Return the absolute value
|
||||||
|
ACOS() Return the arc cosine
|
||||||
|
ADDDATE() Add time values (intervals) to a date value
|
||||||
|
ADDTIME() Add time
|
||||||
|
AES_DECRYPT() Decrypt using AES
|
||||||
|
AES_ENCRYPT() Encrypt using AES
|
||||||
|
AND, && Logical AND
|
||||||
|
ANY_VALUE() Suppress ONLY_FULL_GROUP_BY value rejection
|
||||||
|
ASCII() Return numeric value of left-most character
|
||||||
|
ASIN() Return the arc sine
|
||||||
|
asynchronous_connection_failover_add_managed() Add group member source server configuration information to a replication channel source list 8.0.23
|
||||||
|
asynchronous_connection_failover_add_source() Add source server configuration information server to a replication channel source list 8.0.22
|
||||||
|
asynchronous_connection_failover_delete_managed() Remove a managed group from a replication channel source list 8.0.23
|
||||||
|
asynchronous_connection_failover_delete_source() Remove a source server from a replication channel source list 8.0.22
|
||||||
|
asynchronous_connection_failover_reset() Remove all settings relating to group replication asynchronous failover 8.0.27
|
||||||
|
ATAN() Return the arc tangent
|
||||||
|
ATAN2(), ATAN() Return the arc tangent of the two arguments
|
||||||
|
AVG() Return the average value of the argument
|
||||||
|
BENCHMARK() Repeatedly execute an expression
|
||||||
|
BETWEEN ... AND ... Whether a value is within a range of values
|
||||||
|
BIN() Return a string containing binary representation of a number
|
||||||
|
BIN_TO_UUID() Convert binary UUID to string
|
||||||
|
BINARY Cast a string to a binary string 8.0.27
|
||||||
|
BIT_AND() Return bitwise AND
|
||||||
|
BIT_COUNT() Return the number of bits that are set
|
||||||
|
BIT_LENGTH() Return length of argument in bits
|
||||||
|
BIT_OR() Return bitwise OR
|
||||||
|
BIT_XOR() Return bitwise XOR
|
||||||
|
CAN_ACCESS_COLUMN() Internal use only
|
||||||
|
CAN_ACCESS_DATABASE() Internal use only
|
||||||
|
CAN_ACCESS_TABLE() Internal use only
|
||||||
|
CAN_ACCESS_USER() Internal use only 8.0.22
|
||||||
|
CAN_ACCESS_VIEW() Internal use only
|
||||||
|
CASE Case operator
|
||||||
|
CAST() Cast a value as a certain type
|
||||||
|
CEIL() Return the smallest integer value not less than the argument
|
||||||
|
CEILING() Return the smallest integer value not less than the argument
|
||||||
|
CHAR() Return the character for each integer passed
|
||||||
|
CHAR_LENGTH() Return number of characters in argument
|
||||||
|
CHARACTER_LENGTH() Synonym for CHAR_LENGTH()
|
||||||
|
CHARSET() Return the character set of the argument
|
||||||
|
COALESCE() Return the first non-NULL argument
|
||||||
|
COERCIBILITY() Return the collation coercibility value of the string argument
|
||||||
|
COLLATION() Return the collation of the string argument
|
||||||
|
COMPRESS() Return result as a binary string
|
||||||
|
CONCAT() Return concatenated string
|
||||||
|
CONCAT_WS() Return concatenate with separator
|
||||||
|
CONNECTION_ID() Return the connection ID (thread ID) for the connection
|
||||||
|
CONV() Convert numbers between different number bases
|
||||||
|
CONVERT() Cast a value as a certain type
|
||||||
|
CONVERT_TZ() Convert from one time zone to another
|
||||||
|
COS() Return the cosine
|
||||||
|
COT() Return the cotangent
|
||||||
|
COUNT() Return a count of the number of rows returned
|
||||||
|
COUNT(DISTINCT) Return the count of a number of different values
|
||||||
|
CRC32() Compute a cyclic redundancy check value
|
||||||
|
CUME_DIST() Cumulative distribution value
|
||||||
|
CURDATE() Return the current date
|
||||||
|
CURRENT_DATE(), CURRENT_DATE Synonyms for CURDATE()
|
||||||
|
CURRENT_ROLE() Return the current active roles
|
||||||
|
CURRENT_TIME(), CURRENT_TIME Synonyms for CURTIME()
|
||||||
|
CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP Synonyms for NOW()
|
||||||
|
CURRENT_USER(), CURRENT_USER The authenticated user name and host name
|
||||||
|
CURTIME() Return the current time
|
||||||
|
DATABASE() Return the default (current) database name
|
||||||
|
DATE() Extract the date part of a date or datetime expression
|
||||||
|
DATE_ADD() Add time values (intervals) to a date value
|
||||||
|
DATE_FORMAT() Format date as specified
|
||||||
|
DATE_SUB() Subtract a time value (interval) from a date
|
||||||
|
DATEDIFF() Subtract two dates
|
||||||
|
DAY() Synonym for DAYOFMONTH()
|
||||||
|
DAYNAME() Return the name of the weekday
|
||||||
|
DAYOFMONTH() Return the day of the month (0-31)
|
||||||
|
DAYOFWEEK() Return the weekday index of the argument
|
||||||
|
DAYOFYEAR() Return the day of the year (1-366)
|
||||||
|
DEFAULT() Return the default value for a table column
|
||||||
|
DEGREES() Convert radians to degrees
|
||||||
|
DENSE_RANK() Rank of current row within its partition, without gaps
|
||||||
|
DIV Integer division
|
||||||
|
ELT() Return string at index number
|
||||||
|
EXP() Raise to the power of
|
||||||
|
EXPORT_SET() Return a string such that for every bit set in the value bits, you get an on string and for every unset bit, you get an off string
|
||||||
|
EXTRACT() Extract part of a date
|
||||||
|
ExtractValue() Extract a value from an XML string using XPath notation
|
||||||
|
FIELD() Index (position) of first argument in subsequent arguments
|
||||||
|
FIND_IN_SET() Index (position) of first argument within second argument
|
||||||
|
FIRST_VALUE() Value of argument from first row of window frame
|
||||||
|
FLOOR() Return the largest integer value not greater than the argument
|
||||||
|
FORMAT() Return a number formatted to specified number of decimal places
|
||||||
|
FORMAT_BYTES() Convert byte count to value with units 8.0.16
|
||||||
|
FORMAT_PICO_TIME() Convert time in picoseconds to value with units 8.0.16
|
||||||
|
FOUND_ROWS() For a SELECT with a LIMIT clause, the number of rows that would be returned were there no LIMIT clause
|
||||||
|
FROM_BASE64() Decode base64 encoded string and return result
|
||||||
|
FROM_DAYS() Convert a day number to a date
|
||||||
|
FROM_UNIXTIME() Format Unix timestamp as a date
|
||||||
|
GeomCollection() Construct geometry collection from geometries
|
||||||
|
GeometryCollection() Construct geometry collection from geometries
|
||||||
|
GET_DD_COLUMN_PRIVILEGES() Internal use only
|
||||||
|
GET_DD_CREATE_OPTIONS() Internal use only
|
||||||
|
GET_DD_INDEX_SUB_PART_LENGTH() Internal use only
|
||||||
|
GET_FORMAT() Return a date format string
|
||||||
|
GET_LOCK() Get a named lock
|
||||||
|
GREATEST() Return the largest argument
|
||||||
|
GROUP_CONCAT() Return a concatenated string
|
||||||
|
group_replication_disable_member_action() Disable member action for event specified 8.0.26
|
||||||
|
group_replication_enable_member_action() Enable member action for event specified 8.0.26
|
||||||
|
group_replication_get_communication_protocol() Get version of group replication communication protocol currently in use 8.0.16
|
||||||
|
group_replication_get_write_concurrency() Get maximum number of consensus instances currently set for group 8.0.13
|
||||||
|
group_replication_reset_member_actions() Reset all member actions to defaults and configuration version number to 1 8.0.26
|
||||||
|
group_replication_set_as_primary() Make a specific group member the primary 8.0.29
|
||||||
|
group_replication_set_communication_protocol() Set version for group replication communication protocol to use 8.0.16
|
||||||
|
group_replication_set_write_concurrency() Set maximum number of consensus instances that can be executed in parallel 8.0.13
|
||||||
|
group_replication_switch_to_multi_primary_mode() Changes the mode of a group running in single-primary mode to multi-primary mode 8.0.13
|
||||||
|
group_replication_switch_to_single_primary_mode() Changes the mode of a group running in multi-primary mode to single-primary mode 8.0.13
|
||||||
|
GROUPING() Distinguish super-aggregate ROLLUP rows from regular rows
|
||||||
|
GTID_SUBSET() Return true if all GTIDs in subset are also in set; otherwise false.
|
||||||
|
GTID_SUBTRACT() Return all GTIDs in set that are not in subset.
|
||||||
|
HEX() Hexadecimal representation of decimal or string value
|
||||||
|
HOUR() Extract the hour
|
||||||
|
ICU_VERSION() ICU library version
|
||||||
|
IF() If/else construct
|
||||||
|
IFNULL() Null if/else construct
|
||||||
|
IN() Whether a value is within a set of values
|
||||||
|
INET_ATON() Return the numeric value of an IP address
|
||||||
|
INET_NTOA() Return the IP address from a numeric value
|
||||||
|
INET6_ATON() Return the numeric value of an IPv6 address
|
||||||
|
INET6_NTOA() Return the IPv6 address from a numeric value
|
||||||
|
INSERT() Insert substring at specified position up to specified number of characters
|
||||||
|
INSTR() Return the index of the first occurrence of substring
|
||||||
|
INTERNAL_AUTO_INCREMENT() Internal use only
|
||||||
|
INTERNAL_AVG_ROW_LENGTH() Internal use only
|
||||||
|
INTERNAL_CHECK_TIME() Internal use only
|
||||||
|
INTERNAL_CHECKSUM() Internal use only
|
||||||
|
INTERNAL_DATA_FREE() Internal use only
|
||||||
|
INTERNAL_DATA_LENGTH() Internal use only
|
||||||
|
INTERNAL_DD_CHAR_LENGTH() Internal use only
|
||||||
|
INTERNAL_GET_COMMENT_OR_ERROR() Internal use only
|
||||||
|
INTERNAL_GET_ENABLED_ROLE_JSON() Internal use only 8.0.19
|
||||||
|
INTERNAL_GET_HOSTNAME() Internal use only 8.0.19
|
||||||
|
INTERNAL_GET_USERNAME() Internal use only 8.0.19
|
||||||
|
INTERNAL_GET_VIEW_WARNING_OR_ERROR() Internal use only
|
||||||
|
INTERNAL_INDEX_COLUMN_CARDINALITY() Internal use only
|
||||||
|
INTERNAL_INDEX_LENGTH() Internal use only
|
||||||
|
INTERNAL_IS_ENABLED_ROLE() Internal use only 8.0.19
|
||||||
|
INTERNAL_IS_MANDATORY_ROLE() Internal use only 8.0.19
|
||||||
|
INTERNAL_KEYS_DISABLED() Internal use only
|
||||||
|
INTERNAL_MAX_DATA_LENGTH() Internal use only
|
||||||
|
INTERNAL_TABLE_ROWS() Internal use only
|
||||||
|
INTERNAL_UPDATE_TIME() Internal use only
|
||||||
|
INTERVAL() Return the index of the argument that is less than the first argument
|
||||||
|
IS Test a value against a boolean
|
||||||
|
IS_FREE_LOCK() Whether the named lock is free
|
||||||
|
IS_IPV4() Whether argument is an IPv4 address
|
||||||
|
IS_IPV4_COMPAT() Whether argument is an IPv4-compatible address
|
||||||
|
IS_IPV4_MAPPED() Whether argument is an IPv4-mapped address
|
||||||
|
IS_IPV6() Whether argument is an IPv6 address
|
||||||
|
IS NOT Test a value against a boolean
|
||||||
|
IS NOT NULL NOT NULL value test
|
||||||
|
IS NULL NULL value test
|
||||||
|
IS_USED_LOCK() Whether the named lock is in use; return connection identifier if true
|
||||||
|
IS_UUID() Whether argument is a valid UUID
|
||||||
|
ISNULL() Test whether the argument is NULL
|
||||||
|
JSON_ARRAY() Create JSON array
|
||||||
|
JSON_ARRAY_APPEND() Append data to JSON document
|
||||||
|
JSON_ARRAY_INSERT() Insert into JSON array
|
||||||
|
JSON_ARRAYAGG() Return result set as a single JSON array
|
||||||
|
JSON_CONTAINS() Whether JSON document contains specific object at path
|
||||||
|
JSON_CONTAINS_PATH() Whether JSON document contains any data at path
|
||||||
|
JSON_DEPTH() Maximum depth of JSON document
|
||||||
|
JSON_EXTRACT() Return data from JSON document
|
||||||
|
JSON_INSERT() Insert data into JSON document
|
||||||
|
JSON_KEYS() Array of keys from JSON document
|
||||||
|
JSON_LENGTH() Number of elements in JSON document
|
||||||
|
JSON_MERGE() Merge JSON documents, preserving duplicate keys. Deprecated synonym for JSON_MERGE_PRESERVE() Yes
|
||||||
|
JSON_MERGE_PATCH() Merge JSON documents, replacing values of duplicate keys
|
||||||
|
JSON_MERGE_PRESERVE() Merge JSON documents, preserving duplicate keys
|
||||||
|
JSON_OBJECT() Create JSON object
|
||||||
|
JSON_OBJECTAGG() Return result set as a single JSON object
|
||||||
|
JSON_OVERLAPS() Compares two JSON documents, returns TRUE (1) if these have any key-value pairs or array elements in common, otherwise FALSE (0) 8.0.17
|
||||||
|
JSON_PRETTY() Print a JSON document in human-readable format
|
||||||
|
JSON_QUOTE() Quote JSON document
|
||||||
|
JSON_REMOVE() Remove data from JSON document
|
||||||
|
JSON_REPLACE() Replace values in JSON document
|
||||||
|
JSON_SCHEMA_VALID() Validate JSON document against JSON schema; returns TRUE/1 if document validates against schema, or FALSE/0 if it does not 8.0.17
|
||||||
|
JSON_SCHEMA_VALIDATION_REPORT() Validate JSON document against JSON schema; returns report in JSON format on outcome on validation including success or failure and reasons for failure 8.0.17
|
||||||
|
JSON_SEARCH() Path to value within JSON document
|
||||||
|
JSON_SET() Insert data into JSON document
|
||||||
|
JSON_STORAGE_FREE() Freed space within binary representation of JSON column value following partial update
|
||||||
|
JSON_STORAGE_SIZE() Space used for storage of binary representation of a JSON document
|
||||||
|
JSON_TABLE() Return data from a JSON expression as a relational table
|
||||||
|
JSON_TYPE() Type of JSON value
|
||||||
|
JSON_UNQUOTE() Unquote JSON value
|
||||||
|
JSON_VALID() Whether JSON value is valid
|
||||||
|
JSON_VALUE() Extract value from JSON document at location pointed to by path provided; return this value as VARCHAR(512) or specified type 8.0.21
|
||||||
|
LAG() Value of argument from row lagging current row within partition
|
||||||
|
LAST_DAY Return the last day of the month for the argument
|
||||||
|
LAST_INSERT_ID() Value of the AUTOINCREMENT column for the last INSERT
|
||||||
|
LAST_VALUE() Value of argument from last row of window frame
|
||||||
|
LCASE() Synonym for LOWER()
|
||||||
|
LEAD() Value of argument from row leading current row within partition
|
||||||
|
LEAST() Return the smallest argument
|
||||||
|
LEFT() Return the leftmost number of characters as specified
|
||||||
|
LENGTH() Return the length of a string in bytes
|
||||||
|
LIKE Simple pattern matching
|
||||||
|
LineString() Construct LineString from Point values
|
||||||
|
LN() Return the natural logarithm of the argument
|
||||||
|
LOAD_FILE() Load the named file
|
||||||
|
LOCALTIME(), LOCALTIME Synonym for NOW()
|
||||||
|
LOCALTIMESTAMP, LOCALTIMESTAMP() Synonym for NOW()
|
||||||
|
LOCATE() Return the position of the first occurrence of substring
|
||||||
|
LOG() Return the natural logarithm of the first argument
|
||||||
|
LOG10() Return the base-10 logarithm of the argument
|
||||||
|
LOG2() Return the base-2 logarithm of the argument
|
||||||
|
LOWER() Return the argument in lowercase
|
||||||
|
LPAD() Return the string argument, left-padded with the specified string
|
||||||
|
LTRIM() Remove leading spaces
|
||||||
|
MAKE_SET() Return a set of comma-separated strings that have the corresponding bit in bits set
|
||||||
|
MAKEDATE() Create a date from the year and day of year
|
||||||
|
MAKETIME() Create time from hour, minute, second
|
||||||
|
MASTER_POS_WAIT() Block until the replica has read and applied all updates up to the specified position 8.0.26
|
||||||
|
MATCH() Perform full-text search
|
||||||
|
MAX() Return the maximum value
|
||||||
|
MBRContains() Whether MBR of one geometry contains MBR of another
|
||||||
|
MBRCoveredBy() Whether one MBR is covered by another
|
||||||
|
MBRCovers() Whether one MBR covers another
|
||||||
|
MBRDisjoint() Whether MBRs of two geometries are disjoint
|
||||||
|
MBREquals() Whether MBRs of two geometries are equal
|
||||||
|
MBRIntersects() Whether MBRs of two geometries intersect
|
||||||
|
MBROverlaps() Whether MBRs of two geometries overlap
|
||||||
|
MBRTouches() Whether MBRs of two geometries touch
|
||||||
|
MBRWithin() Whether MBR of one geometry is within MBR of another
|
||||||
|
MD5() Calculate MD5 checksum
|
||||||
|
MEMBER OF() Returns true (1) if first operand matches any element of JSON array passed as second operand, otherwise returns false (0) 8.0.17
|
||||||
|
MICROSECOND() Return the microseconds from argument
|
||||||
|
MID() Return a substring starting from the specified position
|
||||||
|
MIN() Return the minimum value
|
||||||
|
MINUTE() Return the minute from the argument
|
||||||
|
MOD() Return the remainder
|
||||||
|
MONTH() Return the month from the date passed
|
||||||
|
MONTHNAME() Return the name of the month
|
||||||
|
MultiLineString() Contruct MultiLineString from LineString values
|
||||||
|
MultiPoint() Construct MultiPoint from Point values
|
||||||
|
MultiPolygon() Construct MultiPolygon from Polygon values
|
||||||
|
NAME_CONST() Cause the column to have the given name
|
||||||
|
NOT, ! Negates value
|
||||||
|
NOT BETWEEN ... AND ... Whether a value is not within a range of values
|
||||||
|
NOT IN() Whether a value is not within a set of values
|
||||||
|
NOT LIKE Negation of simple pattern matching
|
||||||
|
NOT REGEXP Negation of REGEXP
|
||||||
|
NOW() Return the current date and time
|
||||||
|
NTH_VALUE() Value of argument from N-th row of window frame
|
||||||
|
NTILE() Bucket number of current row within its partition.
|
||||||
|
NULLIF() Return NULL if expr1 = expr2
|
||||||
|
OCT() Return a string containing octal representation of a number
|
||||||
|
OCTET_LENGTH() Synonym for LENGTH()
|
||||||
|
OR, || Logical OR
|
||||||
|
ORD() Return character code for leftmost character of the argument
|
||||||
|
PERCENT_RANK() Percentage rank value
|
||||||
|
PERIOD_ADD() Add a period to a year-month
|
||||||
|
PERIOD_DIFF() Return the number of months between periods
|
||||||
|
PI() Return the value of pi
|
||||||
|
Point() Construct Point from coordinates
|
||||||
|
Polygon() Construct Polygon from LineString arguments
|
||||||
|
POSITION() Synonym for LOCATE()
|
||||||
|
POW() Return the argument raised to the specified power
|
||||||
|
POWER() Return the argument raised to the specified power
|
||||||
|
PS_CURRENT_THREAD_ID() Performance Schema thread ID for current thread 8.0.16
|
||||||
|
PS_THREAD_ID() Performance Schema thread ID for given thread 8.0.16
|
||||||
|
QUARTER() Return the quarter from a date argument
|
||||||
|
QUOTE() Escape the argument for use in an SQL statement
|
||||||
|
RADIANS() Return argument converted to radians
|
||||||
|
RAND() Return a random floating-point value
|
||||||
|
RANDOM_BYTES() Return a random byte vector
|
||||||
|
RANK() Rank of current row within its partition, with gaps
|
||||||
|
REGEXP Whether string matches regular expression
|
||||||
|
REGEXP_INSTR() Starting index of substring matching regular expression
|
||||||
|
REGEXP_LIKE() Whether string matches regular expression
|
||||||
|
REGEXP_REPLACE() Replace substrings matching regular expression
|
||||||
|
REGEXP_SUBSTR() Return substring matching regular expression
|
||||||
|
RELEASE_ALL_LOCKS() Release all current named locks
|
||||||
|
RELEASE_LOCK() Release the named lock
|
||||||
|
REPEAT() Repeat a string the specified number of times
|
||||||
|
REPLACE() Replace occurrences of a specified string
|
||||||
|
REVERSE() Reverse the characters in a string
|
||||||
|
RIGHT() Return the specified rightmost number of characters
|
||||||
|
RLIKE Whether string matches regular expression
|
||||||
|
ROLES_GRAPHML() Return a GraphML document representing memory role subgraphs
|
||||||
|
ROUND() Round the argument
|
||||||
|
ROW_COUNT() The number of rows updated
|
||||||
|
ROW_NUMBER() Number of current row within its partition
|
||||||
|
RPAD() Append string the specified number of times
|
||||||
|
RTRIM() Remove trailing spaces
|
||||||
|
SCHEMA() Synonym for DATABASE()
|
||||||
|
SEC_TO_TIME() Converts seconds to 'hh:mm:ss' format
|
||||||
|
SECOND() Return the second (0-59)
|
||||||
|
SESSION_USER() Synonym for USER()
|
||||||
|
SHA1(), SHA() Calculate an SHA-1 160-bit checksum
|
||||||
|
SHA2() Calculate an SHA-2 checksum
|
||||||
|
SIGN() Return the sign of the argument
|
||||||
|
SIN() Return the sine of the argument
|
||||||
|
SLEEP() Sleep for a number of seconds
|
||||||
|
SOUNDEX() Return a soundex string
|
||||||
|
SOUNDS LIKE Compare sounds
|
||||||
|
SOURCE_POS_WAIT() Block until the replica has read and applied all updates up to the specified position 8.0.26
|
||||||
|
SPACE() Return a string of the specified number of spaces
|
||||||
|
SQRT() Return the square root of the argument
|
||||||
|
ST_Area() Return Polygon or MultiPolygon area
|
||||||
|
ST_AsBinary(), ST_AsWKB() Convert from internal geometry format to WKB
|
||||||
|
ST_AsGeoJSON() Generate GeoJSON object from geometry
|
||||||
|
ST_AsText(), ST_AsWKT() Convert from internal geometry format to WKT
|
||||||
|
ST_Buffer() Return geometry of points within given distance from geometry
|
||||||
|
ST_Buffer_Strategy() Produce strategy option for ST_Buffer()
|
||||||
|
ST_Centroid() Return centroid as a point
|
||||||
|
ST_Collect() Aggregate spatial values into collection 8.0.24
|
||||||
|
ST_Contains() Whether one geometry contains another
|
||||||
|
ST_ConvexHull() Return convex hull of geometry
|
||||||
|
ST_Crosses() Whether one geometry crosses another
|
||||||
|
ST_Difference() Return point set difference of two geometries
|
||||||
|
ST_Dimension() Dimension of geometry
|
||||||
|
ST_Disjoint() Whether one geometry is disjoint from another
|
||||||
|
ST_Distance() The distance of one geometry from another
|
||||||
|
ST_Distance_Sphere() Minimum distance on earth between two geometries
|
||||||
|
ST_EndPoint() End Point of LineString
|
||||||
|
ST_Envelope() Return MBR of geometry
|
||||||
|
ST_Equals() Whether one geometry is equal to another
|
||||||
|
ST_ExteriorRing() Return exterior ring of Polygon
|
||||||
|
ST_FrechetDistance() The discrete Fréchet distance of one geometry from another 8.0.23
|
||||||
|
ST_GeoHash() Produce a geohash value
|
||||||
|
ST_GeomCollFromText(), ST_GeometryCollectionFromText(), ST_GeomCollFromTxt() Return geometry collection from WKT
|
||||||
|
ST_GeomCollFromWKB(), ST_GeometryCollectionFromWKB() Return geometry collection from WKB
|
||||||
|
ST_GeometryN() Return N-th geometry from geometry collection
|
||||||
|
ST_GeometryType() Return name of geometry type
|
||||||
|
ST_GeomFromGeoJSON() Generate geometry from GeoJSON object
|
||||||
|
ST_GeomFromText(), ST_GeometryFromText() Return geometry from WKT
|
||||||
|
ST_GeomFromWKB(), ST_GeometryFromWKB() Return geometry from WKB
|
||||||
|
ST_HausdorffDistance() The discrete Hausdorff distance of one geometry from another 8.0.23
|
||||||
|
ST_InteriorRingN() Return N-th interior ring of Polygon
|
||||||
|
ST_Intersection() Return point set intersection of two geometries
|
||||||
|
ST_Intersects() Whether one geometry intersects another
|
||||||
|
ST_IsClosed() Whether a geometry is closed and simple
|
||||||
|
ST_IsEmpty() Whether a geometry is empty
|
||||||
|
ST_IsSimple() Whether a geometry is simple
|
||||||
|
ST_IsValid() Whether a geometry is valid
|
||||||
|
ST_LatFromGeoHash() Return latitude from geohash value
|
||||||
|
ST_Latitude() Return latitude of Point 8.0.12
|
||||||
|
ST_Length() Return length of LineString
|
||||||
|
ST_LineFromText(), ST_LineStringFromText() Construct LineString from WKT
|
||||||
|
ST_LineFromWKB(), ST_LineStringFromWKB() Construct LineString from WKB
|
||||||
|
ST_LineInterpolatePoint() The point a given percentage along a LineString 8.0.24
|
||||||
|
ST_LineInterpolatePoints() The points a given percentage along a LineString 8.0.24
|
||||||
|
ST_LongFromGeoHash() Return longitude from geohash value
|
||||||
|
ST_Longitude() Return longitude of Point 8.0.12
|
||||||
|
ST_MakeEnvelope() Rectangle around two points
|
||||||
|
ST_MLineFromText(), ST_MultiLineStringFromText() Construct MultiLineString from WKT
|
||||||
|
ST_MLineFromWKB(), ST_MultiLineStringFromWKB() Construct MultiLineString from WKB
|
||||||
|
ST_MPointFromText(), ST_MultiPointFromText() Construct MultiPoint from WKT
|
||||||
|
ST_MPointFromWKB(), ST_MultiPointFromWKB() Construct MultiPoint from WKB
|
||||||
|
ST_MPolyFromText(), ST_MultiPolygonFromText() Construct MultiPolygon from WKT
|
||||||
|
ST_MPolyFromWKB(), ST_MultiPolygonFromWKB() Construct MultiPolygon from WKB
|
||||||
|
ST_NumGeometries() Return number of geometries in geometry collection
|
||||||
|
ST_NumInteriorRing(), ST_NumInteriorRings() Return number of interior rings in Polygon
|
||||||
|
ST_NumPoints() Return number of points in LineString
|
||||||
|
ST_Overlaps() Whether one geometry overlaps another
|
||||||
|
ST_PointAtDistance() The point a given distance along a LineString 8.0.24
|
||||||
|
ST_PointFromGeoHash() Convert geohash value to POINT value
|
||||||
|
ST_PointFromText() Construct Point from WKT
|
||||||
|
ST_PointFromWKB() Construct Point from WKB
|
||||||
|
ST_PointN() Return N-th point from LineString
|
||||||
|
ST_PolyFromText(), ST_PolygonFromText() Construct Polygon from WKT
|
||||||
|
ST_PolyFromWKB(), ST_PolygonFromWKB() Construct Polygon from WKB
|
||||||
|
ST_Simplify() Return simplified geometry
|
||||||
|
ST_SRID() Return spatial reference system ID for geometry
|
||||||
|
ST_StartPoint() Start Point of LineString
|
||||||
|
ST_SwapXY() Return argument with X/Y coordinates swapped
|
||||||
|
ST_SymDifference() Return point set symmetric difference of two geometries
|
||||||
|
ST_Touches() Whether one geometry touches another
|
||||||
|
ST_Transform() Transform coordinates of geometry 8.0.13
|
||||||
|
ST_Union() Return point set union of two geometries
|
||||||
|
ST_Validate() Return validated geometry
|
||||||
|
ST_Within() Whether one geometry is within another
|
||||||
|
ST_X() Return X coordinate of Point
|
||||||
|
ST_Y() Return Y coordinate of Point
|
||||||
|
STATEMENT_DIGEST() Compute statement digest hash value
|
||||||
|
STATEMENT_DIGEST_TEXT() Compute normalized statement digest
|
||||||
|
STD() Return the population standard deviation
|
||||||
|
STDDEV() Return the population standard deviation
|
||||||
|
STDDEV_POP() Return the population standard deviation
|
||||||
|
STDDEV_SAMP() Return the sample standard deviation
|
||||||
|
STR_TO_DATE() Convert a string to a date
|
||||||
|
STRCMP() Compare two strings
|
||||||
|
SUBDATE() Synonym for DATE_SUB() when invoked with three arguments
|
||||||
|
SUBSTR() Return the substring as specified
|
||||||
|
SUBSTRING() Return the substring as specified
|
||||||
|
SUBSTRING_INDEX() Return a substring from a string before the specified number of occurrences of the delimiter
|
||||||
|
SUBTIME() Subtract times
|
||||||
|
SUM() Return the sum
|
||||||
|
SYSDATE() Return the time at which the function executes
|
||||||
|
SYSTEM_USER() Synonym for USER()
|
||||||
|
TAN() Return the tangent of the argument
|
||||||
|
TIME() Extract the time portion of the expression passed
|
||||||
|
TIME_FORMAT() Format as time
|
||||||
|
TIME_TO_SEC() Return the argument converted to seconds
|
||||||
|
TIMEDIFF() Subtract time
|
||||||
|
TIMESTAMP() With a single argument, this function returns the date or datetime expression; with two arguments, the sum of the arguments
|
||||||
|
TIMESTAMPADD() Add an interval to a datetime expression
|
||||||
|
TIMESTAMPDIFF() Return the difference of two datetime expressions, using the units specified
|
||||||
|
TO_BASE64() Return the argument converted to a base-64 string
|
||||||
|
TO_DAYS() Return the date argument converted to days
|
||||||
|
TO_SECONDS() Return the date or datetime argument converted to seconds since Year 0
|
||||||
|
TRIM() Remove leading and trailing spaces
|
||||||
|
TRUNCATE() Truncate to specified number of decimal places
|
||||||
|
UCASE() Synonym for UPPER()
|
||||||
|
UNCOMPRESS() Uncompress a string compressed
|
||||||
|
UNCOMPRESSED_LENGTH() Return the length of a string before compression
|
||||||
|
UNHEX() Return a string containing hex representation of a number
|
||||||
|
UNIX_TIMESTAMP() Return a Unix timestamp
|
||||||
|
UpdateXML() Return replaced XML fragment
|
||||||
|
UPPER() Convert to uppercase
|
||||||
|
USER() The user name and host name provided by the client
|
||||||
|
UTC_DATE() Return the current UTC date
|
||||||
|
UTC_TIME() Return the current UTC time
|
||||||
|
UTC_TIMESTAMP() Return the current UTC date and time
|
||||||
|
UUID() Return a Universal Unique Identifier (UUID)
|
||||||
|
UUID_SHORT() Return an integer-valued universal identifier
|
||||||
|
UUID_TO_BIN() Convert string UUID to binary
|
||||||
|
VALIDATE_PASSWORD_STRENGTH() Determine strength of password
|
||||||
|
VALUES() Define the values to be used during an INSERT
|
||||||
|
VAR_POP() Return the population standard variance
|
||||||
|
VAR_SAMP() Return the sample variance
|
||||||
|
VARIANCE() Return the population standard variance
|
||||||
|
VERSION() Return a string that indicates the MySQL server version
|
||||||
|
WAIT_FOR_EXECUTED_GTID_SET() Wait until the given GTIDs have executed on the replica.
|
||||||
|
WAIT_UNTIL_SQL_THREAD_AFTER_GTIDS() Use WAIT_FOR_EXECUTED_GTID_SET(). 8.0.18
|
||||||
|
WEEK() Return the week number
|
||||||
|
WEEKDAY() Return the weekday index
|
||||||
|
WEEKOFYEAR() Return the calendar week of the date (1-53)
|
||||||
|
WEIGHT_STRING() Return the weight string for a string
|
||||||
|
XOR Logical XOR
|
||||||
|
YEAR() Return the year
|
||||||
|
YEARWEEK() Return the year and week
|
|
113
src/harlequin_mysql/interactions.py
Normal file
113
src/harlequin_mysql/interactions.py
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
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_mysql.catalog import (
|
||||||
|
ColumnCatalogItem,
|
||||||
|
DatabaseCatalogItem,
|
||||||
|
RelationCatalogItem,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def execute_use_statement(
|
||||||
|
item: "DatabaseCatalogItem",
|
||||||
|
driver: "HarlequinDriver",
|
||||||
|
) -> None:
|
||||||
|
if item.connection is None:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
item.connection.execute(f"use {item.label}")
|
||||||
|
except HarlequinQueryError:
|
||||||
|
driver.notify("Could not switch context", severity="error")
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
driver.notify(f"Editor context switched to {item.label}")
|
||||||
|
|
||||||
|
|
||||||
|
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_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))
|
892
src/harlequin_mysql/keywords.csv
Normal file
892
src/harlequin_mysql/keywords.csv
Normal file
|
@ -0,0 +1,892 @@
|
||||||
|
ACCESSIBLE,False,False
|
||||||
|
ACCOUNT,False,False
|
||||||
|
ACTION,False,False
|
||||||
|
ACTIVE,False,False
|
||||||
|
ADD,False,False
|
||||||
|
ADMIN,False,False
|
||||||
|
AFTER,False,False
|
||||||
|
AGAINST,False,False
|
||||||
|
AGGREGATE,False,False
|
||||||
|
ALGORITHM,False,False
|
||||||
|
ALL,False,False
|
||||||
|
ALTER,False,False
|
||||||
|
ALWAYS,False,False
|
||||||
|
ANALYSE,False,True
|
||||||
|
ANALYZE,False,False
|
||||||
|
AND,False,False
|
||||||
|
ANY,False,False
|
||||||
|
ARRAY,False,False
|
||||||
|
AS,False,False
|
||||||
|
ASC,False,False
|
||||||
|
ASCII,False,False
|
||||||
|
ASENSITIVE,False,False
|
||||||
|
AT,False,False
|
||||||
|
ATTRIBUTE,False,False
|
||||||
|
AUTHENTICATION,False,False
|
||||||
|
AUTOEXTEND_SIZE,False,False
|
||||||
|
AUTO_INCREMENT,False,False
|
||||||
|
AVG,False,False
|
||||||
|
AVG_ROW_LENGTH,False,False
|
||||||
|
BACKUP,False,False
|
||||||
|
BEFORE,False,False
|
||||||
|
BEGIN,False,False
|
||||||
|
BETWEEN,False,False
|
||||||
|
BIGINT,False,False
|
||||||
|
BINARY,False,False
|
||||||
|
BINLOG,False,False
|
||||||
|
BIT,False,False
|
||||||
|
BLOB,False,False
|
||||||
|
BLOCK,False,False
|
||||||
|
BOOL,False,False
|
||||||
|
BOOLEAN,False,False
|
||||||
|
BOTH,False,False
|
||||||
|
BTREE,False,False
|
||||||
|
BUCKETS,False,False
|
||||||
|
BULK,False,False
|
||||||
|
BY,False,False
|
||||||
|
BYTE,False,False
|
||||||
|
CACHE,False,False
|
||||||
|
CALL,False,False
|
||||||
|
CASCADE,False,False
|
||||||
|
CASCADED,False,False
|
||||||
|
CASE,False,False
|
||||||
|
CATALOG_NAME,False,False
|
||||||
|
CHAIN,False,False
|
||||||
|
CHALLENGE_RESPONSE,False,False
|
||||||
|
CHANGE,False,False
|
||||||
|
CHANGED,False,False
|
||||||
|
CHANNEL,False,False
|
||||||
|
CHAR,False,False
|
||||||
|
CHARACTER,False,False
|
||||||
|
CHARSET,False,False
|
||||||
|
CHECK,False,False
|
||||||
|
CHECKSUM,False,False
|
||||||
|
CIPHER,False,False
|
||||||
|
CLASS_ORIGIN,False,False
|
||||||
|
CLIENT,False,False
|
||||||
|
CLONE,False,False
|
||||||
|
CLOSE,False,False
|
||||||
|
COALESCE,False,False
|
||||||
|
CODE,False,False
|
||||||
|
COLLATE,False,False
|
||||||
|
COLLATION,False,False
|
||||||
|
COLUMN,False,False
|
||||||
|
COLUMNS,False,False
|
||||||
|
COLUMN_FORMAT,False,False
|
||||||
|
COLUMN_NAME,False,False
|
||||||
|
COMMENT,False,False
|
||||||
|
COMMIT,False,False
|
||||||
|
COMMITTED,False,False
|
||||||
|
COMPACT,False,False
|
||||||
|
COMPLETION,False,False
|
||||||
|
COMPONENT,False,False
|
||||||
|
COMPRESSED,False,False
|
||||||
|
COMPRESSION,False,False
|
||||||
|
CONCURRENT,False,False
|
||||||
|
CONDITION,False,False
|
||||||
|
CONNECTION,False,False
|
||||||
|
CONSISTENT,False,False
|
||||||
|
CONSTRAINT,False,False
|
||||||
|
CONSTRAINT_CATALOG,False,False
|
||||||
|
CONSTRAINT_NAME,False,False
|
||||||
|
CONSTRAINT_SCHEMA,False,False
|
||||||
|
CONTAINS,False,False
|
||||||
|
CONTEXT,False,False
|
||||||
|
CONTINUE,False,False
|
||||||
|
CONVERT,False,False
|
||||||
|
CPU,False,False
|
||||||
|
CREATE,False,False
|
||||||
|
CROSS,False,False
|
||||||
|
CUBE,False,False
|
||||||
|
CUME_DIST,False,False
|
||||||
|
CURRENT,False,False
|
||||||
|
CURRENT_DATE,False,False
|
||||||
|
CURRENT_TIME,False,False
|
||||||
|
CURRENT_TIMESTAMP,False,False
|
||||||
|
CURRENT_USER,False,False
|
||||||
|
CURSOR,False,False
|
||||||
|
CURSOR_NAME,False,False
|
||||||
|
DATA,False,False
|
||||||
|
DATABASE,False,False
|
||||||
|
DATABASES,False,False
|
||||||
|
DATAFILE,False,False
|
||||||
|
DATE,False,False
|
||||||
|
DATETIME,False,False
|
||||||
|
DAY,False,False
|
||||||
|
DAY_HOUR,False,False
|
||||||
|
DAY_MICROSECOND,False,False
|
||||||
|
DAY_MINUTE,False,False
|
||||||
|
DAY_SECOND,False,False
|
||||||
|
DEALLOCATE,False,False
|
||||||
|
DEC,False,False
|
||||||
|
DECIMAL,False,False
|
||||||
|
DECLARE,False,False
|
||||||
|
DEFAULT,False,False
|
||||||
|
DEFAULT_AUTH,False,False
|
||||||
|
DEFINER,False,False
|
||||||
|
DEFINITION,False,False
|
||||||
|
DELAYED,False,False
|
||||||
|
DELAY_KEY_WRITE,False,False
|
||||||
|
DELETE,False,False
|
||||||
|
DENSE_RANK,False,False
|
||||||
|
DESC,False,False
|
||||||
|
DESCRIBE,False,False
|
||||||
|
DESCRIPTION,False,False
|
||||||
|
DES_KEY_FILE,False,True
|
||||||
|
DETERMINISTIC,False,False
|
||||||
|
DIAGNOSTICS,False,False
|
||||||
|
DIRECTORY,False,False
|
||||||
|
DISABLE,False,False
|
||||||
|
DISCARD,False,False
|
||||||
|
DISK,False,False
|
||||||
|
DISTINCT,False,False
|
||||||
|
DISTINCTROW,False,False
|
||||||
|
DIV,False,False
|
||||||
|
DO,False,False
|
||||||
|
DOUBLE,False,False
|
||||||
|
DROP,False,False
|
||||||
|
DUAL,False,False
|
||||||
|
DUMPFILE,False,False
|
||||||
|
DUPLICATE,False,False
|
||||||
|
DYNAMIC,False,False
|
||||||
|
EACH,False,False
|
||||||
|
ELSE,False,False
|
||||||
|
ELSEIF,False,False
|
||||||
|
EMPTY,False,False
|
||||||
|
ENABLE,False,False
|
||||||
|
ENCLOSED,False,False
|
||||||
|
ENCRYPTION,False,False
|
||||||
|
END,False,False
|
||||||
|
ENDS,False,False
|
||||||
|
ENFORCED,False,False
|
||||||
|
ENGINE,False,False
|
||||||
|
ENGINES,False,False
|
||||||
|
ENGINE_ATTRIBUTE,False,False
|
||||||
|
ENUM,False,False
|
||||||
|
ERROR,False,False
|
||||||
|
ERRORS,False,False
|
||||||
|
ESCAPE,False,False
|
||||||
|
ESCAPED,False,False
|
||||||
|
EVENT,False,False
|
||||||
|
EVENTS,False,False
|
||||||
|
EVERY,False,False
|
||||||
|
EXCEPT,False,False
|
||||||
|
EXCHANGE,False,False
|
||||||
|
EXCLUDE,False,False
|
||||||
|
EXECUTE,False,False
|
||||||
|
EXISTS,False,False
|
||||||
|
EXIT,False,False
|
||||||
|
EXPANSION,False,False
|
||||||
|
EXPIRE,False,False
|
||||||
|
EXPLAIN,False,False
|
||||||
|
EXPORT,False,False
|
||||||
|
EXTENDED,False,False
|
||||||
|
EXTENT_SIZE,False,False
|
||||||
|
FACTOR,False,False
|
||||||
|
FAILED_LOGIN_ATTEMPTS,False,False
|
||||||
|
FALSE,False,False
|
||||||
|
FAST,False,False
|
||||||
|
FAULTS,False,False
|
||||||
|
FETCH,False,False
|
||||||
|
FIELDS,False,False
|
||||||
|
FILE,False,False
|
||||||
|
FILE_BLOCK_SIZE,False,False
|
||||||
|
FILTER,False,False
|
||||||
|
FINISH,False,False
|
||||||
|
FIRST,False,False
|
||||||
|
FIRST_VALUE,False,False
|
||||||
|
FIXED,False,False
|
||||||
|
FLOAT,False,False
|
||||||
|
FLOAT4,False,False
|
||||||
|
FLOAT8,False,False
|
||||||
|
FLUSH,False,False
|
||||||
|
FOLLOWING,False,False
|
||||||
|
FOLLOWS,False,False
|
||||||
|
FOR,False,False
|
||||||
|
FORCE,False,False
|
||||||
|
FOREIGN,False,False
|
||||||
|
FORMAT,False,False
|
||||||
|
FOUND,False,False
|
||||||
|
FROM,False,False
|
||||||
|
FULL,False,False
|
||||||
|
FULLTEXT,False,False
|
||||||
|
FUNCTION,False,False
|
||||||
|
GENERAL,False,False
|
||||||
|
GENERATE,False,False
|
||||||
|
GENERATED,False,False
|
||||||
|
GEOMCOLLECTION,False,False
|
||||||
|
GEOMETRY,False,False
|
||||||
|
GEOMETRYCOLLECTION,False,False
|
||||||
|
GET,False,False
|
||||||
|
GET_FORMAT,False,False
|
||||||
|
GET_MASTER_PUBLIC_KEY,False,False
|
||||||
|
GET_SOURCE_PUBLIC_KEY,False,False
|
||||||
|
GLOBAL,False,False
|
||||||
|
GRANT,False,False
|
||||||
|
GRANTS,False,False
|
||||||
|
GROUP,False,False
|
||||||
|
GROUPING,False,False
|
||||||
|
GROUPS,False,False
|
||||||
|
GROUP_REPLICATION,False,False
|
||||||
|
GTID_ONLY,False,False
|
||||||
|
HANDLER,False,False
|
||||||
|
HASH,False,False
|
||||||
|
HAVING,False,False
|
||||||
|
HELP,False,False
|
||||||
|
HIGH_PRIORITY,False,False
|
||||||
|
HISTOGRAM,False,False
|
||||||
|
HISTORY,False,False
|
||||||
|
HOST,False,False
|
||||||
|
HOSTS,False,False
|
||||||
|
HOUR,False,False
|
||||||
|
HOUR_MICROSECOND,False,False
|
||||||
|
HOUR_MINUTE,False,False
|
||||||
|
HOUR_SECOND,False,False
|
||||||
|
IDENTIFIED,False,False
|
||||||
|
IF,False,False
|
||||||
|
IGNORE,False,False
|
||||||
|
IGNORE_SERVER_IDS,False,False
|
||||||
|
IMPORT,False,False
|
||||||
|
IN,False,False
|
||||||
|
INACTIVE,False,False
|
||||||
|
INDEX,False,False
|
||||||
|
INDEXES,False,False
|
||||||
|
INFILE,False,False
|
||||||
|
INITIAL,False,False
|
||||||
|
INITIAL_SIZE,False,False
|
||||||
|
INITIATE,False,False
|
||||||
|
INNER,False,False
|
||||||
|
INOUT,False,False
|
||||||
|
INSENSITIVE,False,False
|
||||||
|
INSERT,False,False
|
||||||
|
INSERT_METHOD,False,False
|
||||||
|
INSTALL,False,False
|
||||||
|
INSTANCE,False,False
|
||||||
|
INT,False,False
|
||||||
|
INT1,False,False
|
||||||
|
INT2,False,False
|
||||||
|
INT3,False,False
|
||||||
|
INT4,False,False
|
||||||
|
INT8,False,False
|
||||||
|
INTEGER,False,False
|
||||||
|
INTERSECT,False,False
|
||||||
|
INTERVAL,False,False
|
||||||
|
INTO,False,False
|
||||||
|
INVISIBLE,False,False
|
||||||
|
INVOKER,False,False
|
||||||
|
IO,False,False
|
||||||
|
IO_AFTER_GTIDS,False,False
|
||||||
|
IO_BEFORE_GTIDS,False,False
|
||||||
|
IO_THREAD,False,False
|
||||||
|
IPC,False,False
|
||||||
|
IS,False,False
|
||||||
|
ISOLATION,False,False
|
||||||
|
ISSUER,False,False
|
||||||
|
ITERATE,False,False
|
||||||
|
JOIN,False,False
|
||||||
|
JSON,False,False
|
||||||
|
JSON_TABLE,False,False
|
||||||
|
JSON_VALUE,False,False
|
||||||
|
KEY,False,False
|
||||||
|
KEYRING,False,False
|
||||||
|
KEYS,False,False
|
||||||
|
KEY_BLOCK_SIZE,False,False
|
||||||
|
KILL,False,False
|
||||||
|
LAG,False,False
|
||||||
|
LANGUAGE,False,False
|
||||||
|
LAST,False,False
|
||||||
|
LAST_VALUE,False,False
|
||||||
|
LATERAL,False,False
|
||||||
|
LEAD,False,False
|
||||||
|
LEADING,False,False
|
||||||
|
LEAVE,False,False
|
||||||
|
LEAVES,False,False
|
||||||
|
LEFT,False,False
|
||||||
|
LESS,False,False
|
||||||
|
LEVEL,False,False
|
||||||
|
LIKE,False,False
|
||||||
|
LIMIT,False,False
|
||||||
|
LINEAR,False,False
|
||||||
|
LINES,False,False
|
||||||
|
LINESTRING,False,False
|
||||||
|
LIST,False,False
|
||||||
|
LOAD,False,False
|
||||||
|
LOCAL,False,False
|
||||||
|
LOCALTIME,False,False
|
||||||
|
LOCALTIMESTAMP,False,False
|
||||||
|
LOCK,False,False
|
||||||
|
LOCKED,False,False
|
||||||
|
LOCKS,False,False
|
||||||
|
LOGFILE,False,False
|
||||||
|
LOGS,False,False
|
||||||
|
LONG,False,False
|
||||||
|
LONGBLOB,False,False
|
||||||
|
LONGTEXT,False,False
|
||||||
|
LOOP,False,False
|
||||||
|
LOW_PRIORITY,False,False
|
||||||
|
MASTER,False,False
|
||||||
|
MASTER_AUTO_POSITION,False,False
|
||||||
|
MASTER_BIND,False,False
|
||||||
|
MASTER_COMPRESSION_ALGORITHMS,False,False
|
||||||
|
MASTER_CONNECT_RETRY,False,False
|
||||||
|
MASTER_DELAY,False,False
|
||||||
|
MASTER_HEARTBEAT_PERIOD,False,False
|
||||||
|
MASTER_HOST,False,False
|
||||||
|
MASTER_LOG_FILE,False,False
|
||||||
|
MASTER_LOG_POS,False,False
|
||||||
|
MASTER_PASSWORD,False,False
|
||||||
|
MASTER_PORT,False,False
|
||||||
|
MASTER_PUBLIC_KEY_PATH,False,False
|
||||||
|
MASTER_RETRY_COUNT,False,False
|
||||||
|
MASTER_SERVER_ID,False,True
|
||||||
|
MASTER_SSL,False,False
|
||||||
|
MASTER_SSL_CA,False,False
|
||||||
|
MASTER_SSL_CAPATH,False,False
|
||||||
|
MASTER_SSL_CERT,False,False
|
||||||
|
MASTER_SSL_CIPHER,False,False
|
||||||
|
MASTER_SSL_CRL,False,False
|
||||||
|
MASTER_SSL_CRLPATH,False,False
|
||||||
|
MASTER_SSL_KEY,False,False
|
||||||
|
MASTER_SSL_VERIFY_SERVER_CERT,False,False
|
||||||
|
MASTER_TLS_CIPHERSUITES,False,False
|
||||||
|
MASTER_TLS_VERSION,False,False
|
||||||
|
MASTER_USER,False,False
|
||||||
|
MASTER_ZSTD_COMPRESSION_LEVEL,False,False
|
||||||
|
MATCH,False,False
|
||||||
|
MAXVALUE,False,False
|
||||||
|
MAX_CONNECTIONS_PER_HOUR,False,False
|
||||||
|
MAX_QUERIES_PER_HOUR,False,False
|
||||||
|
MAX_ROWS,False,False
|
||||||
|
MAX_SIZE,False,False
|
||||||
|
MAX_UPDATES_PER_HOUR,False,False
|
||||||
|
MAX_USER_CONNECTIONS,False,False
|
||||||
|
MEDIUM,False,False
|
||||||
|
MEDIUMBLOB,False,False
|
||||||
|
MEDIUMINT,False,False
|
||||||
|
MEDIUMTEXT,False,False
|
||||||
|
MEMBER,False,False
|
||||||
|
MEMORY,False,False
|
||||||
|
MERGE,False,False
|
||||||
|
MESSAGE_TEXT,False,False
|
||||||
|
MICROSECOND,False,False
|
||||||
|
MIDDLEINT,False,False
|
||||||
|
MIGRATE,False,False
|
||||||
|
MINUTE,False,False
|
||||||
|
MINUTE_MICROSECOND,False,False
|
||||||
|
MINUTE_SECOND,False,False
|
||||||
|
MIN_ROWS,False,False
|
||||||
|
MOD,False,False
|
||||||
|
MODE,False,False
|
||||||
|
MODIFIES,False,False
|
||||||
|
MODIFY,False,False
|
||||||
|
MONTH,False,False
|
||||||
|
MULTILINESTRING,False,False
|
||||||
|
MULTIPOINT,False,False
|
||||||
|
MULTIPOLYGON,False,False
|
||||||
|
MUTEX,False,False
|
||||||
|
MYSQL_ERRNO,False,False
|
||||||
|
NAME,False,False
|
||||||
|
NAMES,False,False
|
||||||
|
NATIONAL,False,False
|
||||||
|
NATURAL,False,False
|
||||||
|
NCHAR,False,False
|
||||||
|
NDB,False,False
|
||||||
|
NDBCLUSTER,False,False
|
||||||
|
NESTED,False,False
|
||||||
|
NETWORK_NAMESPACE,False,False
|
||||||
|
NEVER,False,False
|
||||||
|
NEW,False,False
|
||||||
|
NEXT,False,False
|
||||||
|
NO,False,False
|
||||||
|
NODEGROUP,False,False
|
||||||
|
NONE,False,False
|
||||||
|
NOT,False,False
|
||||||
|
NOWAIT,False,False
|
||||||
|
NO_WAIT,False,False
|
||||||
|
NO_WRITE_TO_BINLOG,False,False
|
||||||
|
NTH_VALUE,False,False
|
||||||
|
NTILE,False,False
|
||||||
|
NULL,False,False
|
||||||
|
NULLS,False,False
|
||||||
|
NUMBER,False,False
|
||||||
|
NUMERIC,False,False
|
||||||
|
NVARCHAR,False,False
|
||||||
|
OF,False,False
|
||||||
|
OFF,False,False
|
||||||
|
OFFSET,False,False
|
||||||
|
OJ,False,False
|
||||||
|
OLD,False,False
|
||||||
|
ON,False,False
|
||||||
|
ONE,False,False
|
||||||
|
ONLY,False,False
|
||||||
|
OPEN,False,False
|
||||||
|
OPTIMIZE,False,False
|
||||||
|
OPTIMIZER_COSTS,False,False
|
||||||
|
OPTION,False,False
|
||||||
|
OPTIONAL,False,False
|
||||||
|
OPTIONALLY,False,False
|
||||||
|
OPTIONS,False,False
|
||||||
|
OR,False,False
|
||||||
|
ORDER,False,False
|
||||||
|
ORDINALITY,False,False
|
||||||
|
ORGANIZATION,False,False
|
||||||
|
OTHERS,False,False
|
||||||
|
OUT,False,False
|
||||||
|
OUTER,False,False
|
||||||
|
OUTFILE,False,False
|
||||||
|
OVER,False,False
|
||||||
|
OWNER,False,False
|
||||||
|
PACK_KEYS,False,False
|
||||||
|
PAGE,False,False
|
||||||
|
PARSER,False,False
|
||||||
|
PARTIAL,False,False
|
||||||
|
PARTITION,False,False
|
||||||
|
PARTITIONING,False,False
|
||||||
|
PARTITIONS,False,False
|
||||||
|
PASSWORD,False,False
|
||||||
|
PASSWORD_LOCK_TIME,False,False
|
||||||
|
PATH,False,False
|
||||||
|
PERCENT_RANK,False,False
|
||||||
|
PERSIST,False,False
|
||||||
|
PERSIST_ONLY,False,False
|
||||||
|
PHASE,False,False
|
||||||
|
PLUGIN,False,False
|
||||||
|
PLUGINS,False,False
|
||||||
|
PLUGIN_DIR,False,False
|
||||||
|
POINT,False,False
|
||||||
|
POLYGON,False,False
|
||||||
|
PORT,False,False
|
||||||
|
PRECEDES,False,False
|
||||||
|
PRECEDING,False,False
|
||||||
|
PRECISION,False,False
|
||||||
|
PREPARE,False,False
|
||||||
|
PRESERVE,False,False
|
||||||
|
PREV,False,False
|
||||||
|
PRIMARY,False,False
|
||||||
|
PRIVILEGES,False,False
|
||||||
|
PRIVILEGE_CHECKS_USER,False,False
|
||||||
|
PROCEDURE,False,False
|
||||||
|
PROCESS,False,False
|
||||||
|
PROCESSLIST,False,False
|
||||||
|
PROFILE,False,False
|
||||||
|
PROFILES,False,False
|
||||||
|
PROXY,False,False
|
||||||
|
PURGE,False,False
|
||||||
|
QUARTER,False,False
|
||||||
|
QUERY,False,False
|
||||||
|
QUICK,False,False
|
||||||
|
RANDOM,False,False
|
||||||
|
RANGE,False,False
|
||||||
|
RANK,False,False
|
||||||
|
READ,False,False
|
||||||
|
READS,False,False
|
||||||
|
READ_ONLY,False,False
|
||||||
|
READ_WRITE,False,False
|
||||||
|
REAL,False,False
|
||||||
|
REBUILD,False,False
|
||||||
|
RECOVER,False,False
|
||||||
|
RECURSIVE,False,False
|
||||||
|
REDOFILE,False,True
|
||||||
|
REDO_BUFFER_SIZE,False,False
|
||||||
|
REDUNDANT,False,False
|
||||||
|
REFERENCE,False,False
|
||||||
|
REFERENCES,False,False
|
||||||
|
REGEXP,False,False
|
||||||
|
REGISTRATION,False,False
|
||||||
|
RELAY,False,False
|
||||||
|
RELAYLOG,False,False
|
||||||
|
RELAY_LOG_FILE,False,False
|
||||||
|
RELAY_LOG_POS,False,False
|
||||||
|
RELAY_THREAD,False,False
|
||||||
|
RELEASE,False,False
|
||||||
|
RELOAD,False,False
|
||||||
|
REMOTE,False,True
|
||||||
|
REMOVE,False,False
|
||||||
|
RENAME,False,False
|
||||||
|
REORGANIZE,False,False
|
||||||
|
REPAIR,False,False
|
||||||
|
REPEAT,False,False
|
||||||
|
REPEATABLE,False,False
|
||||||
|
REPLACE,False,False
|
||||||
|
REPLICA,False,False
|
||||||
|
REPLICAS,False,False
|
||||||
|
REPLICATE_DO_DB,False,False
|
||||||
|
REPLICATE_DO_TABLE,False,False
|
||||||
|
REPLICATE_IGNORE_DB,False,False
|
||||||
|
REPLICATE_IGNORE_TABLE,False,False
|
||||||
|
REPLICATE_REWRITE_DB,False,False
|
||||||
|
REPLICATE_WILD_DO_TABLE,False,False
|
||||||
|
REPLICATE_WILD_IGNORE_TABLE,False,False
|
||||||
|
REPLICATION,False,False
|
||||||
|
REQUIRE,False,False
|
||||||
|
REQUIRE_ROW_FORMAT,False,False
|
||||||
|
RESET,False,False
|
||||||
|
RESIGNAL,False,False
|
||||||
|
RESOURCE,False,False
|
||||||
|
RESPECT,False,False
|
||||||
|
RESTART,False,False
|
||||||
|
RESTORE,False,False
|
||||||
|
RESTRICT,False,False
|
||||||
|
RESUME,False,False
|
||||||
|
RETAIN,False,False
|
||||||
|
RETURN,False,False
|
||||||
|
RETURNED_SQLSTATE,False,False
|
||||||
|
RETURNING,False,False
|
||||||
|
RETURNS,False,False
|
||||||
|
REUSE,False,False
|
||||||
|
REVERSE,False,False
|
||||||
|
REVOKE,False,False
|
||||||
|
RIGHT,False,False
|
||||||
|
RLIKE,False,False
|
||||||
|
ROLE,False,False
|
||||||
|
ROLLBACK,False,False
|
||||||
|
ROLLUP,False,False
|
||||||
|
ROTATE,False,False
|
||||||
|
ROUTINE,False,False
|
||||||
|
ROW,False,False
|
||||||
|
ROWS,False,False
|
||||||
|
ROW_COUNT,False,False
|
||||||
|
ROW_FORMAT,False,False
|
||||||
|
ROW_NUMBER,False,False
|
||||||
|
RTREE,False,False
|
||||||
|
SAVEPOINT,False,False
|
||||||
|
SCHEDULE,False,False
|
||||||
|
SCHEMA,False,False
|
||||||
|
SCHEMAS,False,False
|
||||||
|
SCHEMA_NAME,False,False
|
||||||
|
SECOND,False,False
|
||||||
|
SECONDARY,False,False
|
||||||
|
SECONDARY_ENGINE,False,False
|
||||||
|
SECONDARY_ENGINE_ATTRIBUTE,False,False
|
||||||
|
SECONDARY_LOAD,False,False
|
||||||
|
SECONDARY_UNLOAD,False,False
|
||||||
|
SECOND_MICROSECOND,False,False
|
||||||
|
SECURITY,False,False
|
||||||
|
SELECT,False,False
|
||||||
|
SENSITIVE,False,False
|
||||||
|
SEPARATOR,False,False
|
||||||
|
SERIAL,False,False
|
||||||
|
SERIALIZABLE,False,False
|
||||||
|
SERVER,False,False
|
||||||
|
SESSION,False,False
|
||||||
|
SET,False,False
|
||||||
|
SHARE,False,False
|
||||||
|
SHOW,False,False
|
||||||
|
SHUTDOWN,False,False
|
||||||
|
SIGNAL,False,False
|
||||||
|
SIGNED,False,False
|
||||||
|
SIMPLE,False,False
|
||||||
|
SKIP,False,False
|
||||||
|
SLAVE,False,False
|
||||||
|
SLOW,False,False
|
||||||
|
SMALLINT,False,False
|
||||||
|
SNAPSHOT,False,False
|
||||||
|
SOCKET,False,False
|
||||||
|
SOME,False,False
|
||||||
|
SONAME,False,False
|
||||||
|
SOUNDS,False,False
|
||||||
|
SOURCE,False,False
|
||||||
|
SOURCE_AUTO_POSITION,False,False
|
||||||
|
SOURCE_BIND,False,False
|
||||||
|
SOURCE_COMPRESSION_ALGORITHMS,False,False
|
||||||
|
SOURCE_CONNECT_RETRY,False,False
|
||||||
|
SOURCE_DELAY,False,False
|
||||||
|
SOURCE_HEARTBEAT_PERIOD,False,False
|
||||||
|
SOURCE_HOST,False,False
|
||||||
|
SOURCE_LOG_FILE,False,False
|
||||||
|
SOURCE_LOG_POS,False,False
|
||||||
|
SOURCE_PASSWORD,False,False
|
||||||
|
SOURCE_PORT,False,False
|
||||||
|
SOURCE_PUBLIC_KEY_PATH,False,False
|
||||||
|
SOURCE_RETRY_COUNT,False,False
|
||||||
|
SOURCE_SSL,False,False
|
||||||
|
SOURCE_SSL_CA,False,False
|
||||||
|
SOURCE_SSL_CAPATH,False,False
|
||||||
|
SOURCE_SSL_CERT,False,False
|
||||||
|
SOURCE_SSL_CIPHER,False,False
|
||||||
|
SOURCE_SSL_CRL,False,False
|
||||||
|
SOURCE_SSL_CRLPATH,False,False
|
||||||
|
SOURCE_SSL_KEY,False,False
|
||||||
|
SOURCE_SSL_VERIFY_SERVER_CERT,False,False
|
||||||
|
SOURCE_TLS_CIPHERSUITES,False,False
|
||||||
|
SOURCE_TLS_VERSION,False,False
|
||||||
|
SOURCE_USER,False,False
|
||||||
|
SOURCE_ZSTD_COMPRESSION_LEVEL,False,False
|
||||||
|
SPATIAL,False,False
|
||||||
|
SPECIFIC,False,False
|
||||||
|
SQL,False,False
|
||||||
|
SQLEXCEPTION,False,False
|
||||||
|
SQLSTATE,False,False
|
||||||
|
SQLWARNING,False,False
|
||||||
|
SQL_AFTER_GTIDS,False,False
|
||||||
|
SQL_AFTER_MTS_GAPS,False,False
|
||||||
|
SQL_BEFORE_GTIDS,False,False
|
||||||
|
SQL_BIG_RESULT,False,False
|
||||||
|
SQL_BUFFER_RESULT,False,False
|
||||||
|
SQL_CACHE,False,True
|
||||||
|
SQL_CALC_FOUND_ROWS,False,False
|
||||||
|
SQL_NO_CACHE,False,False
|
||||||
|
SQL_SMALL_RESULT,False,False
|
||||||
|
SQL_THREAD,False,False
|
||||||
|
SQL_TSI_DAY,False,False
|
||||||
|
SQL_TSI_HOUR,False,False
|
||||||
|
SQL_TSI_MINUTE,False,False
|
||||||
|
SQL_TSI_MONTH,False,False
|
||||||
|
SQL_TSI_QUARTER,False,False
|
||||||
|
SQL_TSI_SECOND,False,False
|
||||||
|
SQL_TSI_WEEK,False,False
|
||||||
|
SQL_TSI_YEAR,False,False
|
||||||
|
SRID,False,False
|
||||||
|
SSL,False,False
|
||||||
|
STACKED,False,False
|
||||||
|
START,False,False
|
||||||
|
STARTING,False,False
|
||||||
|
STARTS,False,False
|
||||||
|
STATS_AUTO_RECALC,False,False
|
||||||
|
STATS_PERSISTENT,False,False
|
||||||
|
STATS_SAMPLE_PAGES,False,False
|
||||||
|
STATUS,False,False
|
||||||
|
STOP,False,False
|
||||||
|
STORAGE,False,False
|
||||||
|
STORED,False,False
|
||||||
|
STRAIGHT_JOIN,False,False
|
||||||
|
STREAM,False,False
|
||||||
|
STRING,False,False
|
||||||
|
SUBCLASS_ORIGIN,False,False
|
||||||
|
SUBJECT,False,False
|
||||||
|
SUBPARTITION,False,False
|
||||||
|
SUBPARTITIONS,False,False
|
||||||
|
SUPER,False,False
|
||||||
|
SUSPEND,False,False
|
||||||
|
SWAPS,False,False
|
||||||
|
SWITCHES,False,False
|
||||||
|
SYSTEM,False,False
|
||||||
|
TABLE,False,False
|
||||||
|
TABLES,False,False
|
||||||
|
TABLESPACE,False,False
|
||||||
|
TABLE_CHECKSUM,False,False
|
||||||
|
TABLE_NAME,False,False
|
||||||
|
TEMPORARY,False,False
|
||||||
|
TEMPTABLE,False,False
|
||||||
|
TERMINATED,False,False
|
||||||
|
TEXT,False,False
|
||||||
|
THAN,False,False
|
||||||
|
THEN,False,False
|
||||||
|
THREAD_PRIORITY,False,False
|
||||||
|
TIES,False,False
|
||||||
|
TIME,False,False
|
||||||
|
TIMESTAMP,False,False
|
||||||
|
TIMESTAMPADD,False,False
|
||||||
|
TIMESTAMPDIFF,False,False
|
||||||
|
TINYBLOB,False,False
|
||||||
|
TINYINT,False,False
|
||||||
|
TINYTEXT,False,False
|
||||||
|
TLS,False,False
|
||||||
|
TO,False,False
|
||||||
|
TRAILING,False,False
|
||||||
|
TRANSACTION,False,False
|
||||||
|
TRIGGER,False,False
|
||||||
|
TRIGGERS,False,False
|
||||||
|
TRUE,False,False
|
||||||
|
TRUNCATE,False,False
|
||||||
|
TYPE,False,False
|
||||||
|
TYPES,False,False
|
||||||
|
UNBOUNDED,False,False
|
||||||
|
UNCOMMITTED,False,False
|
||||||
|
UNDEFINED,False,False
|
||||||
|
UNDO,False,False
|
||||||
|
UNDOFILE,False,False
|
||||||
|
UNDO_BUFFER_SIZE,False,False
|
||||||
|
UNICODE,False,False
|
||||||
|
UNINSTALL,False,False
|
||||||
|
UNION,False,False
|
||||||
|
UNIQUE,False,False
|
||||||
|
UNKNOWN,False,False
|
||||||
|
UNLOCK,False,False
|
||||||
|
UNREGISTER,False,False
|
||||||
|
UNSIGNED,False,False
|
||||||
|
UNTIL,False,False
|
||||||
|
UPDATE,False,False
|
||||||
|
UPGRADE,False,False
|
||||||
|
URL,False,False
|
||||||
|
USAGE,False,False
|
||||||
|
USE,False,False
|
||||||
|
USER,False,False
|
||||||
|
USER_RESOURCES,False,False
|
||||||
|
USE_FRM,False,False
|
||||||
|
USING,False,False
|
||||||
|
UTC_DATE,False,False
|
||||||
|
UTC_TIME,False,False
|
||||||
|
UTC_TIMESTAMP,False,False
|
||||||
|
VALIDATION,False,False
|
||||||
|
VALUE,False,False
|
||||||
|
VALUES,False,False
|
||||||
|
VARBINARY,False,False
|
||||||
|
VARCHAR,False,False
|
||||||
|
VARCHARACTER,False,False
|
||||||
|
VARIABLES,False,False
|
||||||
|
VARYING,False,False
|
||||||
|
VCPU,False,False
|
||||||
|
VIEW,False,False
|
||||||
|
VIRTUAL,False,False
|
||||||
|
VISIBLE,False,False
|
||||||
|
WAIT,False,False
|
||||||
|
WARNINGS,False,False
|
||||||
|
WEEK,False,False
|
||||||
|
WEIGHT_STRING,False,False
|
||||||
|
WHEN,False,False
|
||||||
|
WHERE,False,False
|
||||||
|
WHILE,False,False
|
||||||
|
WINDOW,False,False
|
||||||
|
WITH,False,False
|
||||||
|
WITHOUT,False,False
|
||||||
|
WORK,False,False
|
||||||
|
WRAPPER,False,False
|
||||||
|
WRITE,False,False
|
||||||
|
X509,False,False
|
||||||
|
XA,False,False
|
||||||
|
XID,False,False
|
||||||
|
XML,False,False
|
||||||
|
XOR,False,False
|
||||||
|
YEAR,False,False
|
||||||
|
YEAR_MONTH,False,False
|
||||||
|
ZEROFILL,False,False
|
||||||
|
ZONE,False,False
|
||||||
|
MySQL,False,False
|
||||||
|
The,False,False
|
||||||
|
A,False,False
|
||||||
|
ACTIVE,False,False
|
||||||
|
ADMIN,False,False
|
||||||
|
ARRAY,False,False
|
||||||
|
ATTRIBUTE,False,False
|
||||||
|
AUTHENTICATION,False,False
|
||||||
|
BUCKETS,False,False
|
||||||
|
BULK,False,False
|
||||||
|
CHALLENGE_RESPONSE,False,False
|
||||||
|
CLONE,False,False
|
||||||
|
COMPONENT,False,False
|
||||||
|
CUME_DIST,False,False
|
||||||
|
DEFINITION,False,False
|
||||||
|
DENSE_RANK,False,False
|
||||||
|
DESCRIPTION,False,False
|
||||||
|
EMPTY,False,False
|
||||||
|
ENFORCED,False,False
|
||||||
|
ENGINE_ATTRIBUTE,False,False
|
||||||
|
EXCEPT,False,False
|
||||||
|
EXCLUDE,False,False
|
||||||
|
FACTOR,False,False
|
||||||
|
FAILED_LOGIN_ATTEMPTS,False,False
|
||||||
|
FINISH,False,False
|
||||||
|
FIRST_VALUE,False,False
|
||||||
|
FOLLOWING,False,False
|
||||||
|
GENERATE,False,False
|
||||||
|
GEOMCOLLECTION,False,False
|
||||||
|
GET_MASTER_PUBLIC_KEY,False,False
|
||||||
|
GET_SOURCE_PUBLIC_KEY,False,False
|
||||||
|
GROUPING,False,False
|
||||||
|
GROUPS,False,False
|
||||||
|
GTID_ONLY,False,False
|
||||||
|
HISTOGRAM,False,False
|
||||||
|
HISTORY,False,False
|
||||||
|
INACTIVE,False,False
|
||||||
|
INITIAL,False,False
|
||||||
|
INITIATE,False,False
|
||||||
|
INTERSECT,False,False
|
||||||
|
INVISIBLE,False,False
|
||||||
|
JSON_TABLE,False,False
|
||||||
|
JSON_VALUE,False,False
|
||||||
|
KEYRING,False,False
|
||||||
|
LAG,False,False
|
||||||
|
LAST_VALUE,False,False
|
||||||
|
LATERAL,False,False
|
||||||
|
LEAD,False,False
|
||||||
|
LOCKED,False,False
|
||||||
|
MASTER_COMPRESSION_ALGORITHMS,False,False
|
||||||
|
MASTER_PUBLIC_KEY_PATH,False,False
|
||||||
|
MASTER_TLS_CIPHERSUITES,False,False
|
||||||
|
MASTER_ZSTD_COMPRESSION_LEVEL,False,False
|
||||||
|
MEMBER,False,False
|
||||||
|
NESTED,False,False
|
||||||
|
NETWORK_NAMESPACE,False,False
|
||||||
|
NOWAIT,False,False
|
||||||
|
NTH_VALUE,False,False
|
||||||
|
NTILE,False,False
|
||||||
|
NULLS,False,False
|
||||||
|
OF,False,False
|
||||||
|
OFF,False,False
|
||||||
|
OJ,False,False
|
||||||
|
OLD,False,False
|
||||||
|
OPTIONAL,False,False
|
||||||
|
ORDINALITY,False,False
|
||||||
|
ORGANIZATION,False,False
|
||||||
|
OTHERS,False,False
|
||||||
|
OVER,False,False
|
||||||
|
PASSWORD_LOCK_TIME,False,False
|
||||||
|
PATH,False,False
|
||||||
|
PERCENT_RANK,False,False
|
||||||
|
PERSIST,False,False
|
||||||
|
PERSIST_ONLY,False,False
|
||||||
|
PRECEDING,False,False
|
||||||
|
PRIVILEGE_CHECKS_USER,False,False
|
||||||
|
PROCESS,False,False
|
||||||
|
RANDOM,False,False
|
||||||
|
RANK,False,False
|
||||||
|
RECURSIVE,False,False
|
||||||
|
REFERENCE,False,False
|
||||||
|
REGISTRATION,False,False
|
||||||
|
REPLICA,False,False
|
||||||
|
REPLICAS,False,False
|
||||||
|
REQUIRE_ROW_FORMAT,False,False
|
||||||
|
RESOURCE,False,False
|
||||||
|
RESPECT,False,False
|
||||||
|
RESTART,False,False
|
||||||
|
RETAIN,False,False
|
||||||
|
RETURNING,False,False
|
||||||
|
REUSE,False,False
|
||||||
|
ROLE,False,False
|
||||||
|
ROW_NUMBER,False,False
|
||||||
|
SECONDARY,False,False
|
||||||
|
SECONDARY_ENGINE,False,False
|
||||||
|
SECONDARY_ENGINE_ATTRIBUTE,False,False
|
||||||
|
SECONDARY_LOAD,False,False
|
||||||
|
SECONDARY_UNLOAD,False,False
|
||||||
|
SKIP,False,False
|
||||||
|
SOURCE_AUTO_POSITION,False,False
|
||||||
|
SOURCE_BIND,False,False
|
||||||
|
SOURCE_COMPRESSION_ALGORITHMS,False,False
|
||||||
|
SOURCE_CONNECT_RETRY,False,False
|
||||||
|
SOURCE_DELAY,False,False
|
||||||
|
SOURCE_HEARTBEAT_PERIOD,False,False
|
||||||
|
SOURCE_HOST,False,False
|
||||||
|
SOURCE_LOG_FILE,False,False
|
||||||
|
SOURCE_LOG_POS,False,False
|
||||||
|
SOURCE_PASSWORD,False,False
|
||||||
|
SOURCE_PORT,False,False
|
||||||
|
SOURCE_PUBLIC_KEY_PATH,False,False
|
||||||
|
SOURCE_RETRY_COUNT,False,False
|
||||||
|
SOURCE_SSL,False,False
|
||||||
|
SOURCE_SSL_CA,False,False
|
||||||
|
SOURCE_SSL_CAPATH,False,False
|
||||||
|
SOURCE_SSL_CERT,False,False
|
||||||
|
SOURCE_SSL_CIPHER,False,False
|
||||||
|
SOURCE_SSL_CRL,False,False
|
||||||
|
SOURCE_SSL_CRLPATH,False,False
|
||||||
|
SOURCE_SSL_KEY,False,False
|
||||||
|
SOURCE_SSL_VERIFY_SERVER_CERT,False,False
|
||||||
|
SOURCE_TLS_CIPHERSUITES,False,False
|
||||||
|
SOURCE_TLS_VERSION,False,False
|
||||||
|
SOURCE_USER,False,False
|
||||||
|
SOURCE_ZSTD_COMPRESSION_LEVEL,False,False
|
||||||
|
SRID,False,False
|
||||||
|
STREAM,False,False
|
||||||
|
SYSTEM,False,False
|
||||||
|
THREAD_PRIORITY,False,False
|
||||||
|
TIES,False,False
|
||||||
|
TLS,False,False
|
||||||
|
UNBOUNDED,False,False
|
||||||
|
UNREGISTER,False,False
|
||||||
|
URL,False,False
|
||||||
|
VCPU,False,False
|
||||||
|
VISIBLE,False,False
|
||||||
|
WINDOW,False,False
|
||||||
|
ZONE,False,False
|
|
0
src/harlequin_mysql/py.typed
Normal file
0
src/harlequin_mysql/py.typed
Normal file
43
tests/conftest.py
Normal file
43
tests/conftest.py
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Generator
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from mysql.connector import connect
|
||||||
|
|
||||||
|
from harlequin_mysql.adapter import (
|
||||||
|
HarlequinMySQLAdapter,
|
||||||
|
HarlequinMySQLConnection,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def connection() -> Generator[HarlequinMySQLConnection, None, None]:
|
||||||
|
mysqlconn = connect(
|
||||||
|
host="localhost",
|
||||||
|
user="root",
|
||||||
|
password="example",
|
||||||
|
database="mysql",
|
||||||
|
autocommit=True,
|
||||||
|
)
|
||||||
|
cur = mysqlconn.cursor()
|
||||||
|
cur.execute("drop database if exists test;")
|
||||||
|
cur.execute("drop database if exists one;")
|
||||||
|
cur.execute("drop database if exists two;")
|
||||||
|
cur.execute("drop database if exists three;")
|
||||||
|
cur.execute("create database test;")
|
||||||
|
cur.close()
|
||||||
|
conn = HarlequinMySQLAdapter(
|
||||||
|
conn_str=tuple(),
|
||||||
|
host="localhost",
|
||||||
|
user="root",
|
||||||
|
password="example",
|
||||||
|
database="test",
|
||||||
|
).connect()
|
||||||
|
yield conn
|
||||||
|
cur = mysqlconn.cursor()
|
||||||
|
cur.execute("drop database if exists test;")
|
||||||
|
cur.execute("drop database if exists one;")
|
||||||
|
cur.execute("drop database if exists two;")
|
||||||
|
cur.execute("drop database if exists three;")
|
||||||
|
cur.close()
|
220
tests/test_adapter.py
Normal file
220
tests/test_adapter.py
Normal file
|
@ -0,0 +1,220 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from harlequin import (
|
||||||
|
HarlequinAdapter,
|
||||||
|
HarlequinCompletion,
|
||||||
|
HarlequinConnection,
|
||||||
|
HarlequinCursor,
|
||||||
|
)
|
||||||
|
from harlequin.catalog import Catalog, CatalogItem
|
||||||
|
from harlequin.exception import HarlequinConnectionError, HarlequinQueryError
|
||||||
|
from mysql.connector.cursor import MySQLCursor
|
||||||
|
from mysql.connector.pooling import PooledMySQLConnection
|
||||||
|
from textual_fastdatatable.backend import create_backend
|
||||||
|
|
||||||
|
from harlequin_mysql.adapter import (
|
||||||
|
HarlequinMySQLAdapter,
|
||||||
|
HarlequinMySQLConnection,
|
||||||
|
)
|
||||||
|
|
||||||
|
if sys.version_info < (3, 10):
|
||||||
|
from importlib_metadata import entry_points
|
||||||
|
else:
|
||||||
|
from importlib.metadata import entry_points
|
||||||
|
|
||||||
|
|
||||||
|
def test_plugin_discovery() -> None:
|
||||||
|
PLUGIN_NAME = "mysql"
|
||||||
|
eps = entry_points(group="harlequin.adapter")
|
||||||
|
assert eps[PLUGIN_NAME]
|
||||||
|
adapter_cls = eps[PLUGIN_NAME].load()
|
||||||
|
assert issubclass(adapter_cls, HarlequinAdapter)
|
||||||
|
assert adapter_cls == HarlequinMySQLAdapter
|
||||||
|
|
||||||
|
|
||||||
|
def test_connect() -> None:
|
||||||
|
conn = HarlequinMySQLAdapter(
|
||||||
|
conn_str=tuple(), user="root", password="example"
|
||||||
|
).connect()
|
||||||
|
assert isinstance(conn, HarlequinConnection)
|
||||||
|
|
||||||
|
|
||||||
|
def test_init_extra_kwargs() -> None:
|
||||||
|
assert HarlequinMySQLAdapter(
|
||||||
|
conn_str=tuple(), user="root", password="example", foo=1, bar="baz"
|
||||||
|
).connect()
|
||||||
|
|
||||||
|
|
||||||
|
def test_connect_raises_connection_error() -> None:
|
||||||
|
with pytest.raises(HarlequinConnectionError):
|
||||||
|
_ = HarlequinMySQLAdapter(conn_str=("foo",)).connect()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"options,expected",
|
||||||
|
[
|
||||||
|
({}, "127.0.0.1:3306/"),
|
||||||
|
({"host": "foo.bar"}, "foo.bar:3306/"),
|
||||||
|
({"host": "foo.bar", "port": "3305"}, "foo.bar:3305/"),
|
||||||
|
({"unix_socket": "/foo/bar"}, "/foo/bar:3306/"),
|
||||||
|
({"unix_socket": "/foo/bar", "database": "baz"}, "/foo/bar:3306/baz"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_connection_id(options: dict[str, str | int | None], expected: str) -> None:
|
||||||
|
adapter = HarlequinMySQLAdapter(
|
||||||
|
conn_str=tuple(),
|
||||||
|
**options, # type: ignore[arg-type]
|
||||||
|
)
|
||||||
|
assert adapter.connection_id == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_catalog(connection: HarlequinMySQLConnection) -> None:
|
||||||
|
catalog = connection.get_catalog()
|
||||||
|
assert isinstance(catalog, Catalog)
|
||||||
|
assert catalog.items
|
||||||
|
assert isinstance(catalog.items[0], CatalogItem)
|
||||||
|
assert any(
|
||||||
|
item.label == "test" and item.type_label == "db" for item in catalog.items
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_completions(connection: HarlequinMySQLConnection) -> None:
|
||||||
|
completions = connection.get_completions()
|
||||||
|
assert completions
|
||||||
|
assert isinstance(completions[0], HarlequinCompletion)
|
||||||
|
expected = ["action", "var_pop"]
|
||||||
|
filtered = list(filter(lambda x: x.label in expected, completions))
|
||||||
|
assert len(filtered) == len(expected)
|
||||||
|
|
||||||
|
|
||||||
|
def test_execute_ddl(connection: HarlequinMySQLConnection) -> None:
|
||||||
|
cur = connection.execute("create table foo (a int)")
|
||||||
|
assert cur is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_execute_select(connection: HarlequinMySQLConnection) -> 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_no_records(connection: HarlequinMySQLConnection) -> None:
|
||||||
|
cur = connection.execute("select 1 as a where false")
|
||||||
|
assert isinstance(cur, HarlequinCursor)
|
||||||
|
assert cur.columns() == [("a", "##")]
|
||||||
|
data = cur.fetchall()
|
||||||
|
backend = create_backend(data)
|
||||||
|
assert backend.row_count == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_execute_select_dupe_cols(connection: HarlequinMySQLConnection) -> 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: HarlequinMySQLConnection) -> 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: HarlequinMySQLConnection) -> None:
|
||||||
|
with pytest.raises(HarlequinQueryError):
|
||||||
|
_ = connection.execute("selec;")
|
||||||
|
|
||||||
|
|
||||||
|
def test_can_execute_pool_size_queries(connection: HarlequinMySQLConnection) -> None:
|
||||||
|
pool_size = connection._pool.pool_size
|
||||||
|
cursors: list[HarlequinCursor] = []
|
||||||
|
for _ in range(pool_size):
|
||||||
|
cur = connection.execute("select 1")
|
||||||
|
assert cur is not None
|
||||||
|
cursors.append(cur)
|
||||||
|
assert len(cursors) == pool_size
|
||||||
|
|
||||||
|
|
||||||
|
def test_can_execute_pool_size_ddl(connection: HarlequinMySQLConnection) -> None:
|
||||||
|
pool_size = connection._pool.pool_size
|
||||||
|
cursors: list[None] = []
|
||||||
|
for i in range(pool_size):
|
||||||
|
cur = connection.execute(f"create table t_{i} as select {i}")
|
||||||
|
assert cur is None
|
||||||
|
cursors.append(cur)
|
||||||
|
assert len(cursors) == pool_size
|
||||||
|
|
||||||
|
|
||||||
|
def test_execute_more_than_pool_size_queries_does_not_raise(
|
||||||
|
connection: HarlequinMySQLConnection,
|
||||||
|
) -> None:
|
||||||
|
pool_size = connection._pool.pool_size
|
||||||
|
cursors: list[HarlequinCursor] = []
|
||||||
|
for _ in range(pool_size * 2):
|
||||||
|
cur = connection.execute("select 1")
|
||||||
|
if cur is not None:
|
||||||
|
cursors.append(cur)
|
||||||
|
assert len(cursors) == pool_size
|
||||||
|
|
||||||
|
|
||||||
|
def test_execute_more_than_pool_size_ddl_does_not_raise(
|
||||||
|
connection: HarlequinMySQLConnection,
|
||||||
|
) -> None:
|
||||||
|
pool_size = connection._pool.pool_size
|
||||||
|
number_of_ddl_queries = pool_size * 2
|
||||||
|
cursors: list[None] = []
|
||||||
|
for i in range(number_of_ddl_queries):
|
||||||
|
cur = connection.execute(f"create table t_{i} as select {i}")
|
||||||
|
assert cur is None
|
||||||
|
cursors.append(cur)
|
||||||
|
assert len(cursors) == number_of_ddl_queries
|
||||||
|
|
||||||
|
|
||||||
|
def test_use_database_updates_pool(connection: HarlequinMySQLConnection) -> None:
|
||||||
|
conn, cur = connection.safe_get_mysql_cursor()
|
||||||
|
assert conn is not None
|
||||||
|
assert cur is not None
|
||||||
|
assert conn.database == "test"
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
connection.execute("use mysql")
|
||||||
|
|
||||||
|
pool_size = connection._pool.pool_size
|
||||||
|
|
||||||
|
conns: list[PooledMySQLConnection] = []
|
||||||
|
curs: list[MySQLCursor] = []
|
||||||
|
for _ in range(pool_size):
|
||||||
|
conn, cur = connection.safe_get_mysql_cursor()
|
||||||
|
assert conn is not None
|
||||||
|
assert cur is not None
|
||||||
|
assert conn.database == "mysql"
|
||||||
|
conns.append(conn)
|
||||||
|
curs.append(cur)
|
||||||
|
|
||||||
|
assert len(conns) == pool_size
|
||||||
|
for cur in curs:
|
||||||
|
cur.close()
|
||||||
|
for conn in conns:
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
def test_close(connection: HarlequinMySQLConnection) -> None:
|
||||||
|
connection.close()
|
||||||
|
# run again to test error handling.
|
||||||
|
connection.close()
|
85
tests/test_catalog.py
Normal file
85
tests/test_catalog.py
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from harlequin_mysql.adapter import HarlequinMySQLConnection
|
||||||
|
from harlequin_mysql.catalog import (
|
||||||
|
ColumnCatalogItem,
|
||||||
|
DatabaseCatalogItem,
|
||||||
|
RelationCatalogItem,
|
||||||
|
TableCatalogItem,
|
||||||
|
ViewCatalogItem,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def connection_with_objects(
|
||||||
|
connection: HarlequinMySQLConnection,
|
||||||
|
) -> HarlequinMySQLConnection:
|
||||||
|
connection.execute("create database 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 database two")
|
||||||
|
connection.execute("create view two.qux as select * from one.foo")
|
||||||
|
connection.execute("create database three")
|
||||||
|
# the original connection fixture will clean this up.
|
||||||
|
return connection
|
||||||
|
|
||||||
|
|
||||||
|
def test_catalog(connection_with_objects: HarlequinMySQLConnection) -> None:
|
||||||
|
conn = connection_with_objects
|
||||||
|
|
||||||
|
catalog = conn.get_catalog()
|
||||||
|
|
||||||
|
# five databases: dev, test, one, two, and three.
|
||||||
|
assert len(catalog.items) == 5
|
||||||
|
|
||||||
|
database_items = catalog.items
|
||||||
|
assert all(isinstance(item, DatabaseCatalogItem) for item in database_items)
|
||||||
|
|
||||||
|
[database_one_item] = filter(lambda item: item.label == "one", database_items)
|
||||||
|
assert isinstance(database_one_item, DatabaseCatalogItem)
|
||||||
|
assert not database_one_item.children
|
||||||
|
assert not database_one_item.loaded
|
||||||
|
|
||||||
|
table_items = database_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)
|
||||||
|
|
||||||
|
[database_two_item] = filter(lambda item: item.label == "two", database_items)
|
||||||
|
assert isinstance(database_two_item, DatabaseCatalogItem)
|
||||||
|
assert not database_two_item.children
|
||||||
|
assert not database_two_item.loaded
|
||||||
|
|
||||||
|
view_items = database_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
|
||||||
|
|
||||||
|
[database_three_item] = filter(lambda item: item.label == "three", database_items)
|
||||||
|
assert isinstance(database_three_item, DatabaseCatalogItem)
|
||||||
|
assert not database_three_item.children
|
||||||
|
assert not database_three_item.loaded
|
||||||
|
|
||||||
|
three_children = database_three_item.fetch_children()
|
||||||
|
assert not three_children
|
Loading…
Add table
Add a link
Reference in a new issue