diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9aae4d8..0d66089 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,42 +1,270 @@ name: CI +permissions: + contents: write + on: push: - branches: [ main, master ] + branches: + - "**" pull_request: - branches: [ main, master ] + branches: + - main + +env: + PYTHON_VERSION: "3.14" + PIP_CACHE_DIR: /home/runner/.cache/pip + UV_CACHE_DIR: /home/runner/.cache/uv + WHEEL_CACHE_DIR: /home/runner/.cache/wheels + PIP_FIND_LINKS: /home/runner/.cache/wheels jobs: - pre-commit: + test: + if: github.event_name == 'pull_request' || (github.event_name == 'push' && github.ref != 'refs/heads/main' && github.ref != 'refs/heads/nightly') + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v4 + with: + version: latest + enable-cache: true + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Install wx build deps + run: | + sudo apt-get update + sudo apt-get install -y \ + build-essential pkg-config gettext \ + libgtk-3-dev libglib2.0-dev \ + libjpeg-dev libpng-dev libtiff-dev libtiff6 \ + libnotify-dev libsm-dev \ + libsdl2-dev \ + libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \ + libglu1-mesa-dev freeglut3-dev \ + libx11-dev libxext-dev libxinerama-dev libxi-dev libxrandr-dev \ + libxss-dev libxtst-dev xvfb + + if ! sudo apt-get install -y libwebkit2gtk-4.0-dev; then + sudo apt-get install -y libwebkit2gtk-4.1-dev + fi + + - name: Cache pip + uses: actions/cache@v4 + with: + path: ${{ env.PIP_CACHE_DIR }} + key: pip-${{ runner.os }}-py${{ env.PYTHON_VERSION }}-${{ hashFiles('**/uv.lock', '**/pyproject.toml') }} + restore-keys: | + pip-${{ runner.os }}-py${{ env.PYTHON_VERSION }}- + + - name: Cache wheelhouse + uses: actions/cache@v4 + with: + path: ${{ env.WHEEL_CACHE_DIR }} + key: wheel-${{ runner.os }}-py${{ env.PYTHON_VERSION }}-${{ hashFiles('**/uv.lock', '**/pyproject.toml') }} + restore-keys: | + wheel-${{ runner.os }}-py${{ env.PYTHON_VERSION }}- + + - name: Initialize venv UV + run: uv venv + + - name: Export requirements from uv.lock (include dev extra) + run: uv export --format requirements-txt --locked --no-hashes --extra dev -o requirements.lock.txt + + - name: Check wheelhouse status + id: wheelhouse_check + run: | + mkdir -p "${WHEEL_CACHE_DIR}" + + if ls "${WHEEL_CACHE_DIR}"/wxPython*.whl >/dev/null 2>&1; then + echo "wxPython wheel found in cache, skipping wheel build" + echo "needs_build=false" >> "$GITHUB_OUTPUT" + else + echo "wxPython wheel missing, wheelhouse build required" + echo "needs_build=true" >> "$GITHUB_OUTPUT" + fi + + - name: Build wheelhouse from lock + if: steps.wheelhouse_check.outputs.needs_build == 'true' + run: | + mkdir -p "${WHEEL_CACHE_DIR}" + python -m pip wheel -w "${WHEEL_CACHE_DIR}" -r requirements.lock.txt + + - name: Upgrade build tooling + run: uv pip install -U pip setuptools wheel + + - name: Install dependencies from wheelhouse (fallback on build) + run: | + if uv sync --extra dev --find-links "${WHEEL_CACHE_DIR}" --no-build; then + echo "Dependencies installed from wheelhouse without builds" + else + echo "Wheel-only install failed, falling back to normal uv sync" + uv sync --extra dev --find-links "${WHEEL_CACHE_DIR}" + fi + + - name: Run tests (--all) + run: xvfb-run -a ./scripts/runtest.sh --all + + update: + if: github.event_name == 'push' && github.ref == 'refs/heads/nightly' + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v4 + with: + version: latest + enable-cache: true + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Install wx build deps + run: | + sudo apt-get update + sudo apt-get install -y \ + build-essential pkg-config gettext \ + libgtk-3-dev libglib2.0-dev \ + libjpeg-dev libpng-dev libtiff-dev libtiff6 \ + libnotify-dev libsm-dev \ + libsdl2-dev \ + libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \ + libglu1-mesa-dev freeglut3-dev \ + libx11-dev libxext-dev libxinerama-dev libxi-dev libxrandr-dev \ + libxss-dev libxtst-dev xvfb + + if ! sudo apt-get install -y libwebkit2gtk-4.0-dev; then + sudo apt-get install -y libwebkit2gtk-4.1-dev + fi + + - name: Cache pip + uses: actions/cache@v4 + with: + path: ${{ env.PIP_CACHE_DIR }} + key: pip-${{ runner.os }}-py${{ env.PYTHON_VERSION }}-${{ hashFiles('**/uv.lock', '**/pyproject.toml') }} + restore-keys: | + pip-${{ runner.os }}-py${{ env.PYTHON_VERSION }}- + + - name: Cache wheelhouse + uses: actions/cache@v4 + with: + path: ${{ env.WHEEL_CACHE_DIR }} + key: wheel-${{ runner.os }}-py${{ env.PYTHON_VERSION }}-${{ hashFiles('**/uv.lock', '**/pyproject.toml') }} + restore-keys: | + wheel-${{ runner.os }}-py${{ env.PYTHON_VERSION }}- + + - name: Initialize venv UV + run: uv venv + + - name: Export requirements from uv.lock (include dev extra) + run: uv export --format requirements-txt --locked --no-hashes --extra dev -o requirements.lock.txt + + - name: Check wheelhouse status + id: wheelhouse_check + run: | + mkdir -p "${WHEEL_CACHE_DIR}" + + if ls "${WHEEL_CACHE_DIR}"/wxPython*.whl >/dev/null 2>&1; then + echo "wxPython wheel found in cache, skipping wheel build" + echo "needs_build=false" >> "$GITHUB_OUTPUT" + else + echo "wxPython wheel missing, wheelhouse build required" + echo "needs_build=true" >> "$GITHUB_OUTPUT" + fi + + - name: Build wheelhouse from lock + if: steps.wheelhouse_check.outputs.needs_build == 'true' + run: | + mkdir -p "${WHEEL_CACHE_DIR}" + python -m pip wheel -w "${WHEEL_CACHE_DIR}" -r requirements.lock.txt + + - name: Upgrade build tooling + run: uv pip install -U pip setuptools wheel + + - name: Install dependencies from wheelhouse (fallback on build) + run: | + if uv sync --extra dev --find-links "${WHEEL_CACHE_DIR}" --no-build; then + echo "Dependencies installed from wheelhouse without builds" + else + echo "Wheel-only install failed, falling back to normal uv sync" + uv sync --extra dev --find-links "${WHEEL_CACHE_DIR}" + fi + + - name: Run tests and update README (--update) + run: xvfb-run -a ./scripts/runtest.sh --update + + - name: Commit and push updated README + run: | + git config --global user.name "github-actions" + git config --global user.email "github-actions@github.com" + + git add README.md + + if git diff --cached --quiet; then + echo "No README changes to commit" + else + git commit -m "chore: update badges [skip ci]" + git push + fi + + - name: Build (placeholder) + run: echo "Build scripts are not ready yet." + + release: + if: github.event_name == 'push' && github.ref == 'refs/heads/main' runs-on: ubuntu-latest - container: ubuntu:22.04 - + steps: - - uses: actions/checkout@v4 - - - name: Install system dependencies - run: | - apt-get update - apt-get install -y python3 python3-pip curl pkg-config git pkg-config libgtk-3-dev libwebkit2gtk-4.0-dev nodejs npm - - - name: Install uv - run: | - curl -LsSf https://astral.sh/uv/install.sh | sh - echo "$HOME/.local/bin" >> $GITHUB_PATH - echo "$HOME/.cargo/bin" >> $GITHUB_PATH - - - name: Set up cache - uses: actions/cache@v3 - with: - path: ~/.cache/uv - key: ${{ runner.os }}-uv-${{ hashFiles('**/uv.lock') }} - restore-keys: | - ${{ runner.os }}-uv- - - - name: Install dependencies - run: | - uv sync --dev - - - name: Run pre-commit - run: | - uv run pre-commit run --all-files \ No newline at end of file + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Build (placeholder) + run: echo "Build scripts are not ready yet." + + - name: Read version from pyproject.toml + id: version + run: | + version=$(python -c "import tomllib; from pathlib import Path; data = tomllib.loads(Path('pyproject.toml').read_text()); project = data.get('project', {}); version = project.get('version', '').strip(); print(version) if version else (_ for _ in ()).throw(SystemExit('Missing project.version in pyproject.toml'))") + echo "version=${version}" >> "$GITHUB_OUTPUT" + + - name: Create and push tag + env: + VERSION: ${{ steps.version.outputs.version }} + run: | + tag="v${VERSION}" + + if git rev-parse "$tag" >/dev/null 2>&1; then + echo "Tag $tag already exists" + else + git tag "$tag" + git push origin "$tag" + fi + + - name: Create published release with autogenerated notes + env: + GH_TOKEN: ${{ github.token }} + VERSION: ${{ steps.version.outputs.version }} + run: | + tag="v${VERSION}" + + if gh release view "$tag" >/dev/null 2>&1; then + echo "Release $tag already exists" + else + gh release create "$tag" --generate-notes --title "$tag" + fi diff --git a/.gitignore b/.gitignore index 470faee..55cfb84 100755 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,16 @@ __pycache__/ *.swp *.log *.yml - .coverage \ No newline at end of file + .coverage +# Backup files +*.bak +*.bkp +*.backup + +# Unready assets +petersql_hat.xcf +petersql_hat.png +PeterSQL.png + +# Unready build scripts +scripts/build/nix/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2de1d21..5bbcb03 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,7 +14,7 @@ repos: hooks: - id: runtest name: run tests and update badges - entry: bash ./scripts/runtest.sh + entry: bash ./scripts/runtest-local.sh language: system pass_filenames: false always_run: true diff --git a/CODE_STYLE.md b/CODE_STYLE.md index aef0bb1..5189a1f 100644 --- a/CODE_STYLE.md +++ b/CODE_STYLE.md @@ -1,4 +1,4 @@ -# Code Style Guidelines (v1.2) +# Code Style Guidelines (v1.4) These rules define the expected coding style for this project. They apply to all contributors, including humans, AI-assisted tools, and automated systems. @@ -27,7 +27,21 @@ They are mandatory unless explicitly stated otherwise. --- -## 1. Comments +## 1. Language + +- All code, comments, documentation, commit messages, and free-form text MUST be written in English. +- This includes: + - Comments in code + - Docstrings + - Documentation files (README, guides, etc.) + - Commit messages + - Variable and function names + - Error messages and user-facing text +- No exceptions are allowed. + +--- + +## 2. Comments - Comments MUST be written in English. - Comments MUST be concise and non-verbose. @@ -49,7 +63,15 @@ They are mandatory unless explicitly stated otherwise. --- -## 2. Naming Conventions +## 3. Commit Messages + +- Commit messages MUST be concise and non-verbose. +- They serve as a brief summary of changes. Detailed explanations belong in the merge request description. +- The merge request description MUST be comprehensive and well-written. + +--- + +## 4. Naming Conventions - Variable, attribute, class, function, and parameter names MUST be descriptive. - Names MUST NOT be aggressively shortened. @@ -82,7 +104,7 @@ self.par = par --- -## 3. Python Typing +## 5. Python Typing This project targets Python 3.14 and uses PEP 585 generics for standard collections. @@ -174,7 +196,7 @@ if TYPE_CHECKING: from pkg.heavy import HeavyType # TYPE_CHECKING: unavoidable circular import ``` -## 4. Import Rules +## 6. Import Rules ### Submodules vs symbols (`from ... import ...`) @@ -384,7 +406,7 @@ When importing multiple symbols from the same module: - each line MUST import as many symbols as possible - keep the same import group ordering rules -### Good examples +#### Good examples ```python from windows.components.stc.detectors import detect_syntax_id, is_base64, is_csv @@ -397,7 +419,7 @@ from .detectors import is_regex, is_sql, is_xml from .detectors import is_base64, is_csv, is_html ``` -### Bad examples +#### Bad examples ```python from windows.components.stc.detectors import is_html @@ -417,9 +439,46 @@ from .detectors import ( ) ``` +### Lazy Imports + +- Lazy imports (imports inside functions or methods) MUST NOT be used. +- Lazy imports are allowed ONLY as a last resort when: + - There is an unavoidable circular import that cannot be resolved by refactoring + - The performance gain is critical and measurable (e.g., avoiding expensive module initialization) +- When lazy imports are used, they MUST include a clear inline comment explaining why they are necessary. + +#### Good examples + +```python +from windows.main import CURRENT_CONNECTION + + +def get_dialect() -> str: + connection = CURRENT_CONNECTION.get_value() + return connection.engine.value.dialect +``` + +#### Bad examples + +```python +def get_dialect() -> str: + from windows.main import CURRENT_CONNECTION # Lazy import without justification + connection = CURRENT_CONNECTION.get_value() + return connection.engine.value.dialect +``` + +#### Allowed (last resort) + +```python +def get_dialect() -> str: + from windows.main import CURRENT_CONNECTION # Lazy import: unavoidable circular dependency + connection = CURRENT_CONNECTION.get_value() + return connection.engine.value.dialect +``` + --- -## 5. Variable Definition Order +## 7. Variable Definition Order When defining multiple variables in sequence, variables MUST be ordered by increasing number of characters in the variable name (shorter names first). @@ -445,7 +504,7 @@ pos = self._editor.GetCurrentPos() --- -## 6. Python Classes +## 8. Python Classes ### Naming @@ -518,19 +577,19 @@ class Example: --- -## 7. Function and Method Size +## 9. Function and Method Size - A function/method MUST be at most 50 lines. - If it exceeds 50 lines, it MUST be split into smaller functions/methods with clear names. --- -## 8. Walrus Operator ( := ) +## 10. Walrus Operator ( := ) - Always try to use the walrus operator when it improves clarity and avoids redundant calls. - Do NOT use it if it reduces readability. -### Good examples +#### Good examples ```python if (user := get_user()) is not None: @@ -540,7 +599,7 @@ while (line := file.readline()): handle_line(line) ``` -### Bad examples +#### Bad examples ```python user = get_user() @@ -550,7 +609,7 @@ if user is not None: --- -## 9. Mypy & Static Analysis +## 11. Mypy & Static Analysis - Code MUST be mypy-friendly. - Do NOT silence errors with `# type: ignore` unless there is no reasonable alternative. diff --git a/ENGINES.md b/ENGINES.md new file mode 100644 index 0000000..e499f93 --- /dev/null +++ b/ENGINES.md @@ -0,0 +1,21 @@ +# Engine Specifications + +This project stores SQL autocomplete vocabulary in normalized engine specifications under `structures/engines/`. + +## How Version Deltas Work + +The specification model uses a **base + delta** strategy: + +- `common.functions` and `common.keywords` contain the shared baseline for that engine. +- `versions..functions_remove` and `versions..keywords_remove` remove entries that are not valid for an older major version. + +We intentionally keep newer capabilities in `common` and apply only removals for older majors. + +## Version Resolution Rule + +At runtime, vocabulary resolution uses: + +1. exact major match when available; +2. otherwise, the highest configured major version `<=` the server major. + +Example: if PostgreSQL server major is `19` and the highest configured major is `18`, version `18` is used. diff --git a/PROJECT_STATUS.md b/PROJECT_STATUS.md index 96504b4..c2efc22 100644 --- a/PROJECT_STATUS.md +++ b/PROJECT_STATUS.md @@ -1,44 +1,40 @@ # PeterSQL — Project Status -> **Last Updated:** 2026-02-11 -> **Version:** Based on code inspection +> **Last Updated:** 2026-03-07 +> **Validation Policy:** new engine features are marked **PARTIAL** until broader integration validation is complete. --- ## 1. Executive Summary -### ✅ What is Solid and Complete +### ✅ Solid and Stable Areas | Area | Status | |------|--------| -| **SQLite Engine** | Most mature. Full CRUD for Table, Column, Index, Foreign Key, Record, View, Trigger. Check constraints supported. | -| **MySQL/MariaDB Engines** | Strong parity. Full CRUD for Table, Column, Index, Foreign Key, Record, View, Trigger, Function. | -| **Connection Management** | SSH tunnel support (MySQL, MariaDB, PostgreSQL), session lifecycle, multi-database navigation. | -| **UI Explorer** | Tree navigation for databases, tables, views, triggers, procedures, functions, events. | -| **Table Editor** | Column editor, index editor, foreign key editor with full CRUD. | -| **Record Editor** | Insert, update, delete, duplicate records with filtering support. | -| **SQL Autocomplete** | Keywords, functions, table/column names. | -| **SSH Tunnel Testing** | Comprehensive test coverage for MySQL, MariaDB, and PostgreSQL SSH tunnel functionality. | - -### ⚠️ Partially Implemented (Risky) - -| Area | Issue | -|------|-------| -| **PostgreSQL Engine** | Schema support incomplete. No Function class. | -| **Procedure/Event UI** | Explorer shows them but no editor panels exist. | +| **SQLite Engine** | Most mature path with complete day-to-day table/record workflows. | +| **MySQL/MariaDB Core** | Strong parity for tables, columns, indexes, foreign keys, records, views, triggers, functions. | +| **UI Core Editors** | Table, columns, indexes, foreign keys, records, and view editor are operational. | +| **Explorer Navigation** | Databases, tables, views, triggers, procedures, functions, and events are visible in tree explorer. | +| **SSH Tunnel Support** | Implemented for MySQL, MariaDB, and PostgreSQL. | + +### 🟡 Partial / Under Validation -### ❌ Completely Missing +| Area | Current State | +|------|---------------| +| **PostgreSQL Function** | Class + CRUD methods exist, context introspection exists, still considered under validation. | +| **PostgreSQL Procedure** | Class + CRUD methods exist, context introspection exists, still considered under validation. | +| **Check Constraints (MySQL/MariaDB/PostgreSQL)** | Engine classes and introspection exist, cross-version validation still needed. | +| **Connection Reliability Features** | Persistent connection statistics, empty DB password support, and TLS auto-retry are implemented and need longer real-world validation. | + +### ❌ Missing / Not Implemented | Area | Notes | |------|-------| -| **Schema/Namespace Management** | PostgreSQL schemas visible but no CRUD. | -| **User/Role Management** | Not implemented for any engine. | -| **Privileges/Grants** | Not implemented. | -| **Sequences** | Not implemented (PostgreSQL). | -| **Materialized Views** | Not implemented. | -| **Partitioning** | Not implemented. | -| **Import/Export/Dump** | Not implemented. | -| **Database Create/Drop** | Not implemented in UI. | +| **Function/Procedure UI Editors** | Explorer lists objects, but dedicated create/edit UI is still missing. | +| **Database Create/Drop UI** | No complete create/drop workflow across engines. | +| **Schema/Sequence Management** | PostgreSQL schema/sequence CRUD is not available. | +| **User/Role/Grants** | Not implemented for any engine. | +| **Import/Export** | Dump/restore and structured data import/export not implemented. | --- @@ -48,267 +44,131 @@ | Symbol | Meaning | |--------|---------| -| ✅ DONE | Fully implemented and tested | -| 🟡 PARTIAL | Implemented but incomplete or has issues | +| ✅ DONE | Implemented and validated in current project scope | +| 🟡 PARTIAL | Implemented but still under validation / known risk | | ❌ NOT IMPL | Not implemented | -| ➖ N/A | Not applicable to this engine | +| ➖ N/A | Not applicable | --- ### 2.1 SQLite -| Object Type | Create | Read | Update | Delete | Notes | Evidence | -|-------------|--------|------|--------|--------|-------|----------| -| **Database** | ➖ | ✅ | ➖ | ➖ | Single file = single DB | `SQLiteContext.get_databases()` | -| **Table** | ✅ | ✅ | ✅ | ✅ | Full recreate strategy for ALTER | `SQLiteTable.create/alter/drop()` | -| **Column** | ✅ | ✅ | ✅ | ✅ | Via table recreate | `SQLiteColumn.add/modify/rename/drop()` | -| **Index** | ✅ | ✅ | ✅ | ✅ | PRIMARY handled in table | `SQLiteIndex.create/drop/modify()` | -| **Primary Key** | ✅ | ✅ | ✅ | ✅ | Inline or table constraint | `SQLiteTable.raw_create()` | -| **Foreign Key** | ✅ | ✅ | ✅ | ✅ | Table-level constraints | `SQLiteForeignKey` (passive) | -| **Unique Constraint** | ✅ | ✅ | ✅ | ✅ | Via CREATE UNIQUE INDEX | `SQLiteIndex` | -| **Check Constraint** | ✅ | ✅ | 🟡 | 🟡 | Read works, modify via recreate | `SQLiteCheck`, `get_checks()` | -| **Default** | ✅ | ✅ | ✅ | ✅ | Column attribute | `SQLiteColumn.server_default` | -| **View** | ✅ | ✅ | ✅ | ✅ | `alter()` implemented | `SQLiteView` | -| **Trigger** | ✅ | ✅ | ✅ | ✅ | `alter()` implemented | `SQLiteTrigger` | -| **Function** | ➖ | ➖ | ➖ | ➖ | SQLite has no stored functions | — | -| **Procedure** | ➖ | ➖ | ➖ | ➖ | SQLite has no procedures | — | -| **Records** | ✅ | ✅ | ✅ | ✅ | Full DML | `SQLiteRecord.insert/update/delete()` | -| **Transactions** | ✅ | ➖ | ➖ | ➖ | Context manager | `AbstractContext.transaction()` | -| **Collation** | ✅ | ✅ | ➖ | ➖ | Static list | `COLLATIONS` in `__init__.py` | +| Object | Create | Read | Update | Delete | Notes | +|--------|--------|------|--------|--------|-------| +| Table / Column / Index / FK / Record | ✅ | ✅ | ✅ | ✅ | Most mature engine path. | +| View / Trigger | ✅ | ✅ | ✅ | ✅ | Fully available in engine layer. | +| Check Constraint | ✅ | ✅ | 🟡 | 🟡 | Modify path depends on recreate strategy. | +| Function / Procedure | ➖ | ➖ | ➖ | ➖ | Not applicable to SQLite. | --- ### 2.2 MySQL -| Object Type | Create | Read | Update | Delete | Notes | Evidence | -|-------------|--------|------|--------|--------|-------|----------| -| **Database** | ❌ | ✅ | ❌ | ❌ | Read-only listing | `MySQLContext.get_databases()` | -| **Table** | ✅ | ✅ | ✅ | ✅ | Full support | `MySQLTable` | -| **Column** | ✅ | ✅ | ✅ | ✅ | ADD/MODIFY/RENAME/DROP | `MySQLColumn` | -| **Index** | ✅ | ✅ | ✅ | ✅ | PRIMARY, UNIQUE, INDEX | `MySQLIndex` | -| **Primary Key** | ✅ | ✅ | ✅ | ✅ | Via index | `MySQLIndexType.PRIMARY` | -| **Foreign Key** | ✅ | ✅ | ✅ | ✅ | Full support | `MySQLForeignKey` | -| **Unique Constraint** | ✅ | ✅ | ✅ | ✅ | Via index | `MySQLIndexType.UNIQUE` | -| **Check Constraint** | ❌ | ❌ | ❌ | ❌ | Not implemented | — | -| **Default** | ✅ | ✅ | ✅ | ✅ | Column attribute | `MySQLColumn.server_default` | -| **View** | ✅ | ✅ | ✅ | ✅ | `alter()` implemented | `MySQLView` | -| **Trigger** | ✅ | ✅ | ✅ | ✅ | `alter()` implemented | `MySQLTrigger` | -| **Function** | ✅ | ✅ | ✅ | ✅ | Full support | `MySQLFunction` | -| **Procedure** | ❌ | ❌ | ❌ | ❌ | Class exists but empty | `SQLProcedure` base only | -| **Records** | ✅ | ✅ | ✅ | ✅ | Full DML | `MySQLRecord` | -| **Transactions** | ✅ | ➖ | ➖ | ➖ | Context manager | `AbstractContext.transaction()` | -| **Collation** | ✅ | ✅ | ➖ | ➖ | Dynamic from server | `_on_connect()` | -| **Engine** | ✅ | ✅ | ✅ | ➖ | InnoDB, MyISAM, etc. | `MySQLTable.alter_engine()` | +| Object | Create | Read | Update | Delete | Notes | +|--------|--------|------|--------|--------|-------| +| Table / Column / Index / FK / Record | ✅ | ✅ | ✅ | ✅ | Stable core workflow. | +| View / Trigger / Function | ✅ | ✅ | ✅ | ✅ | Implemented in engine layer. | +| Check Constraint | 🟡 | 🟡 | 🟡 | 🟡 | Implemented (`MySQLCheck` + `get_checks()`), validation ongoing. | +| Procedure | ❌ | ❌ | ❌ | ❌ | `build_empty_procedure` still not implemented. | +| Database Create/Drop | ❌ | ✅ | ❌ | ❌ | Read-only listing in context. | --- ### 2.3 MariaDB -| Object Type | Create | Read | Update | Delete | Notes | Evidence | -|-------------|--------|------|--------|--------|-------|----------| -| **Database** | ❌ | ✅ | ❌ | ❌ | Read-only listing | `MariaDBContext.get_databases()` | -| **Table** | ✅ | ✅ | ✅ | ✅ | Full support | `MariaDBTable` | -| **Column** | ✅ | ✅ | ✅ | ✅ | ADD/MODIFY/CHANGE/DROP | `MariaDBColumn` | -| **Index** | ✅ | ✅ | ✅ | ✅ | PRIMARY, UNIQUE, INDEX | `MariaDBIndex` | -| **Primary Key** | ✅ | ✅ | ✅ | ✅ | Via index | `MariaDBIndexType.PRIMARY` | -| **Foreign Key** | ✅ | ✅ | ✅ | ✅ | Full support | `MariaDBForeignKey` | -| **Unique Constraint** | ✅ | ✅ | ✅ | ✅ | Via index | `MariaDBIndexType.UNIQUE` | -| **Check Constraint** | ❌ | ❌ | ❌ | ❌ | Not implemented | — | -| **Default** | ✅ | ✅ | ✅ | ✅ | Column attribute | `MariaDBColumn.server_default` | -| **View** | ✅ | ✅ | ✅ | ✅ | `alter()` implemented | `MariaDBView` | -| **Trigger** | ✅ | ✅ | ✅ | ✅ | `alter()` implemented | `MariaDBTrigger` | -| **Function** | ✅ | ✅ | ✅ | ✅ | Full support | `MariaDBFunction` | -| **Procedure** | ❌ | ❌ | ❌ | ❌ | Class exists but empty | `SQLProcedure` base only | -| **Records** | ✅ | ✅ | ✅ | ✅ | Full DML | `MariaDBRecord` | -| **Transactions** | ✅ | ➖ | ➖ | ➖ | Context manager | `AbstractContext.transaction()` | -| **Collation** | ✅ | ✅ | ➖ | ➖ | Dynamic from server | `_on_connect()` | -| **Engine** | ✅ | ✅ | ✅ | ➖ | InnoDB, Aria, etc. | `MariaDBTable.alter_engine()` | +| Object | Create | Read | Update | Delete | Notes | +|--------|--------|------|--------|--------|-------| +| Table / Column / Index / FK / Record | ✅ | ✅ | ✅ | ✅ | Stable core workflow. | +| View / Trigger / Function | ✅ | ✅ | ✅ | ✅ | Implemented in engine layer. | +| Check Constraint | 🟡 | 🟡 | 🟡 | 🟡 | Implemented (`MariaDBCheck` + `get_checks()`), validation ongoing. | +| Procedure | ❌ | ❌ | ❌ | ❌ | `build_empty_procedure` still not implemented. | +| Database Create/Drop | ❌ | ✅ | ❌ | ❌ | Read-only listing in context. | --- ### 2.4 PostgreSQL -| Object Type | Create | Read | Update | Delete | Notes | Evidence | -|-------------|--------|------|--------|--------|-------|----------| -| **Database** | ❌ | ✅ | ❌ | ❌ | Read-only listing | `PostgreSQLContext.get_databases()` | -| **Schema** | ❌ | 🟡 | ❌ | ❌ | Read via table.schema | `PostgreSQLTable.schema` | -| **Table** | ✅ | ✅ | ✅ | ✅ | Full support | `PostgreSQLTable` | -| **Column** | ✅ | ✅ | ✅ | ✅ | Via table alter | `PostgreSQLColumn` (passive) | -| **Index** | ✅ | ✅ | ✅ | ✅ | PRIMARY, UNIQUE, INDEX, BTREE, etc. | `PostgreSQLIndex` | -| **Primary Key** | ✅ | ✅ | ✅ | ✅ | Via index | `PostgreSQLIndexType.PRIMARY` | -| **Foreign Key** | ✅ | ✅ | ✅ | ✅ | Full support | `PostgreSQLForeignKey` | -| **Unique Constraint** | ✅ | ✅ | ✅ | ✅ | Via index | `PostgreSQLIndexType.UNIQUE` | -| **Check Constraint** | ❌ | ❌ | ❌ | ❌ | Not implemented | — | -| **Default** | ✅ | ✅ | 🟡 | 🟡 | Column attribute | `PostgreSQLColumn.server_default` | -| **View** | ✅ | 🟡 | ✅ | ✅ | `alter()` implemented | `PostgreSQLView` | -| **Trigger** | ✅ | ✅ | ✅ | ✅ | Full support | `PostgreSQLTrigger` | -| **Function** | ❌ | ❌ | ❌ | ❌ | Class not implemented | — | -| **Procedure** | ❌ | ❌ | ❌ | ❌ | Not implemented | — | -| **Sequence** | ❌ | ❌ | ❌ | ❌ | Not implemented | — | -| **Records** | ✅ | ✅ | ✅ | ✅ | Uses parameterized queries | `PostgreSQLRecord` | -| **Transactions** | ✅ | ➖ | ➖ | ➖ | Context manager | `AbstractContext.transaction()` | -| **Collation** | ✅ | ✅ | ➖ | ➖ | Dynamic from server | `_on_connect()` | -| **Custom Types/Enum** | ❌ | ✅ | ❌ | ❌ | Read-only introspection | `_load_custom_types()` | -| **Extension** | ❌ | ❌ | ❌ | ❌ | Not implemented | — | +| Object | Create | Read | Update | Delete | Notes | +|--------|--------|------|--------|--------|-------| +| Table / Column / Index / FK / Record | ✅ | ✅ | ✅ | ✅ | Core operations available. | +| View / Trigger | ✅ | ✅ | ✅ | ✅ | Implemented in engine layer. | +| Function | 🟡 | 🟡 | 🟡 | 🟡 | `PostgreSQLFunction` implemented, still under validation. | +| Procedure | 🟡 | 🟡 | 🟡 | 🟡 | `PostgreSQLProcedure` implemented, still under validation. | +| Check Constraint | 🟡 | 🟡 | 🟡 | 🟡 | Implemented (`PostgreSQLCheck` + `get_checks()`), validation ongoing. | +| Schema / Sequence | ❌ | 🟡 | ❌ | ❌ | Basic schema visibility exists; no CRUD layer yet. | --- ## 3. UI Capability Matrix -| Object Type | Explorer | Create UI | Read/List UI | Update UI | Delete UI | Notes | -|-------------|----------|-----------|--------------|-----------|-----------|-------| -| **Connection** | ✅ | ✅ | ✅ | ✅ | ✅ | `ConnectionsManager` | -| **Database** | ✅ | ❌ | ✅ | ❌ | ❌ | List only | -| **Table** | ✅ | ✅ | ✅ | ✅ | ✅ | Full editor | -| **Column** | ✅ | ✅ | ✅ | ✅ | ✅ | `TableColumnsController` | -| **Index** | ✅ | ✅ | ✅ | ✅ | ✅ | `TableIndexController` | -| **Foreign Key** | ✅ | ✅ | ✅ | ✅ | ✅ | `TableForeignKeyController` | -| **Check Constraint** | ✅ | 🟡 | ✅ | 🟡 | 🟡 | `TableCheckController` (SQLite only) | -| **View** | ✅ | ❌ | ✅ | ❌ | ✅ | List + delete only | -| **Trigger** | ✅ | ❌ | ✅ | ❌ | ❌ | List only | -| **Function** | ✅ | ❌ | ❌ | ❌ | ❌ | Explorer shows, no editor | -| **Procedure** | ✅ | ❌ | ❌ | ❌ | ❌ | Explorer shows, no editor | -| **Event** | ✅ | ❌ | ❌ | ❌ | ❌ | Explorer shows, no editor | -| **Records** | ✅ | ✅ | ✅ | ✅ | ✅ | `TableRecordsController` | -| **SQL Query** | ✅ | ✅ | ✅ | ➖ | ➖ | Query editor with autocomplete | - -### UI Feature Support - -| Feature | Status | Evidence | -|---------|--------|----------| -| **Tree Explorer** | ✅ DONE | `TreeExplorerController` | -| **Table Editor** | ✅ DONE | `EditTableModel`, notebook tabs | -| **Column Editor** | ✅ DONE | `TableColumnsController` | -| **Index Editor** | ✅ DONE | `TableIndexController` | -| **Foreign Key Editor** | ✅ DONE | `TableForeignKeyController` | -| **Check Editor** | 🟡 PARTIAL | `TableCheckController` (SQLite) | -| **Record Editor** | ✅ DONE | `TableRecordsController` | -| **SQL Autocomplete** | ✅ DONE | `SQLAutoCompleteController` | -| **Query Log** | ✅ DONE | `sql_query_logs` StyledTextCtrl | -| **DDL Preview** | ✅ DONE | `sql_create_table` with sqlglot | -| **Theme Support** | ✅ DONE | `ThemeManager`, system color change | -| **View Editor** | ❌ NOT IMPL | Panel exists but no create/edit | -| **Trigger Editor** | ❌ NOT IMPL | Panel exists but no create/edit | -| **Function Editor** | ❌ NOT IMPL | No panel | -| **Procedure Editor** | ❌ NOT IMPL | No panel | +| Object Type | Explorer | Create UI | Edit UI | Delete UI | Notes | +|-------------|----------|-----------|---------|-----------|-------| +| Connection | ✅ | ✅ | ✅ | ✅ | Includes connection statistics and TLS state handling. | +| Database | ✅ | ❌ | ❌ | ❌ | Read/list only. | +| Table / Column / Index / Foreign Key | ✅ | ✅ | ✅ | ✅ | Main table editor workflow complete. | +| Check Constraint | ✅ | 🟡 | 🟡 | 🟡 | `TableCheckController` exists; broader multi-engine UX validation pending. | +| View | ✅ | ✅ | ✅ | ✅ | Dedicated view editor is available. | +| Trigger | ✅ | ❌ | ❌ | ❌ | Explorer only; no dedicated trigger editor panel yet. | +| Function | ✅ | ❌ | ❌ | ❌ | Explorer only; no dedicated editor yet. | +| Procedure | ✅ | ❌ | ❌ | ❌ | Explorer only; no dedicated editor yet. | +| Event | ✅ | ❌ | ❌ | ❌ | Explorer only. | +| Records | ✅ | ✅ | ✅ | ✅ | Insert/update/delete/duplicate in table records tab. | --- -## 4. Cross-Cutting Gaps - -### Features Blocked by Missing Introspection +## 4. Cross-Cutting Notes -| Feature | Blocked By | -|---------|------------| -| PostgreSQL Function UI | No `PostgreSQLFunction` class | -| Check Constraint UI (MySQL/MariaDB) | No `get_checks()` implementation | -| Sequence management | No `SQLSequence` class | -| Schema management | No `SQLSchema` class | +### Recently Added -### Engine Inconsistencies +- Persistent connection statistics in connection model and dialog. +- Empty database password accepted in connection validation. +- Automatic TLS retry path for MySQL/MariaDB when server requires TLS. +- CI workflow split into `test`, `update` (nightly), and `release` jobs. -| Issue | Engines Affected | -|-------|------------------| -| Check constraints | MySQL, MariaDB, PostgreSQL missing | +### Main Remaining Risks -### UI Features Waiting on Engine Support - -| UI Feature | Waiting On | -|------------|------------| -| View create/edit dialog | UI implementation needed | -| Trigger create/edit dialog | UI implementation needed | -| Function editor | PostgreSQL `PostgreSQLFunction` class | -| Database create/drop | All engines need `create_database()` | +- Newly implemented PostgreSQL Function/Procedure paths need broader integration validation. +- Check constraints across MySQL/MariaDB/PostgreSQL need more cross-version coverage. +- UI parity lags engine parity for Trigger/Function/Procedure editors. --- -## 5. Actionable Backlog +## 5. Actionable Backlog (High Signal) -### Priority 1: Critical Fixes +### Priority A — Validate Newly Implemented Features -| Item | Object | Operation | Engine(s) | What's Missing | -|------|--------|-----------|-----------|----------------| -| 1.1 | Function | All | PostgreSQL | `PostgreSQLFunction` class missing | +1. PostgreSQL Function integration validation (all supported PG variants). +2. PostgreSQL Procedure integration validation (all supported PG variants). +3. Check constraints validation matrix for MySQL, MariaDB, PostgreSQL. +4. Connection statistics + TLS auto-retry robustness checks. -### Priority 2: Engine Parity +### Priority B — Close Engine Gaps -| Item | Object | Operation | Engine(s) | What's Missing | -|------|--------|-----------|-----------|----------------| -| 2.1 | Check Constraint | CRUD | MySQL, MariaDB, PostgreSQL | `get_checks()`, `SQLCheck` subclass | -| 2.2 | Procedure | CRUD | All | Only base class exists | -| 2.3 | SSH Tunnel | Connect | MariaDB, PostgreSQL | Only MySQL has it | +1. MySQL Procedure implementation. +2. MariaDB Procedure implementation. +3. Database create/drop methods in engine contexts. -### Priority 3: UI Completeness +### Priority C — UI Completeness -| Item | Object | Operation | What's Missing | -|------|--------|-----------|----------------| -| 3.1 | View | Create/Edit | Dialog and controller | -| 3.2 | Trigger | Create/Edit | Dialog and controller | -| 3.3 | Function | All | Panel, dialog, controller | -| 3.4 | Procedure | All | Panel, dialog, controller | -| 3.5 | Database | Create/Drop | Dialog and engine methods | +1. Trigger create/edit UI. +2. Function create/edit UI. +3. Procedure create/edit UI. -### Priority 4: New Features +### Priority D — Future Platform Features -| Item | Object | Operation | What's Missing | -|------|--------|-----------|----------------| -| 4.1 | Schema | CRUD | `SQLSchema` class, PostgreSQL support | -| 4.2 | Sequence | CRUD | `SQLSequence` class, PostgreSQL support | -| 4.3 | User/Role | CRUD | `SQLUser`, `SQLRole` classes | -| 4.4 | Privileges | CRUD | `SQLGrant` class | -| 4.5 | Import/Export | Execute | Dump/restore functionality | +1. PostgreSQL schema CRUD. +2. PostgreSQL sequence CRUD. +3. User/role/grants management. +4. Import/export workflows. --- ## 6. Definition of DONE -A CRUD capability is considered **DONE** when: - -- [ ] **Engine Layer** - - [ ] Dataclass exists with all required fields - - [ ] `create()` method implemented and tested - - [ ] `read()`/`get_*()` method returns correct data - - [ ] `update()`/`alter()` method handles all field changes - - [ ] `delete()`/`drop()` method works correctly - - [ ] Integration test passes with real database - -- [ ] **UI Layer** - - [ ] Object appears in Explorer tree - - [ ] Create dialog/panel exists and is functional - - [ ] Edit dialog/panel exists and is functional - - [ ] Delete confirmation and action works - - [ ] Changes reflect immediately in Explorer - -- [ ] **Cross-Cutting** - - [ ] Error handling with user-friendly messages - - [ ] Transaction support where applicable - - [ ] No regressions in existing tests - - [ ] Code follows `CODE_STYLE.md` - ---- +A capability is treated as **DONE** only when: -## Appendix: File Reference - -### Engine Layer Files - -| Engine | Context | Database | Builder | DataType | IndexType | -|--------|---------|----------|---------|----------|-----------| -| SQLite | `sqlite/context.py` | `sqlite/database.py` | `sqlite/builder.py` | `sqlite/datatype.py` | `sqlite/indextype.py` | -| MySQL | `mysql/context.py` | `mysql/database.py` | `mysql/builder.py` | `mysql/datatype.py` | `mysql/indextype.py` | -| MariaDB | `mariadb/context.py` | `mariadb/database.py` | `mariadb/builder.py` | `mariadb/datatype.py` | `mariadb/indextype.py` | -| PostgreSQL | `postgresql/context.py` | `postgresql/database.py` | `postgresql/builder.py` | `postgresql/datatype.py` | `postgresql/indextype.py` | - -### UI Layer Files - -| Component | File | -|-----------|------| -| Main Frame | `windows/main/main_frame.py` | -| Explorer | `windows/main/explorer.py` | -| Column Editor | `windows/main/column.py` | -| Index Editor | `windows/main/index.py` | -| Foreign Key Editor | `windows/main/foreign_key.py` | -| Check Editor | `windows/main/check.py` | -| Record Editor | `windows/main/records.py` | -| Table Model | `windows/main/table.py` | -| Database List | `windows/main/database.py` | -| Connection Manager | `windows/connections/manager.py` | +- Engine methods are implemented (`create/read/update/delete` where applicable). +- Integration tests pass on target engine versions. +- UI workflow exists (if feature is user-facing in explorer). +- No known regression in existing suites. +- Documentation is updated (`README`, `PROJECT_STATUS`, `ROADMAP`). diff --git a/PeterSQL.fbp b/PeterSQL.fbp index ef4f6c3..594082d 100755 --- a/PeterSQL.fbp +++ b/PeterSQL.fbp @@ -13,7 +13,7 @@ 0 /home/gtripoli/Projects/PeterSQL/windows UTF-8 - windows/__init__ + windows/views 1000 1 1 @@ -31,7 +31,7 @@ 1 0 0 - + 0 wxAUI_MGR_DEFAULT @@ -60,16 +60,16 @@ on_close - + bSizer34 wxVERTICAL none - + 5 wxEXPAND 1 - + 1 1 1 @@ -127,8 +127,8 @@ - - + + 1 1 1 @@ -144,7 +144,7 @@ 0 1 - 1 + 0 0 Dock 0 @@ -180,7 +180,7 @@ wxTAB_TRAVERSAL - + bSizer35 wxVERTICAL @@ -305,11 +305,11 @@ - MyMenu - m_menu5 + ConnectionMenu + connection_tree_menu protected - + Load From File; icons/16x16/folder.png 0 1 @@ -323,42 +323,70 @@ on_new_directory - + Load From File; icons/16x16/server.png 0 1 wxID_ANY wxITEM_NORMAL - New Session + New connection m_menuItem5 none - on_new_session + on_new_connection m_separator1 none - + Load From File; icons/16x16/edit_marker.png + 0 + 0 + + wxID_ANY + wxITEM_NORMAL + Rename + m_menuItem18 + none + + + on_rename + + + Load From File; icons/16x16/page_copy_columns.png + 0 + 0 + + wxID_ANY + wxITEM_NORMAL + Clone connection + m_menuItem19 + none + + + on_clone_connection + + + Load From File; icons/16x16/delete.png 0 1 wxID_ANY wxITEM_NORMAL - Import - m_menuItem10 + Delete + m_menuItem21 none - on_import + on_delete - - + + 1 1 1 @@ -410,16 +438,16 @@ wxTAB_TRAVERSAL - + bSizer36 wxVERTICAL none - + 5 wxALL|wxEXPAND 1 - + 1 1 1 @@ -1427,6 +1455,71 @@ 156 + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + Use TLS + + 0 + + + 0 + + 1 + use_tls_enabled + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + 5 wxALL @@ -2298,6 +2391,65 @@ + + 5 + wxALL|wxEXPAND + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + Load From File; icons/16x16/information.png + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + m_bitmap11 + 1 + + + protected + 1 + + Resizable + 1 + + ; ; forward_declare + 0 + SSH host + port (the SSH server that forwards traffic to the DB) + + + + + @@ -2712,82 +2864,18 @@ - - - - - - Statistics - 0 - - 1 - 1 - 1 - 1 - 0 - - 0 - 0 - - - - 1 - 0 - 1 - - 1 - 0 - Dock - 0 - Left - 0 - 1 - - 1 - - 0 - 0 - wxID_ANY - - 0 - - - 0 - - 1 - panel_statistics - 1 - - - protected - 1 - - Resizable - 1 - - ; ; forward_declare - 0 - - - - wxTAB_TRAVERSAL - - - bSizer361 - wxVERTICAL - none 5 wxEXPAND 0 - bSizer37 + bSizer1213212 wxHORIZONTAL none 5 - wxALL + wxALIGN_CENTER|wxALL 0 1 @@ -2818,16 +2906,16 @@ 0 0 wxID_ANY - Created at + Identity file 0 0 0 - 200,-1 + -1,-1 1 - m_staticText15 + m_staticText213212 1 @@ -2836,9 +2924,9 @@ Resizable 1 - + 150,-1 - ; ; forward_declare + 0 @@ -2849,9 +2937,9 @@ 5 - wxALL - 0 - + wxALIGN_CENTER|wxALL + 1 + 1 1 1 @@ -2880,16 +2968,15 @@ 0 0 wxID_ANY - - 0 0 + Select a file 0 1 - created_at + identity_file 1 @@ -2899,30 +2986,94 @@ Resizable 1 - + wxFLP_CHANGE_DIR|wxFLP_DEFAULT_STYLE|wxFLP_FILE_MUST_EXIST ; ; forward_declare 0 + + wxFILTER_NONE + wxDefaultValidator + + + *.* - -1 5 - wxEXPAND + wxEXPAND | wxALL 0 - - - bSizer371 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + m_staticline6 + 1 + + + protected + 1 + + Resizable + 1 + + wxLI_HORIZONTAL + ; ; forward_declare + 0 + + + + + + + + 5 + wxEXPAND + 0 + + + bSizer121311 wxHORIZONTAL none 5 - wxALL + wxALIGN_CENTER|wxALL 0 1 @@ -2953,16 +3104,16 @@ 0 0 wxID_ANY - Last connection + Remote host + port 0 0 0 - 200,-1 + -1,-1 1 - m_staticText151 + m_staticText21311 1 @@ -2971,9 +3122,9 @@ Resizable 1 - + 150,-1 - ; ; forward_declare + 0 @@ -2984,9 +3135,9 @@ 5 - wxALL - 0 - + wxALIGN_CENTER|wxALL + 1 + 1 1 1 @@ -3015,16 +3166,15 @@ 0 0 wxID_ANY - - 0 0 + 0 0 1 - last_connection_at + remote_hostname 1 @@ -3035,31 +3185,24 @@ 1 - ; ; forward_declare + 0 + + wxFILTER_NONE + wxDefaultValidator + + - -1 - - - - 5 - wxEXPAND - 0 - - - bSizer3711 - wxHORIZONTAL - none 5 wxALL 0 - + 1 1 1 @@ -3088,16 +3231,17 @@ 0 0 wxID_ANY - Successful connections - 0 + 3306 + 65536 0 + 0 0 - 200,-1 + 1 - m_staticText1511 + remote_port 1 @@ -3107,21 +3251,21 @@ Resizable 1 - + wxSP_ARROW_KEYS ; ; forward_declare 0 + - -1 5 - wxALL + wxALL|wxEXPAND 0 - + 1 1 1 @@ -3132,6 +3276,7 @@ 0 + Load From File; icons/16x16/information.png 1 0 @@ -3150,8 +3295,6 @@ 0 0 wxID_ANY - - 0 0 @@ -3159,7 +3302,7 @@ 0 1 - successful_connections + m_bitmap1 1 @@ -3169,25 +3312,87 @@ Resizable 1 - ; ; forward_declare 0 - + Remote host/port is the real DB target (defaults to DB Host/Port). - -1 - + + + + + + Statistics + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + panel_statistics + 1 + + + protected + 1 + + Resizable + 1 + + ; ; forward_declare + 0 + + + + wxTAB_TRAVERSAL + + + bSizer361 + wxVERTICAL + none + 5 wxEXPAND 0 - + - bSizer37111 + bSizer37 wxHORIZONTAL none @@ -3223,7 +3428,7 @@ 0 0 wxID_ANY - Unsuccessful connections + Created at 0 0 @@ -3232,7 +3437,7 @@ 0 200,-1 1 - m_staticText15111 + m_staticText15 1 @@ -3294,7 +3499,7 @@ 0 1 - unsuccessful_connections + created_at 1 @@ -3316,85 +3521,1165 @@ - - - - - - - - - - - - 5 - wxEXPAND | wxALL - 0 - - 1 - 1 - 1 - 1 - 0 - - 0 - 0 - - - - 1 - 0 - 1 - - 1 - 0 - Dock - 0 - Left - 0 - 1 - - 1 - - 0 - 0 - wxID_ANY - - 0 - - - 0 - - 1 - m_staticline4 - 1 - - - protected - 1 - - Resizable - 1 - - wxLI_HORIZONTAL - ; ; forward_declare - 0 - - + + 5 + wxEXPAND + 0 + + + bSizer371 + wxHORIZONTAL + none + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + Last connection + 0 + + 0 + + + 0 + 200,-1 + 1 + m_staticText151 + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + + + -1 + + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + 0 + + + 0 + + 1 + last_connection_at + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + + + -1 + + + + + + 5 + wxEXPAND + 0 + + + bSizer3711 + wxHORIZONTAL + none + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + Successful connections + 0 + + 0 + + + 0 + 200,-1 + 1 + m_staticText1511 + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + + + -1 + + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + 0 + + + 0 + + 1 + successful_connected + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + + + -1 + + + + + + 5 + wxEXPAND + 0 + + + bSizer371111 + wxHORIZONTAL + none + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + Last successful connection + 0 + + 0 + + + 0 + 200,-1 + 1 + m_staticText151111 + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + + + -1 + + + + 5 + wxALL + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + 0 + + + 0 + + 1 + last_successful_connection + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + + + -1 + + + + + + 5 + wxEXPAND + 0 + + + bSizer37111 + wxHORIZONTAL + none + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + Unsuccessful connections + 0 + + 0 + + + 0 + 200,-1 + 1 + m_staticText15111 + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + + + -1 + + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + 0 + + + 0 + + 1 + unsuccessful_connections + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + + + -1 + + + + + + 5 + wxEXPAND + 0 + + + bSizer371112 + wxHORIZONTAL + none + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + Last failure reason + 0 + + 0 + + + 0 + 200,-1 + 1 + m_staticText151112 + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + + + -1 + + + + 5 + wxALL + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + 0 + + + 0 + + 1 + last_failure_raison + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + + + -1 + + + + + + 5 + wxEXPAND + 0 + + + bSizer3711121 + wxHORIZONTAL + none + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + Total connection attempts + 0 + + 0 + + + 0 + 200,-1 + 1 + m_staticText1511121 + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + + + -1 + + + + 5 + wxALL + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + 0 + + + 0 + + 1 + total_connection_attempts + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + + + -1 + + + + + + 5 + wxEXPAND + 0 + + + bSizer37111211 + wxHORIZONTAL + none + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + Average connection time (ms) + 0 + + 0 + + + 0 + 200,-1 + 1 + m_staticText15111211 + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + + + -1 + + + + 5 + wxALL + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + 0 + + + 0 + + 1 + average_connection_time + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + + + -1 + + + + + + 5 + wxEXPAND + 0 + + + bSizer371112111 + wxHORIZONTAL + none + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + Most recent connection duration + 0 + + 0 + + + 0 + 200,-1 + 1 + m_staticText151112111 + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + + + -1 + + + + 5 + wxALL + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + 0 + + + 0 + + 1 + most_recent_connection_duration + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + + + -1 + + + + + + + + + + + + + + + + 5 + wxEXPAND | wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + m_staticline4 + 1 + + + protected + 1 + + Resizable + 1 + + wxLI_HORIZONTAL + ; ; forward_declare + 0 + + - + 0 wxEXPAND 0 - + bSizer28 wxHORIZONTAL none - + 5 wxEXPAND 1 @@ -3475,7 +4760,38 @@ - on_create_session + on_create + + MenuCreate + m_menu12 + protected + + + 0 + 1 + + wxID_ANY + wxITEM_NORMAL + Create connection + m_menuItem16 + none + + + + + + 0 + 1 + + wxID_ANY + wxITEM_NORMAL + Create directory + m_menuItem17 + none + + + + @@ -3641,11 +4957,11 @@ none - + 5 wxEXPAND 0 - + bSizer29 wxHORIZONTAL @@ -3871,6 +5187,7 @@ + on_test_session @@ -3971,7 +5288,7 @@ wxID_ANY 800,600 - Settings + SettingsDialog 800,600 wxDEFAULT_DIALOG_STYLE @@ -4251,7 +5568,7 @@ - + 0 wxAUI_MGR_DEFAULT @@ -4279,34 +5596,34 @@ - + bSizer111 wxVERTICAL none - + 5 wxEXPAND 0 - + bSizer112 wxVERTICAL none - + 5 wxEXPAND 1 - + bSizer113 wxHORIZONTAL none - + 5 wxALIGN_CENTER|wxALL 0 - + 1 1 1 @@ -4364,11 +5681,11 @@ -1 - + 5 wxALL 0 - + 1 1 1 @@ -4434,11 +5751,11 @@ - + 5 wxEXPAND | wxALL 1 - + 1 1 1 @@ -4502,30 +5819,30 @@ - + 5 wxEXPAND 0 - + bSizer114 wxHORIZONTAL none - + 5 wxEXPAND 1 - + 0 protected 0 - + 5 wxALL 0 - + 1 1 1 @@ -4595,11 +5912,11 @@ - + 5 wxALL 0 - + 1 1 1 @@ -4692,7 +6009,7 @@ 800,600 MainFrameView - 1024,762 + 1280,1024 wxDEFAULT_FRAME_STYLE|wxMAXIMIZE_BOX PeterSQL @@ -9290,7 +10607,7 @@ 5 wxEXPAND | wxALL - 1 + 0 1 1 @@ -9385,7 +10702,7 @@ 0 1 - m_panel34 + pnl_view_editor_root 1 @@ -9406,158 +10723,1526 @@ bSizer85 wxVERTICAL none + + 5 + wxALL|wxEXPAND + 0 + + + bSizer87 + wxHORIZONTAL + none + + 5 + wxALIGN_CENTER|wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + Name + 0 + + 0 + + + 0 + 150,-1 + 1 + m_staticText40 + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + + + -1 + + + + 5 + wxALIGN_CENTER|wxALL + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + 0 + + 0 + + 1 + txt_view_name + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + 5 wxEXPAND - 1 + 0 - bSizer86 - wxVERTICAL + bSizer89 + wxHORIZONTAL none 5 wxEXPAND - 0 + 1 + + + bSizer116 + wxVERTICAL + none + + 5 + wxEXPAND | wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + pnl_row_definer + 1 + + + protected + 1 + + Resizable + 1 + + ; ; forward_declare + 0 + + + + wxTAB_TRAVERSAL + + + szr_view_definer + wxHORIZONTAL + none + + 5 + wxALIGN_CENTER|wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + Definer + 0 + + 0 + + + 0 + 150,-1 + 1 + lbl_view_definer + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + + + -1 + + + + 5 + wxALIGN_CENTER|wxALL + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + cmb_view_definer + 1 + + + protected + 1 + + Resizable + -1 + 1 + + + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + + + + 5 + wxEXPAND | wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + pnl_row_schema + 1 + + + protected + 1 + + Resizable + 1 + + ; ; forward_declare + 0 + + + + wxTAB_TRAVERSAL + + + szr_view_schema + wxHORIZONTAL + none + + 5 + wxALIGN_CENTER|wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + Schema + 0 + + 0 + + + 0 + 150,-1 + 1 + lbl_view_schema + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + + + -1 + + + + 5 + wxALIGN_CENTER|wxALL + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + cho_view_schema + 1 + + + protected + 1 + + Resizable + 0 + 1 + + + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + + + + + 5 + wxEXPAND + 1 - bSizer89 - wxHORIZONTAL + bSizer8711 + wxVERTICAL none + + 5 + wxEXPAND | wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + pnl_row_sql_security + 1 + + + protected + 1 + + Resizable + 1 + + ; ; forward_declare + 0 + + + + wxTAB_TRAVERSAL + + + szr_view_sql_security + wxHORIZONTAL + none + + 5 + wxALIGN_CENTER|wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + SQL security + 0 + + 0 + + + 0 + 150,-1 + 1 + lbl_view_sql_security + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + + + -1 + + + + 5 + wxALIGN_CENTER|wxALL + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + "DEFINER" "INVOKER" + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + cho_view_sql_security + 1 + + + protected + 1 + + Resizable + 0 + 1 + + + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + + + 5 + wxALL|wxEXPAND + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + pnl_row_algorithm + 1 + + + protected + 1 + + Resizable + 1 + + ; ; forward_declare + 0 + + + + wxTAB_TRAVERSAL + + Algorithm + + szr_view_algorithm + wxVERTICAL + 1 + none + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + UNDEFINED + + 0 + + + 0 + + 1 + rad_view_algorithm_undefined + 1 + + + protected + 1 + + Resizable + 1 + + wxRB_GROUP + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + 0 + + + + + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + MERGE + + 0 + + + 0 + + 1 + rad_view_algorithm_merge + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + 0 + + + + + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + TEMPTABLE + + 0 + + + 0 + + 1 + rad_view_algorithm_temptable + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + 0 + + + + + + + + + + 5 + wxALL|wxEXPAND + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + pnl_row_constraint + 1 + + + protected + 1 + + Resizable + 1 + + ; ; forward_declare + 0 + + + + wxTAB_TRAVERSAL + + View constraint + + szr_view_constraint + wxVERTICAL + 1 + none + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + None + + 0 + + + 0 + + 1 + rad_view_constraint_none + 1 + + + protected + 1 + + Resizable + 1 + + wxRB_GROUP + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + 0 + + + + + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + LOCAL + + 0 + + + 0 + + 1 + rad_view_constraint_local + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + 0 + + + + + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + CASCADE + + 0 + + + 0 + + 1 + rad_view_constraint_cascaded + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + 0 + + + + + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + CHECK ONLY + + 0 + + + 0 + + 1 + rad_view_constraint_check_only + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + 0 + + + + + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + READ ONLY + + 0 + + + 0 + + 1 + rad_view_constraint_read_only + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + 0 + + + + + + + + 5 wxEXPAND - 1 - + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 - bSizer87 - wxHORIZONTAL - none - - 5 - wxALIGN_CENTER|wxALL - 0 - - 1 - 1 - 1 - 1 - 0 - - 0 - 0 - - - - 1 - 0 - 1 - - 1 - 0 - Dock - 0 - Left - 0 - 1 - - 1 - - 0 - 0 - wxID_ANY - Name - 0 - - 0 - - - 0 - - 1 - m_staticText40 - 1 - - - protected - 1 - - Resizable - 1 - - - ; ; forward_declare - 0 - - - - - -1 - - - - 5 - wxALL|wxEXPAND - 1 - - 1 - 1 - 1 - 1 - 0 - - 0 - 0 - - - - 1 - 0 - 1 - - 1 - 0 - Dock - 0 - Left - 0 - 1 - - 1 - - 0 - 0 - wxID_ANY - - 0 - - 0 - - 0 - - 1 - m_textCtrl22 - 1 - - - protected - 1 - - Resizable - 1 - - - ; ; forward_declare - 0 - - - wxFILTER_NONE - wxDefaultValidator - - - - - + 1 + pnl_row_security_barrier + 1 + + + protected + 1 + + Resizable + 1 + + ; ; forward_declare + 0 + + + + wxTAB_TRAVERSAL + + + bSizer126 + wxVERTICAL + none + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + Force + + 0 + + + 0 + + 1 + chk_view_force + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + @@ -9565,137 +12250,128 @@ 5 wxEXPAND - 1 - + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 - bSizer871 - wxHORIZONTAL - none - - 5 - wxALIGN_CENTER|wxALL - 0 - - 1 - 1 - 1 - 1 - 0 - - 0 - 0 - - - - 1 - 0 - 1 - - 1 - 0 - Dock - 0 - Left - 0 - 1 - - 1 - - 0 - 0 - wxID_ANY - Temporary - 0 - - 0 - - - 0 - - 1 - m_staticText401 - 1 - - - protected - 1 - - Resizable - 1 - - - ; ; forward_declare - 0 - - - - - -1 - - - - 5 - wxALIGN_CENTER|wxALL - 0 - - 1 - 1 - 1 - 1 - 0 - - 0 - 0 - - - - 1 - 0 - 0 - 1 - - 1 - 0 - Dock - 0 - Left - 0 - 1 - - 1 - - 0 - 0 - wxID_ANY - - - 0 - - - 0 - - 1 - m_checkBox5 - 1 - - - protected - 1 - - Resizable - 1 - - - ; ; forward_declare - 0 - - - wxFILTER_NONE - wxDefaultValidator - - - - + 1 + pnl_row_force + 1 + + + protected + 1 + + Resizable + 1 + + ; ; forward_declare + 0 + + + + wxTAB_TRAVERSAL + + + bSizer127 + wxVERTICAL + none + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + Security barrier + + 0 + + + 0 + + 1 + chk_view_security_barrier + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + @@ -9751,9 +12427,9 @@ -1,-1 0 - + -1,200 1 - sql_view + stc_view_select 1 @@ -9763,7 +12439,7 @@ 0 Resizable 1 - -1,200 + -1,-1 ; ; forward_declare 1 4 @@ -9817,7 +12493,7 @@ 0 Left 0 - 1 + 0 1 @@ -10721,89 +13397,427 @@ protected 0 - - - 5 - wxALL - 0 - - 1 - 1 - 1 - 1 - 0 - - 0 - 0 - 0 - - - Load From File; icons/16x16/resultset_next.png - - 1 - 0 - 1 - - 1 - - 0 - 0 - - Dock - 0 - Left - 0 - 1 - - 1 - - - 0 - 0 - wxID_ANY - Next - - 0 - - 0 - - - 0 - - 1 - m_button40 - 1 - - - protected - 1 - - - - Resizable - 1 - - wxBORDER_NONE - ; ; forward_declare - 0 - - - wxFILTER_NONE - wxDefaultValidator - - - - - on_next_records + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + 0 + + + Load From File; icons/16x16/resultset_next.png + + 1 + 0 + 1 + + 1 + + 0 + 0 + + Dock + 0 + Left + 0 + 1 + + 1 + + + 0 + 0 + wxID_ANY + Next + + 0 + + 0 + + + 0 + + 1 + m_button40 + 1 + + + protected + 1 + + + + Resizable + 1 + + wxBORDER_NONE + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + on_next_records + + + + + + 5 + wxALL|wxEXPAND + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + 0 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + Filters + + 0 + + + 0 + + 1 + m_collapsiblePane1 + 1 + + + protected + 1 + + Resizable + 1 + + wxCP_DEFAULT_STYLE|wxCP_NO_TLW_RESIZE + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + wxFULL_REPAINT_ON_RESIZE + on_collapsible_pane_changed + + + bSizer831 + wxVERTICAL + none + + 5 + wxEXPAND | wxALL + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + 1 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + 0 + + 0 + 0 + wxID_ANY + 1 + 0 + + 0 + -1,-1 + + 0 + + 1 + sql_query_filters + 1 + + + protected + 1 + + 0 + Resizable + 1 + -1,100 + ; ; forward_declare + 1 + 4 + 0 + + 1 + 0 + 0 + + + + + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + 0 + + + Load From File; icons/16x16/tick.png + + 1 + 0 + 1 + CTRL+ENTER + 1 + + 0 + 0 + + Dock + 0 + Left + 0 + 1 + + 1 + + + 0 + 0 + wxID_ANY + Apply + + 0 + + 0 + + + 0 + + 1 + m_button41 + 1 + + + protected + 1 + + + + Resizable + 1 + + wxBORDER_NONE + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + on_apply_filters + 5 - wxALL|wxEXPAND - 0 - + wxALL|wxEXPAND + 1 + + + + 1 + 0 + 1 + + ,90,400,10,70,0 + 0 + wxID_ANY + + + list_ctrl_table_records + protected + + + wxDV_MULTIPLE + TableRecordsDataViewCtrl; .components.dataview; forward_declare + + + + + + + + + MyMenu + m_menu10 + protected + + + 0 + 1 + + wxID_ANY + wxITEM_NORMAL + Insert row + m_menuItem13 + none + Ins + + + + + 0 + 1 + + wxID_ANY + wxITEM_NORMAL + MyMenuItem + m_menuItem14 + none + + + + + + + + Load From File; icons/16x16/arrow_right.png + Query + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + panel_query + 1 + + + protected + 1 + + Resizable + 1 + + + 0 + + + + wxTAB_TRAVERSAL + + + bSizer26 + wxVERTICAL + none + + 5 + wxEXPAND + 1 + 1 1 1 @@ -10818,7 +13832,6 @@ 1 0 1 - 0 1 0 @@ -10833,15 +13846,15 @@ 0 0 wxID_ANY - Filters 0 + 0 0 1 - m_collapsiblePane1 + m_splitter6 1 @@ -10849,433 +13862,345 @@ 1 Resizable + 0.0 + -300 + -1 1 - wxCP_DEFAULT_STYLE|wxCP_NO_TLW_RESIZE + wxSPLIT_HORIZONTAL + wxSP_3D ; ; forward_declare 0 - - wxFILTER_NONE - wxDefaultValidator - - wxFULL_REPAINT_ON_RESIZE - on_collapsible_pane_changed - - - bSizer831 - wxVERTICAL - none - - 5 - wxEXPAND | wxALL - 1 - - 1 - 1 - 1 - 1 - 0 - - 0 - 0 - 1 - - - - 1 - 0 - 1 - - 1 - 0 - Dock - 0 - Left - 0 - 1 - - 1 - 0 - - 0 - 0 - wxID_ANY - 1 - 0 - - 0 - -1,-1 - - 0 + + + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + m_panel52 + 1 + + + protected + 1 + + Resizable + 1 + + ; ; forward_declare + 0 + + + + wxTAB_TRAVERSAL + - 1 - sql_query_filters - 1 - - - protected - 1 - - 0 - Resizable - 1 - -1,100 - ; ; forward_declare - 1 - 4 - 0 - - 1 - 0 - 0 - - - + bSizer125 + wxVERTICAL + none + + 5 + wxEXPAND | wxALL + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + 1 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + 1 + + 0 + 0 + wxID_ANY + 1 + 1 + + 0 + + + 0 + + 1 + sql_query_editor + 1 + + + protected + 1 + + 0 + Resizable + 1 + + ; ; forward_declare + 1 + 4 + 0 + + 1 + 0 + 0 + + + + + + + 5 + wxALIGN_RIGHT|wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + 0 + + + + + 1 + 0 + 1 + + 1 + + 0 + 0 + + Dock + 0 + Left + 0 + 1 + + 1 + + + 0 + 0 + wxID_ANY + New + + 0 + + 0 + + + 0 + + 1 + m_button12 + 1 + + + protected + 1 + + + + Resizable + 1 + + + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + - - 5 - wxALL - 0 - - 1 - 1 - 1 - 1 - 0 - - 0 - 0 - 0 - - - Load From File; icons/16x16/tick.png - - 1 - 0 - 1 - CTRL+ENTER - 1 - - 0 - 0 - - Dock - 0 - Left - 0 - 1 - - 1 - - - 0 - 0 - wxID_ANY - Apply - - 0 - - 0 - - - 0 + + + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 1 + wxID_ANY + + 0 + + + 0 + + 1 + m_panel53 + 1 + + + protected + 1 + + Resizable + 1 + + ; ; forward_declare + 0 + + + + wxTAB_TRAVERSAL + - 1 - m_button41 - 1 - - - protected - 1 - - - - Resizable - 1 - - wxBORDER_NONE - ; ; forward_declare - 0 - - - wxFILTER_NONE - wxDefaultValidator - - - - - on_apply_filters + bSizer1261 + wxVERTICAL + none + + 5 + wxEXPAND | wxALL + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + notebook_sql_results + 1 + + + protected + 1 + + Resizable + 1 + + + FlatNotebook; wx.lib.agw.flatnotebook; forward_declare + 0 + + + + + + - - 5 - wxALL|wxEXPAND - 1 - - - - 1 - 0 - 1 - - ,90,400,10,70,0 - 0 - wxID_ANY - - - list_ctrl_table_records - protected - - - wxDV_MULTIPLE - TableRecordsDataViewCtrl; .components.dataview; forward_declare - - - - - - - - - MyMenu - m_menu10 - protected - - - 0 - 1 - - wxID_ANY - wxITEM_NORMAL - Insert row - m_menuItem13 - none - Ins - - - - - 0 - 1 - - wxID_ANY - wxITEM_NORMAL - MyMenuItem - m_menuItem14 - none - - - - - - - - Load From File; icons/16x16/arrow_right.png - Query - 0 - - 1 - 1 - 1 - 1 - 0 - - 0 - 0 - - - - 1 - 0 - 1 - - 1 - 0 - Dock - 0 - Left - 0 - 0 - - 1 - - 0 - 0 - wxID_ANY - - 0 - - - 0 - - 1 - QueryPanel - 1 - - - protected - 1 - - Resizable - 1 - - - 0 - - - - wxTAB_TRAVERSAL - - - bSizer26 - wxVERTICAL - none - - 5 - wxALL|wxEXPAND - 1 - - 1 - 1 - 1 - 1 - 0 - - 0 - 0 - - - - 1 - 0 - 1 - - 1 - 0 - Dock - 0 - Left - 0 - 1 - - 1 - - 0 - 0 - wxID_ANY - - 0 - - 0 - - 0 - - 1 - m_textCtrl10 - 1 - - - protected - 1 - - Resizable - 1 - - wxTE_MULTILINE|wxTE_RICH|wxTE_RICH2 - ; ; forward_declare - 0 - - - wxFILTER_NONE - wxDefaultValidator - - - - - - - - - 5 - wxALIGN_RIGHT|wxALL - 0 - - 1 - 1 - 1 - 1 - 0 - - 0 - 0 - 0 - - - - - 1 - 0 - 1 - - 1 - - 0 - 0 - - Dock - 0 - Left - 0 - 1 - - 1 - - - 0 - 0 - wxID_ANY - New - - 0 - - 0 - - - 0 - - 1 - m_button12 - 1 - - - protected - 1 - - - - Resizable - 1 - - - ; ; forward_declare - 0 - - - wxFILTER_NONE - wxDefaultValidator - - - - - - @@ -11605,8 +14530,1517 @@ - - + + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + -1,-1 + + 0 + + 1 + panel_sql_log + 1 + + + protected + 1 + + Resizable + 1 + -1,-1 + ; ; forward_declare + 0 + + + + wxTAB_TRAVERSAL + + + sizer_log_sql + wxVERTICAL + none + + 5 + wxEXPAND | wxALL + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + 1 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + 0 + + 0 + 0 + wxID_ANY + 1 + 1 + + 0 + -1,-1 + + 0 + + 1 + sql_query_logs + 1 + + + protected + 1 + + 0 + Resizable + 1 + -1,200 + ; ; forward_declare + 1 + 4 + 0 + + 1 + 0 + 0 + + + + + + + + + + + + + + + + + + 1 + 0 + 1 + + 4 + + 0 + wxID_ANY + + + status_bar + protected + + + wxSTB_SIZEGRIP + + + + + + + + + 0 + wxAUI_MGR_DEFAULT + + + 1 + 0 + 1 + impl_virtual + + + 0 + wxID_ANY + + + Trash + + 500,300 + ; ; forward_declare + + 0 + + + wxTAB_TRAVERSAL + + + bSizer90 + wxVERTICAL + none + + 5 + wxALL|wxEXPAND + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + 0 + + 0 + + 1 + m_textCtrl221 + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + 5 + wxEXPAND + 1 + + + bSizer93 + wxVERTICAL + none + + 5 + wxEXPAND | wxALL + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + tree_ctrl_explorer____ + 1 + + + protected + 1 + + Resizable + 1 + + wxTL_DEFAULT_STYLE + ; ; forward_declare + 0 + + + + + + wxALIGN_LEFT + wxCOL_RESIZABLE + Column5 + wxCOL_WIDTH_DEFAULT + + + + + 5 + wxEXPAND + 1 + + + bSizer129 + wxVERTICAL + none + + 5 + wxALL|wxEXPAND + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + UNDEFINED + + 0 + + + 0 + + 1 + m_radioBtn11 + 1 + + + protected + 1 + + Resizable + 1 + + wxRB_GROUP + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + 0 + + + + + + + 5 + wxALL|wxEXPAND + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + MERGE + + 0 + + + 0 + + 1 + m_radioBtn21 + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + 0 + + + + + MyMenu + m_menu13 + protected + + + 0 + 1 + + wxID_ANY + wxITEM_NORMAL + Import + m_menuItem10 + none + + + on_import + + + + + + 5 + wxALL|wxEXPAND + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + TEMPTABLE + + 0 + + + 0 + + 1 + m_radioBtn31 + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + 0 + + + + + + + 5 + wxALIGN_CENTER|wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + Algorithm + 0 + + 0 + + + 0 + + 1 + m_staticText4011 + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + + + -1 + + + + 5 + wxALL|wxEXPAND + 1 + + 2 + wxBOTH + + + 0 + + fgSizer1 + wxFLEX_GROWMODE_NONE + none + 3 + 0 + + 5 + wxEXPAND + 1 + + 0 + protected + 0 + + + + + + 5 + wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + Read only + + 0 + + + 0 + + 1 + m_checkBox7 + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + 5 + wxALL|wxEXPAND + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + "UNDEFINED" "MERGE" "TEMPTABLE" + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + Algorithm + 1 + + 0 + + + 0 + + 1 + rad_view_algorithm + 1 + + + protected + 1 + + Resizable + 0 + 1 + + wxRA_SPECIFY_COLS + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + 5 + wxALL|wxEXPAND + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + "None" "LOCAL" "CASCADED" "CHECK OPTION" "READ ONLY" + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + View constraint + 1 + + 0 + + + 0 + -1,-1 + 1 + rad_view_constraint + 1 + + + protected + 1 + + Resizable + 0 + 1 + + wxRA_SPECIFY_COLS + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + MyMenu + m_menu15 + protected + + + + + 5 + wxALL|wxEXPAND + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + 0 + + 0 + + 1 + m_textCtrl10 + 1 + + + protected + 1 + + Resizable + 1 + + wxTE_MULTILINE|wxTE_RICH|wxTE_RICH2 + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + + + 5 + wxEXPAND | wxALL + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + notebook_sql_results + 1 + + + protected + 1 + + Resizable + 1 + + wxAUI_NB_DEFAULT_STYLE|wxAUI_NB_MIDDLE_CLICK_CLOSE + ; ; forward_declare + -1 + 0 + + + + + + + + + 5 + wxALIGN_CENTER|wxALL + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + 0 + + 0 + + 1 + ssh_tunnel_password1 + 1 + + + protected + 1 + + Resizable + 1 + + wxTE_PASSWORD + + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + + + 5 + wxEXPAND | wxALL + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + 0 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + collapsible + + 0 + + + 0 + + 1 + m_collapsiblePane2 + 1 + + + protected + 1 + + Resizable + 1 + + wxCP_DEFAULT_STYLE + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + bSizer92 + wxVERTICAL + none + + + + + 5 + wxALL|wxEXPAND + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + tree_ctrl_sessions + 1 + + + protected + 1 + + Resizable + 1 + + wxTR_DEFAULT_STYLE|wxTR_FULL_ROW_HIGHLIGHT|wxTR_HAS_BUTTONS|wxTR_HIDE_ROOT|wxTR_TWIST_BUTTONS + ; ; forward_declare + 0 + + + + + show_tree_ctrl_menu + + MyMenu + m_menu12 + protected + + + + + 5 + wxEXPAND | wxALL + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + m_treeListCtrl3 + 1 + + + protected + 1 + + Resizable + 1 + + wxTL_DEFAULT_STYLE + ; ; forward_declare + 0 + + + + + + + + 5 + wxEXPAND | wxALL + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + tree_ctrl_sessions1 + 1 + + + protected + 1 + + Resizable + 1 + + wxTL_DEFAULT_STYLE + ; ; forward_declare + 0 + + + + + + wxALIGN_LEFT + wxCOL_RESIZABLE + Column3 + wxCOL_WIDTH_DEFAULT + + + wxALIGN_LEFT + wxCOL_RESIZABLE + Column4 + wxCOL_WIDTH_DEFAULT + + + + + 5 + wxALL|wxEXPAND + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + 0 + + 0 + + 1 + table_collationdd + 1 + + + protected + 1 + + Resizable + 1 + + + + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + 5 + wxALL|wxEXPAND + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + 0 + + 0 + + 1 + m_textCtrl21 + 1 + + + protected + 1 + + Resizable + 1 + + wxTE_MULTILINE + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + 0 + wxEXPAND + 0 + + + bSizer51 + wxVERTICAL + none + + 0 + wxEXPAND | wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + panel_credentials + 1 + + + protected + 1 + + Resizable + 1 + + ; ; forward_declare + 0 + + + + wxTAB_TRAVERSAL + + + bSizer48 + wxVERTICAL + none + + 5 + wxEXPAND | wxALL + 1 + 1 1 1 @@ -11617,6 +16051,7 @@ 0 + 1 0 @@ -11637,12 +16072,12 @@ wxID_ANY 0 - -1,-1 + 0 1 - LogSQLPanel + m_notebook8 1 @@ -11651,228 +16086,227 @@ Resizable 1 - -1,-1 + + ; ; forward_declare 0 - wxTAB_TRAVERSAL - - - sizer_log_sql - wxVERTICAL - none - - 5 - wxEXPAND | wxALL - 1 - - 1 - 1 - 1 - 1 - 0 - - 0 - 0 - 1 - - - - 1 - 0 - 1 - - 1 - 0 - Dock - 0 - Left - 0 - 1 - - 1 - 0 - - 0 - 0 - wxID_ANY - 1 - 1 - - 0 - -1,-1 - - 0 - - 1 - sql_query_logs - 1 - - - protected - 1 - - 0 - Resizable - 1 - -1,200 - ; ; forward_declare - 1 - 4 - 0 - - 1 - 0 - 0 - - - - + + + + + + + + 0 + wxEXPAND | wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 1 + wxID_ANY + + 0 + + + 0 + + 1 + panel_source + 1 + + + protected + 1 + + Resizable + 1 + + ; ; forward_declare + 0 + + + + wxTAB_TRAVERSAL + + + bSizer52 + wxVERTICAL + none + + 0 + wxEXPAND + 0 + + + bSizer1212 + wxHORIZONTAL + none + + 5 + wxALIGN_CENTER|wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + Filename + 0 + + 0 + + + 0 + -1,-1 + 1 + m_staticText212 + 1 + + + protected + 1 + + Resizable + 1 + 150,-1 + + + 0 + + + + + -1 - - - - - - - - - - - - 1 - 0 - 1 - - 4 - - 0 - wxID_ANY - - - status_bar - protected - - - wxSTB_SIZEGRIP - - - - - - - - - 0 - wxAUI_MGR_DEFAULT - - - 1 - 0 - 1 - impl_virtual - - - 0 - wxID_ANY - - - Trash - - 500,300 - ; ; forward_declare - - 0 - - - wxTAB_TRAVERSAL - - - bSizer90 - wxVERTICAL - none - - 5 - wxALL|wxEXPAND - 1 - - 1 - 1 - 1 - 1 - 0 - - 0 - 0 - - - - 1 - 0 - 1 - - 1 - 0 - Dock - 0 - Left - 0 - 1 - - 1 - - 0 - 0 - wxID_ANY - - 0 - - 0 - - 0 - - 1 - m_textCtrl221 - 1 - - - protected - 1 - - Resizable - 1 - - - ; ; forward_declare - 0 - - - wxFILTER_NONE - wxDefaultValidator - - - - - - - - - 5 - wxEXPAND - 1 - - - bSizer93 - wxVERTICAL - none + + 5 + wxALL + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + Select a file + + 0 + + 1 + filename + 1 + + + protected + 1 + + Resizable + 1 + + wxFLP_CHANGE_DIR|wxFLP_USE_TEXTCTRL + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + Database (*.db;*.db3;*.sdb;*.s3db;*.sqlite;*.sqlite3)|*.db;*.db3;*.sdb;*.s3db;*.sqlite;*.sqlite3 + + + + + + + + + + 5 - wxEXPAND | wxALL - 1 - + wxALIGN_CENTER|wxALL + 0 + 1 1 1 @@ -11901,14 +16335,16 @@ 0 0 wxID_ANY + Port + 0 0 0 - + -1,-1 1 - tree_ctrl_explorer____ + m_staticText2211 1 @@ -11917,20 +16353,15 @@ Resizable 1 - - wxTL_DEFAULT_STYLE - ; ; forward_declare + 150,-1 + + 0 - - wxALIGN_LEFT - wxCOL_RESIZABLE - Column5 - wxCOL_WIDTH_DEFAULT - + -1 @@ -11939,7 +16370,7 @@ 5 wxEXPAND | wxALL 1 - + 1 1 1 @@ -11954,7 +16385,6 @@ 1 0 1 - 0 1 0 @@ -11969,7 +16399,6 @@ 0 0 wxID_ANY - collapsible 0 @@ -11977,7 +16406,7 @@ 0 1 - m_collapsiblePane2 + m_panel35 1 @@ -11987,20 +16416,15 @@ Resizable 1 - wxCP_DEFAULT_STYLE ; ; forward_declare 0 - - wxFILTER_NONE - wxDefaultValidator - - + wxTAB_TRAVERSAL - bSizer92 + bSizer96 wxVERTICAL none @@ -12008,9 +16432,9 @@ 5 - wxALL|wxEXPAND - 1 - + wxALIGN_CENTER|wxALL + 0 + 1 1 1 @@ -12042,11 +16466,12 @@ 0 + 0 0 1 - tree_ctrl_sessions + ssh_tunnel_port 1 @@ -12056,26 +16481,25 @@ Resizable 1 - wxTR_DEFAULT_STYLE|wxTR_FULL_ROW_HIGHLIGHT|wxTR_HAS_BUTTONS|wxTR_HIDE_ROOT|wxTR_TWIST_BUTTONS + ; ; forward_declare 0 + + wxFILTER_NONE + wxDefaultValidator + + - show_tree_ctrl_menu - - MyMenu - m_menu12 - protected - 5 - wxEXPAND | wxALL + wxALIGN_CENTER|wxALL 1 - + 1 1 1 @@ -12107,11 +16531,12 @@ 0 + 0 0 1 - m_treeListCtrl3 + ssh_tunnel_local_port 1 @@ -12121,7 +16546,71 @@ Resizable 1 - wxTL_DEFAULT_STYLE + + + 0 + + + wxFILTER_NONE + wxDefaultValidator + + + + + + + + + 5 + wxALL|wxEXPAND + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 1 + wxID_ANY + + 0 + + + 0 + + 1 + tree_ctrl_sessions2 + 1 + + + protected + 1 + + Resizable + 1 + + wxTR_DEFAULT_STYLE ; ; forward_declare 0 @@ -12161,7 +16650,7 @@ 1 0 - 0 + 1 wxID_ANY 0 @@ -12170,7 +16659,7 @@ 0 1 - tree_ctrl_sessions1 + tree_ctrl_sessions_bkp3 1 @@ -12180,7 +16669,7 @@ Resizable 1 - wxTL_DEFAULT_STYLE + wxTL_DEFAULT_STYLE|wxTL_SINGLE ; ; forward_declare 0 @@ -12190,13 +16679,13 @@ wxALIGN_LEFT wxCOL_RESIZABLE - Column3 + Name wxCOL_WIDTH_DEFAULT wxALIGN_LEFT wxCOL_RESIZABLE - Column4 + Usage wxCOL_WIDTH_DEFAULT @@ -12205,7 +16694,59 @@ 5 wxALL|wxEXPAND 1 - + + + + 1 + 0 + 1 + + + 1 + wxID_ANY + + + tree_ctrl_sessions_bkp + protected + + + wxDV_SINGLE + ; ; forward_declare + + + + + + wxALIGN_LEFT + + wxDATAVIEW_COL_RESIZABLE + Database + wxDATAVIEW_CELL_INERT + 0 + m_dataViewColumn1 + protected + IconText + -1 + + + wxALIGN_LEFT + + wxDATAVIEW_COL_RESIZABLE + Size + wxDATAVIEW_CELL_INERT + 1 + m_dataViewColumn3 + protected + Progress + 50 + + + + + 5 + wxALL + 0 + 1 1 1 @@ -12234,15 +16775,16 @@ 0 0 wxID_ANY + %(total_rows)s + 0 0 - 0 0 1 - table_collationdd + rows_database_table 1 @@ -12253,24 +16795,20 @@ 1 - + ; ; forward_declare 0 - - wxFILTER_NONE - wxDefaultValidator - - + -1 5 - wxALL|wxEXPAND - 1 - + wxALL + 0 + 1 1 1 @@ -12299,15 +16837,16 @@ 0 0 wxID_ANY + rows total + 0 0 - 0 0 1 - m_textCtrl21 + m_staticText44 1 @@ -12317,363 +16856,262 @@ Resizable 1 - wxTE_MULTILINE + ; ; forward_declare 0 - - wxFILTER_NONE - wxDefaultValidator - - + -1 - 0 - wxEXPAND + 5 + wxALL 0 - + + + + 1 + 0 + 1 + + + 0 + wxID_ANY + - bSizer51 - wxVERTICAL - none - - 0 - wxEXPAND | wxALL - 0 - - 1 - 1 - 1 - 1 - 0 - - 0 - 0 - - - - 1 - 0 - 1 - - 1 - 0 - Dock - 0 - Left - 0 - 1 - - 1 - - 0 - 0 - wxID_ANY - - 0 - - - 0 - - 1 - panel_credentials - 1 - - - protected - 1 - - Resizable - 1 - - ; ; forward_declare - 0 - - - - wxTAB_TRAVERSAL - - - bSizer48 - wxVERTICAL - none - - 5 - wxEXPAND | wxALL - 1 - - 1 - 1 - 1 - 1 - 0 - - 0 - 0 - - - - - 1 - 0 - 1 - - 1 - 0 - Dock - 0 - Left - 0 - 1 - - 1 - - 0 - 0 - wxID_ANY - - 0 - - - 0 - - 1 - m_notebook8 - 1 - - - protected - 1 - - Resizable - 1 - - - ; ; forward_declare - 0 - - - - - - - - + ____list_ctrl_database_tables + protected + + + + ; ; forward_declare + + + + + + wxALIGN_LEFT + + wxDATAVIEW_COL_RESIZABLE + Name + wxDATAVIEW_CELL_INERT + 0 + m_dataViewColumn5 + protected + Text + -1 + + + wxALIGN_LEFT + + wxDATAVIEW_COL_RESIZABLE + Name + wxDATAVIEW_CELL_INERT + 0 + m_dataViewColumn6 + protected + Text + -1 + + + wxALIGN_LEFT + + wxDATAVIEW_COL_RESIZABLE + Name + wxDATAVIEW_CELL_INERT + 0 + m_dataViewColumn7 + protected + Text + -1 + + + wxALIGN_LEFT + + wxDATAVIEW_COL_RESIZABLE + Name + wxDATAVIEW_CELL_INERT + 0 + m_dataViewColumn8 + protected + Text + -1 + + + wxALIGN_LEFT + + wxDATAVIEW_COL_RESIZABLE + Name + wxDATAVIEW_CELL_INERT + 0 + m_dataViewColumn9 + protected + Text + -1 + + + wxALIGN_LEFT + + wxDATAVIEW_COL_RESIZABLE + Name + wxDATAVIEW_CELL_INERT + 0 + m_dataViewColumn10 + protected + Text + -1 + + + wxALIGN_LEFT + + wxDATAVIEW_COL_RESIZABLE + Name + wxDATAVIEW_CELL_INERT + 0 + m_dataViewColumn11 + protected + Text + -1 - - 0 - wxEXPAND | wxALL - 0 - - 1 - 1 - 1 - 1 - 0 - - 0 - 0 - - - - 1 - 0 - 1 - - 1 - 0 - Dock - 0 - Left - 0 - 1 - - 1 - - 0 - 1 - wxID_ANY - - 0 - - - 0 - - 1 - panel_source - 1 - - - protected - 1 - - Resizable - 1 - - ; ; forward_declare - 0 - - - - wxTAB_TRAVERSAL - - - bSizer52 - wxVERTICAL - none - - 0 - wxEXPAND - 0 - - - bSizer1212 - wxHORIZONTAL - none - - 5 - wxALIGN_CENTER|wxALL - 0 - - 1 - 1 - 1 - 1 - 0 - - 0 - 0 - - - - 1 - 0 - 1 - - 1 - 0 - Dock - 0 - Left - 0 - 1 - - 1 - - 0 - 0 - wxID_ANY - Filename - 0 - - 0 - - - 0 - -1,-1 - 1 - m_staticText212 - 1 - - - protected - 1 - - Resizable - 1 - 150,-1 - - - 0 - - - - - -1 - - - - 5 - wxALL - 1 - - 1 - 1 - 1 - 1 - 0 - - 0 - 0 - - - - 1 - 0 - 1 - - 1 - 0 - Dock - 0 - Left - 0 - 1 - - 1 - - 0 - 0 - wxID_ANY - - 0 - - Select a file - - 0 - - 1 - filename - 1 - - - protected - 1 - - Resizable - 1 - - wxFLP_CHANGE_DIR|wxFLP_USE_TEXTCTRL - ; ; forward_declare - 0 - - - wxFILTER_NONE - wxDefaultValidator - - - Database (*.db;*.db3;*.sdb;*.s3db;*.sqlite;*.sqlite3)|*.db;*.db3;*.sdb;*.s3db;*.sqlite;*.sqlite3 - - - - - - - - - + + wxALIGN_LEFT + + wxDATAVIEW_COL_RESIZABLE + Name + wxDATAVIEW_CELL_INERT + 0 + m_dataViewColumn20 + protected + Text + -1 + + + wxALIGN_LEFT + + wxDATAVIEW_COL_RESIZABLE + Name + wxDATAVIEW_CELL_INERT + 0 + m_dataViewColumn21 + protected + Text + -1 + + + + + 5 + wxALL|wxEXPAND + 1 + + + + 1 + 0 + 1 + + + 0 + wxID_ANY + + + ___list_ctrl_database_tables + protected + + + + + + + + + + wxALIGN_LEFT + + wxDATAVIEW_COL_RESIZABLE + Name + wxDATAVIEW_CELL_INERT + m_dataViewListColumn6 + protected + Text + -1 + + + wxALIGN_LEFT + + wxDATAVIEW_COL_RESIZABLE + Lines + wxDATAVIEW_CELL_INERT + m_dataViewListColumn7 + protected + Text + -1 + + + wxALIGN_LEFT + + wxDATAVIEW_COL_RESIZABLE + Size + wxDATAVIEW_CELL_INERT + m_dataViewListColumn8 + protected + Text + -1 + + + wxALIGN_LEFT + + wxDATAVIEW_COL_RESIZABLE + Created at + wxDATAVIEW_CELL_INERT + m_dataViewListColumn9 + protected + Text + -1 + + + wxALIGN_LEFT + + wxDATAVIEW_COL_RESIZABLE + Updated at + wxDATAVIEW_CELL_INERT + m_dataViewListColumn10 + protected + Text + -1 + + + wxALIGN_LEFT + + wxDATAVIEW_COL_RESIZABLE + Engine + wxDATAVIEW_CELL_INERT + m_dataViewListColumn11 + protected + Text + -1 + + + wxALIGN_LEFT + + wxDATAVIEW_COL_RESIZABLE + Comments + wxDATAVIEW_CELL_INERT + m_dataViewListColumn12 + protected + Text + -1 5 - wxEXPAND | wxALL - 1 - + wxALL|wxEXPAND + 0 + 1 1 1 @@ -12709,35 +17147,84 @@ 0 1 - m_panel35 + m_gauge1 1 protected 1 + 100 Resizable 1 + ; ; forward_declare 0 + + wxFILTER_NONE + wxDefaultValidator + + 0 - wxTAB_TRAVERSAL - - - bSizer96 - wxVERTICAL - none - + 5 - wxALIGN_CENTER|wxALL + wxEXPAND 0 - + + 0 + protected + 150 + + + + 5 + wxEXPAND + 1 + + 0 + protected + 0 + + + + 5 + wxALL|wxEXPAND + 1 + + + + 1 + 0 + 1 + + + 1 + wxID_ANY + + + tree_ctrl_explorer__ + protected + + + + ; ; forward_declare + + + + + + + + 5 + wxALL + 0 + 1 1 1 @@ -12769,12 +17256,11 @@ 0 - 0 0 1 - ssh_tunnel_port + m_vlistBox1 1 @@ -12788,21 +17274,16 @@ ; ; forward_declare 0 - - wxFILTER_NONE - wxDefaultValidator - - - + 5 - wxALIGN_CENTER|wxALL - 1 - + wxALL + 0 + 1 1 1 @@ -12816,6 +17297,7 @@ 1 0 + 1 1 @@ -12834,12 +17316,11 @@ 0 - 0 0 1 - ssh_tunnel_local_port + m_listBox1 1 @@ -12850,14 +17331,13 @@ 1 - + ; ; forward_declare 0 wxFILTER_NONE wxDefaultValidator - @@ -12866,10 +17346,10 @@ 5 wxEXPAND - 0 + 1 - bSizer12211 + bSizer871 wxHORIZONTAL none @@ -12905,16 +17385,16 @@ 0 0 wxID_ANY - Port + Temporary 0 0 0 - -1,-1 + 1 - m_staticText2211 + m_staticText401 1 @@ -12923,83 +17403,89 @@ Resizable 1 - 150,-1 + - + ; ; forward_declare + 0 + + + + + -1 + + + + 5 + wxALIGN_CENTER|wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + + 0 + + + 0 + + 1 + m_checkBox5 + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare 0 + + wxFILTER_NONE + wxDefaultValidator + - -1 - - 5 - wxALL|wxEXPAND - 1 - - 1 - 1 - 1 - 1 - 0 - - 0 - 0 - - - - 1 - 0 - 1 - - 1 - 0 - Dock - 0 - Left - 0 - 1 - - 1 - - 0 - 1 - wxID_ANY - - 0 - - - 0 - - 1 - tree_ctrl_sessions2 - 1 - - - protected - 1 - - Resizable - 1 - - wxTR_DEFAULT_STYLE - ; ; forward_declare - 0 - - - - - - - + 5 wxEXPAND | wxALL 1 - + 1 1 1 @@ -13014,6 +17500,7 @@ 1 0 1 + 0 1 0 @@ -13026,8 +17513,9 @@ 1 0 - 1 + 0 wxID_ANY + Engine options 0 @@ -13035,7 +17523,7 @@ 0 1 - tree_ctrl_sessions_bkp3 + m_collapsiblePane3 1 @@ -13045,84 +17533,204 @@ Resizable 1 - wxTL_DEFAULT_STYLE|wxTL_SINGLE + wxCP_DEFAULT_STYLE ; ; forward_declare 0 + + wxFILTER_NONE + wxDefaultValidator + - - wxALIGN_LEFT - wxCOL_RESIZABLE - Name - wxCOL_WIDTH_DEFAULT - - - wxALIGN_LEFT - wxCOL_RESIZABLE - Usage - wxCOL_WIDTH_DEFAULT - - - - - 5 - wxALL|wxEXPAND - 1 - - - - 1 - 0 - 1 - - - 1 - wxID_ANY - - - tree_ctrl_sessions_bkp - protected - - - wxDV_SINGLE - ; ; forward_declare - - - - - - wxALIGN_LEFT - - wxDATAVIEW_COL_RESIZABLE - Database - wxDATAVIEW_CELL_INERT - 0 - m_dataViewColumn1 - protected - IconText - -1 - - - wxALIGN_LEFT - - wxDATAVIEW_COL_RESIZABLE - Size - wxDATAVIEW_CELL_INERT - 1 - m_dataViewColumn3 - protected - Progress - 50 + + + bSizer115 + wxVERTICAL + none + + 5 + wxEXPAND | wxALL + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + m_panel41 + 1 + + + protected + 1 + + Resizable + 1 + + ; ; forward_declare + 0 + + + + wxTAB_TRAVERSAL + + + + 5 + wxEXPAND | wxALL + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + m_panel42 + 1 + + + protected + 1 + + Resizable + 1 + + ; ; forward_declare + 0 + + + + wxTAB_TRAVERSAL + + + + 5 + wxEXPAND | wxALL + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + m_panel43 + 1 + + + protected + 1 + + Resizable + 1 + + ; ; forward_declare + 0 + + + + wxTAB_TRAVERSAL + + - + 5 - wxALL - 0 - + wxALL|wxEXPAND + 1 + 1 1 1 @@ -13151,16 +17759,15 @@ 0 0 wxID_ANY - %(total_rows)s - 0 0 + 0 0 1 - rows_database_table + m_textCtrl2211 1 @@ -13174,17 +17781,21 @@ ; ; forward_declare 0 + + wxFILTER_NONE + wxDefaultValidator + + - -1 - + 5 - wxALL - 0 - + wxALL|wxEXPAND + 1 + 1 1 1 @@ -13213,16 +17824,15 @@ 0 0 wxID_ANY - rows total - 0 0 + 0 0 1 - m_staticText44 + m_textCtrl2212 1 @@ -13236,258 +17846,380 @@ ; ; forward_declare 0 + + wxFILTER_NONE + wxDefaultValidator + + - -1 - - - - 5 - wxALL - 0 - - - - 1 - 0 - 1 - - - 0 - wxID_ANY - - - ____list_ctrl_database_tables - protected - - - - ; ; forward_declare - - - - - - wxALIGN_LEFT - - wxDATAVIEW_COL_RESIZABLE - Name - wxDATAVIEW_CELL_INERT - 0 - m_dataViewColumn5 - protected - Text - -1 - - - wxALIGN_LEFT - - wxDATAVIEW_COL_RESIZABLE - Name - wxDATAVIEW_CELL_INERT - 0 - m_dataViewColumn6 - protected - Text - -1 - - - wxALIGN_LEFT - - wxDATAVIEW_COL_RESIZABLE - Name - wxDATAVIEW_CELL_INERT - 0 - m_dataViewColumn7 - protected - Text - -1 - - - wxALIGN_LEFT - - wxDATAVIEW_COL_RESIZABLE - Name - wxDATAVIEW_CELL_INERT - 0 - m_dataViewColumn8 - protected - Text - -1 - - - wxALIGN_LEFT - - wxDATAVIEW_COL_RESIZABLE - Name - wxDATAVIEW_CELL_INERT - 0 - m_dataViewColumn9 - protected - Text - -1 - - - wxALIGN_LEFT - - wxDATAVIEW_COL_RESIZABLE - Name - wxDATAVIEW_CELL_INERT - 0 - m_dataViewColumn10 - protected - Text - -1 - - - wxALIGN_LEFT - - wxDATAVIEW_COL_RESIZABLE - Name - wxDATAVIEW_CELL_INERT - 0 - m_dataViewColumn11 - protected - Text - -1 - - - wxALIGN_LEFT - - wxDATAVIEW_COL_RESIZABLE - Name - wxDATAVIEW_CELL_INERT - 0 - m_dataViewColumn20 - protected - Text - -1 - - - wxALIGN_LEFT - - wxDATAVIEW_COL_RESIZABLE - Name - wxDATAVIEW_CELL_INERT - 0 - m_dataViewColumn21 - protected - Text - -1 - - + 5 wxALL|wxEXPAND 1 - + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + 1 + 0 + + 1 1 + 0 + Dock + 0 + Left 0 1 + 1 + 0 0 wxID_ANY + + 0 + + 0 - ___list_ctrl_database_tables + 1 + m_comboBox11 + 1 + + protected + 1 + Resizable + -1 + 1 - + ; ; forward_declare + 0 + + wxFILTER_NONE + wxDefaultValidator + + - - wxALIGN_LEFT - - wxDATAVIEW_COL_RESIZABLE - Name - wxDATAVIEW_CELL_INERT - m_dataViewListColumn6 - protected - Text - -1 - - - wxALIGN_LEFT - - wxDATAVIEW_COL_RESIZABLE - Lines - wxDATAVIEW_CELL_INERT - m_dataViewListColumn7 - protected - Text - -1 - - - wxALIGN_LEFT - - wxDATAVIEW_COL_RESIZABLE - Size - wxDATAVIEW_CELL_INERT - m_dataViewListColumn8 - protected - Text - -1 - - - wxALIGN_LEFT - - wxDATAVIEW_COL_RESIZABLE - Created at - wxDATAVIEW_CELL_INERT - m_dataViewListColumn9 - protected - Text - -1 - - - wxALIGN_LEFT - - wxDATAVIEW_COL_RESIZABLE - Updated at - wxDATAVIEW_CELL_INERT - m_dataViewListColumn10 - protected - Text - -1 - - - wxALIGN_LEFT - - wxDATAVIEW_COL_RESIZABLE - Engine - wxDATAVIEW_CELL_INERT - m_dataViewListColumn11 - protected - Text - -1 + + + + 5 + wxEXPAND + 1 + + 2 + 0 + + gSizer3 + none + 0 + 0 + + 5 + wxEXPAND + 1 + + + bSizer8712 + wxHORIZONTAL + none + + 5 + wxALIGN_CENTER|wxALL + 0 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + Algorithm + 0 + + 0 + + + 0 + + 1 + m_staticText4012 + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + + + -1 + + + + 5 + wxALL|wxEXPAND + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + UNDEFINED + + 0 + + + 0 + + 1 + m_radioBtn1 + 1 + + + protected + 1 + + Resizable + 1 + + wxRB_GROUP + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + 0 + + + + + + + 5 + wxALL|wxEXPAND + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + MERGE + + 0 + + + 0 + + 1 + m_radioBtn2 + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + 0 + + + + + + + 5 + wxALL|wxEXPAND + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + TEMPTABLE + + 0 + + + 0 + + 1 + m_radioBtn3 + 1 + + + protected + 1 + + Resizable + 1 + + + ; ; forward_declare + 0 + + + wxFILTER_NONE + wxDefaultValidator + + 0 + + + + + + - - wxALIGN_LEFT - - wxDATAVIEW_COL_RESIZABLE - Comments - wxDATAVIEW_CELL_INERT - m_dataViewListColumn12 - protected - Text - -1 + + 5 + wxEXPAND + 0 + + + bSizer12211 + wxHORIZONTAL + none + - + 5 - wxALL|wxEXPAND + wxALL 0 - + 1 1 1 @@ -13516,6 +18248,7 @@ 0 0 wxID_ANY + RadioBtn 0 @@ -13523,14 +18256,13 @@ 0 1 - m_gauge1 + m_radioBtn10 1 protected 1 - 100 Resizable 1 @@ -13548,118 +18280,80 @@ - - 5 - wxEXPAND - 0 - - 0 - protected - 150 - - - - 5 - wxEXPAND - 1 - - 0 - protected - 0 - - - - 5 - wxALL|wxEXPAND - 1 - - - - 1 - 0 - 1 - - - 1 - wxID_ANY - - - tree_ctrl_explorer__ - protected - - - - ; ; forward_declare - - - - - - 5 - wxALL + wxEXPAND 0 - - 1 - 1 - 1 - 1 - 0 - - 0 - 0 - - - - 1 - 0 - 1 - - 1 - 0 - Dock - 0 - Left - 0 - 1 - - 1 - - 0 - 0 - wxID_ANY - - 0 - - - 0 + - 1 - m_vlistBox1 - 1 - - - protected - 1 - - Resizable - 1 - - - ; ; forward_declare - 0 - - - - + bSizer86 + wxHORIZONTAL + none + + 5 + wxEXPAND | wxALL + 1 + + 1 + 1 + 1 + 1 + 0 + + 0 + 0 + + + + 1 + 0 + 1 + + 1 + 0 + Dock + 0 + Left + 0 + 1 + + 1 + + 0 + 0 + wxID_ANY + + 0 + + + 0 + + 1 + m_panel44 + 1 + + + protected + 1 + + Resizable + 1 + + ; ; forward_declare + 0 + + + + wxTAB_TRAVERSAL + + 5 wxALL 0 - + 1 1 1 @@ -13673,7 +18367,6 @@ 1 0 - 1 1 @@ -13692,11 +18385,12 @@ 0 + Select a file 0 1 - m_listBox1 + filename1 1 @@ -13706,7 +18400,7 @@ Resizable 1 - + wxFLP_CHANGE_DIR|wxFLP_DEFAULT_STYLE|wxFLP_FILE_MUST_EXIST ; ; forward_declare 0 @@ -13714,6 +18408,8 @@ wxFILTER_NONE wxDefaultValidator + + *.* diff --git a/PeterSQL.xcf b/PeterSQL.xcf new file mode 100644 index 0000000..1526273 Binary files /dev/null and b/PeterSQL.xcf differ diff --git a/README.md b/README.md index 84caa0b..577a352 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ![status: unstable](https://img.shields.io/badge/status-unstable-orange) -![Coverage](https://img.shields.io/badge/coverage-50%25-brightgreen) +![Coverage](https://img.shields.io/badge/coverage-54%25-brightgreen) ![SQLite](https://img.shields.io/badge/SQLite-tested-lightgrey) ![MySQL](https://img.shields.io/badge/MySQL-5.7%20%7C%208.0%20%7C%20latest-lightgrey) @@ -12,11 +12,15 @@ PeterSQL

-> Heidi's (silly?) friend — a wxPython-based reinterpretation of HeidiSQL +> Inspired by HeidiSQL — reimagined in pure Python. **PeterSQL** is a graphical client for database management, inspired by the excellent [HeidiSQL](https://www.heidisql.com/), but written entirely in **Python** -using **wxPython**, with a focus on portability and native look & feel. +using **wxPython**, with a focus on portability, extensibility, and native look & feel. + +PeterSQL is **not a clone and not a port** of HeidiSQL. +It shares the same spirit — clarity, speed, practicality — but follows its own +path as a Python-native project. --- @@ -27,28 +31,59 @@ Features may be incomplete or change without notice. Use at your own risk and **do not rely on this project in production environments** yet. +For a detailed status snapshot, see: + +- [PROJECT_STATUS.md](PROJECT_STATUS.md) +- [ROADMAP.md](ROADMAP.md) + +### Recent updates + +- PostgreSQL engine now includes **Function** and **Procedure** classes with CRUD-style operations. +- Check constraint support was added for **MySQL**, **MariaDB**, and **PostgreSQL** engine layers. +- Connection manager now tracks **persistent connection statistics** (attempts, success/failure, timing). +- Empty database passwords are now accepted for local setups. +- MySQL/MariaDB connections can auto-retry by enabling TLS when required by the server. + --- ## 🧭 Why PeterSQL? -Over the years, I have used **HeidiSQL** as my primary tool for working with +For years, I have used **HeidiSQL** as my primary tool for working with MySQL, MariaDB, SQLite, and other databases. -It is a tool I deeply appreciate: **streamlined**, **intuitive**, and -**powerful**. +It is streamlined, intuitive, and powerful. + +PeterSQL started as a personal challenge: +to recreate that same *spirit* in a **pure Python** application. -Rather than trying to compete with HeidiSQL, PeterSQL started as a personal -challenge: to recreate the same *spirit* in a **pure Python** application. +But PeterSQL is not meant to be a 1:1 replacement. -PeterSQL is not a 1:1 port. -It is a Python-first reinterpretation, built with different goals in mind. +Where HeidiSQL is Delphi-based and Windows-centric, +PeterSQL is: - 🐍 **Written entirely in Python** -- 🧩 **Built entirely in Python to enable easy modification and extension** -- 🎯 **Focused on simplicity and clarity**, inspired by HeidiSQL +- 🧩 **Easily modifiable and extensible** +- 🌍 **Cross-platform** +- 🎯 **Focused on clarity and simplicity** - 🆓 **Free and open source** -PeterSQL exists for developers who love HeidiSQL’s approach, but want a tool -that feels native to the Python ecosystem. +PeterSQL aims to feel natural for developers who live in the Python ecosystem +and appreciate lightweight, practical tools. + +--- + +## 🔭 Vision + +PeterSQL is evolving beyond a simple SQL client. + +Planned directions include: + +- 🧠 Smarter, scope-aware SQL autocomplete +- 📊 Visual schema / diagram viewer (inspired by tools like MySQL Workbench) +- 🔌 Extensible architecture for future tooling +- 🐍 Better integration with Python-based workflows + +The goal is not to replicate existing tools, +but to build a Python-native SQL workbench with its own identity. --- @@ -56,7 +91,33 @@ that feels native to the Python ecosystem. - [Python 3.14+](https://www.python.org/) - [wxPython 4.2.5](https://wxpython.org/) - native cross-platform interface -- [wxFormBuilder 4.2.1](https://github.com/wxFormBuilder/wxFormBuilder) - for the construction of the interface +- [wxFormBuilder 4.2.1](https://github.com/wxFormBuilder/wxFormBuilder) - UI construction + +--- + +## 🌍 Available Languages + +PeterSQL supports the following languages: + +- 🇺🇸 **English** (en_US) +- 🇮🇹 **Italiano** (it_IT) +- 🇫🇷 **Français** (fr_FR) +- 🇪🇸 **Español** (es_ES) +- 🇩🇪 **Deutsch** (de_DE) + +You can change the language in the application settings (Settings → General → Language). + +--- + +## 🧪 Test Coverage + +PeterSQL has **comprehensive integration tests** across all supported database engines covering Tables, Records, Columns, Indexes, Foreign Keys, Triggers, Views, and SSH tunnels. + +- 🏗️ **Granular base class architecture** - zero code duplication +- 🐛 **Bug detection** - tests have found multiple API inconsistencies +- ✅ **Full CRUD coverage** for core database objects + +For detailed test coverage matrix, statistics, architecture, and running instructions, see **[tests/README.md](tests/README.md)**. --- @@ -66,7 +127,7 @@ PeterSQL uses [uv](https://github.com/astral-sh/uv) for fast and reliable depend ### Prerequisites -- Python 3.11+ +- Python 3.14+ - uv (install with: `curl -LsSf https://astral.sh/uv/install.sh | sh`) ### Setup @@ -75,7 +136,6 @@ PeterSQL uses [uv](https://github.com/astral-sh/uv) for fast and reliable depend ```bash git clone https://github.com/gtripoli/petersql.git cd petersql - ``` 2. Install dependencies (including dev tools for testing): ```bash @@ -107,7 +167,7 @@ If `uv sync` fails because no compatible wxPython wheel is available for your pl This forces a source build and usually unblocks the setup. ```bash -uv pip install -U --reinstall wxPython==4.2.4 --no-binary wxPython +uv pip install -U --reinstall wxPython==4.2.5 --no-binary wxPython ``` ###### Once the build finishes, rerun `uv sync` so the refreshed environment picks up the manually installed wxPython. @@ -122,4 +182,4 @@ uv pip install -U --reinstall wxPython==4.2.4 --no-binary wxPython Main Frame - Indexes Main Frame - Foreign Keys Main Frame - Foreign Keys Columns -

\ No newline at end of file +

diff --git a/ROADMAP.md b/ROADMAP.md index 0d3f1bf..2b898e7 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,415 +1,134 @@ # PeterSQL — Development Roadmap -> **Last Updated:** 2026-02-11 -> **Based on:** PROJECT_STATUS.md analysis +> **Last Updated:** 2026-03-07 +> **Status Rule:** newly implemented features are tracked as **PARTIAL** until validated across supported versions. --- ## 🎯 Overview -This roadmap organizes remaining development tasks by priority, difficulty, and component. Tasks are structured to be easily trackable with checkboxes and notes. +This roadmap reflects the current project state and separates: + +1. features already implemented but still under validation, +2. true implementation gaps, +3. UI parity work. --- ## 📊 Priority Matrix -| Priority | Impact | Effort | Target | -|----------|--------|--------|--------| -| 🔴 **P0 - Critical** | High | Low- Medium | 1-2 weeks | -| 🟡 **P1 - High** | High | Medium | 2-4 weeks | -| 🟢 **P2 - Medium** | Medium | Medium-High | 1-2 months | -| 🔵 **P3 - Low** | Low | High | 2-3 months | +| Priority | Focus | Target | +|----------|-------|--------| +| 🔴 **P0 - Validation Now** | stabilize newly added engine features | 1-2 weeks | +| 🟡 **P1 - Engine Gaps** | close remaining CRUD parity gaps | 2-4 weeks | +| 🟢 **P2 - UI Completeness** | add missing editors for exposed objects | 1-2 months | +| 🔵 **P3 - Advanced Features** | schema/security/import-export roadmap | 2-3 months | --- -## 🔴 P0 - Critical Fixes (1-2 weeks) - -### Engine Layer +## 🔴 P0 - Validation Now -- [ ] **PostgreSQLFunction Class Implementation** - - **Engine:** PostgreSQL - - **Difficulty:** 🟢 Medium - - **Files:** `structures/engines/postgresql/database.py` - - **Notes:** - - Create full CRUD methods (create, read, update, drop) - - Follow pattern from MySQLFunction/MariaDBFunction - - Add parameters, returns, deterministic fields - - Test with real PostgreSQL functions +### Implemented recently (still PARTIAL) ---- +- [x] **PostgreSQL Function engine implementation** (PARTIAL) + - **Files:** `structures/engines/postgresql/database.py`, `structures/engines/postgresql/context.py` + - **Next:** validate behavior across supported PostgreSQL variants. -## 🟡 P1 - High Priority (2-4 weeks) - -### Engine Parity - -#### Check Constraints Implementation -- [ ] **MySQL Check Constraints** - - **Engine:** MySQL - - **Difficulty:** 🟢 Medium - - **Files:** `structures/engines/mysql/database.py`, `structures/engines/mysql/context.py` - - **Notes:** - - Implement `get_checks()` method in context - - Create `MySQLCheck` class - - Add check constraint introspection queries - -- [ ] **MariaDB Check Constraints** - - **Engine:** MariaDB - - **Difficulty:** 🟢 Medium - - **Files:** `structures/engines/mariadb/database.py`, `structures/engines/mariadb/context.py` - - **Notes:** - - Similar to MySQL implementation - - Test with MariaDB-specific syntax - -- [ ] **PostgreSQL Check Constraints** - - **Engine:** PostgreSQL - - **Difficulty:** 🟢 Medium +- [x] **PostgreSQL Procedure engine implementation** (PARTIAL) - **Files:** `structures/engines/postgresql/database.py`, `structures/engines/postgresql/context.py` - - **Notes:** - - Implement `get_checks()` method - - Create `PostgreSQLCheck` class - - Handle PostgreSQL check constraint syntax - -#### SSH Tunnel Support -- [ ] **MariaDB SSH Tunnel** - - **Engine:** MariaDB - - **Difficulty:** 🟡 Medium-High - - **Files:** `structures/ssh_tunnel.py`, `structures/engines/mariadb/context.py` - - **Notes:** - - Follow MySQL SSH tunnel pattern - - Test with different SSH configurations - - Add connection timeout handling - -- [ ] **PostgreSQL SSH Tunnel** - - **Engine:** PostgreSQL - - **Difficulty:** 🟡 Medium-High - - **Files:** `structures/ssh_tunnel.py`, `structures/engines/postgresql/context.py` - - **Notes:** - - Similar to MariaDB implementation - - Consider PostgreSQL-specific port requirements - -### Procedure Support -- [ ] **Procedure Implementation (All Engines)** - - **Engine:** MySQL, MariaDB, PostgreSQL - - **Difficulty:** 🟡 Medium-High - - **Files:** `structures/engines/*/database.py` - - **Notes:** - - Extend base `SQLProcedure` class - - Implement CRUD methods for each engine - - Add parameter handling - - Test with stored procedures + - **Next:** validate create/alter/drop and introspection consistency. + +- [x] **Check constraint implementations for MySQL/MariaDB/PostgreSQL** (PARTIAL) + - **Files:** + - `structures/engines/mysql/database.py`, `structures/engines/mysql/context.py` + - `structures/engines/mariadb/database.py`, `structures/engines/mariadb/context.py` + - `structures/engines/postgresql/database.py`, `structures/engines/postgresql/context.py` + - **Next:** cross-version validation matrix. + +- [x] **Connection reliability updates** (PARTIAL) + - **Scope:** persistent connection statistics, empty DB password support, TLS auto-retry (MySQL/MariaDB). + - **Files:** + - `structures/connection.py` + - `windows/dialogs/connections/model.py` + - `windows/dialogs/connections/view.py` + - **Next:** long-run behavioral validation. --- -## 🟢 P2 - Medium Priority (1-2 months) - -### UI Layer - Core Editors - -#### View Editor -- [ ] **View Create/Edit Dialog** - - **UI Component:** View Editor - - **Difficulty:** 🟢 Medium - - **Files:** `windows/main/view.py` (new), `windows/main/explorer.py` - - **Notes:** - - Create dialog similar to table editor - - SQL editor with syntax highlighting - - Preview functionality - - Validation for view SQL - -#### Trigger Editor -- [ ] **Trigger Create/Edit Dialog** - - **UI Component:** Trigger Editor - - **Difficulty:** 🟡 Medium-High - - **Files:** `windows/main/trigger.py` (new), `windows/main/explorer.py` - - **Notes:** - - Complex form with timing/event options - - SQL editor for trigger body - - Database-specific syntax support - - Test trigger validation - -#### Function Editor -- [ ] **Function Create/Edit Panel** - - **UI Component:** Function Editor - - **Difficulty:** 🟡 Medium-High - - **Files:** `windows/main/function.py` (new) - - **Notes:** - - Parameter definition interface - - Return type selection - - SQL editor for function body - - Database-specific options (deterministic, etc.) - -#### Procedure Editor -- [ ] **Procedure Create/Edit Panel** - - **UI Component:** Procedure Editor - - **Difficulty:** 🟡 Medium-High - - **Files:** `windows/main/procedure.py` (new) - - **Notes:** - - Similar to function editor - - IN/OUT/INOUT parameter handling - - SQL editor for procedure body - -### Database Management -- [ ] **Database Create/Drop Operations** - - **UI Component:** Database Management - - **Difficulty:** 🟢 Medium - - **Files:** `windows/main/database.py`, `structures/engines/*/context.py` - - **Notes:** - - Implement `create_database()` in all engines - - Add database creation dialog - - Database drop confirmation - - Permission checking +## 🟡 P1 - Engine Gaps ---- +- [ ] **MySQL Procedure implementation** + - **Current blocker:** `build_empty_procedure` not implemented in MySQL context. + - **Files:** `structures/engines/mysql/context.py`, `structures/engines/mysql/database.py` -## 🔵 P3 - Low Priority (2-3 months) - -### SSH Tunnel Testing & Performance - -#### SSH Tunnel Test Coverage -- [x] **MySQL SSH Tunnel Tests** ✅ COMPLETED - - **Engine:** MySQL - - **Difficulty:** 🟢 Medium - - **Files:** `tests/engines/mysql/test_integration.py` - - **Status:** ✅ Complete - - **Notes:** - - Basic CRUD operations through SSH tunnel - - Transaction support testing - - Error handling validation - - Integration with testcontainers - -- [x] **MariaDB SSH Tunnel Tests** ✅ COMPLETED - - **Engine:** MariaDB - - **Difficulty:** 🟢 Medium - - **Files:** `tests/engines/mariadb/test_integration.py` - - **Status:** ✅ Complete - - **Notes:** - - Basic CRUD operations through SSH tunnel - - Transaction support testing - - Error handling validation - - Integration with testcontainers - - SSH tunnel implementation completed - -- [x] **PostgreSQL SSH Tunnel Tests** ✅ COMPLETED - - **Engine:** PostgreSQL - - **Difficulty:** 🟢 Medium - - **Files:** `tests/engines/postgresql/test_integration.py` - - **Status:** ✅ Complete - - **Notes:** - - Basic CRUD operations through SSH tunnel - - Transaction support testing - - Error handling validation - - Integration with testcontainers - - SSH tunnel implementation completed - -#### SSH Tunnel Performance & Reliability -- [ ] **SSH Tunnel Performance Benchmarks** - - **Feature:** Performance Testing - - **Difficulty:** 🟡 Medium-High - - **Files:** `tests/performance/ssh_tunnel_performance.py` - - **Notes:** - - Latency measurements - - Throughput testing - - Connection pooling impact - - Resource usage monitoring - -- [ ] **SSH Tunnel Error Recovery** - - **Feature:** Reliability Testing - - **Difficulty:** 🟡 Medium-High - - **Files:** `tests/engines/*/test_ssh_tunnel_resilience.py` - - **Notes:** - - Network interruption scenarios - - Connection timeout handling - - Automatic reconnection testing - - Graceful degradation - -### Advanced Features - -#### Schema Management (PostgreSQL) -- [ ] **PostgreSQL Schema CRUD** - - **Engine:** PostgreSQL - - **Difficulty:** 🔵 High - - **Files:** `structures/engines/postgresql/database.py`, `structures/engines/postgresql/context.py` - - **Notes:** - - Create `SQLSchema` base class - - Implement `PostgreSQLSchema` class - - Schema operations in UI - - Object movement between schemas - -#### Sequence Management (PostgreSQL) -- [ ] **PostgreSQL Sequence CRUD** - - **Engine:** PostgreSQL - - **Difficulty:** 🔵 High - - **Files:** `structures/engines/postgresql/database.py` (new classes) - - **Notes:** - - Create `SQLSequence` base class - - Implement `PostgreSQLSequence` - - Sequence editor in UI - - Integration with table columns - -#### User/Role Management -- [ ] **User/Role CRUD (All Engines)** - - **Engine:** All - - **Difficulty:** 🔵 High - - **Files:** New files in each engine directory - - **Notes:** - - Create `SQLUser`, `SQLRole` base classes - - Engine-specific implementations - - User management UI - - Permission handling - -#### Privileges/Grants -- [ ] **Grant Management System** - - **Engine:** All - - **Difficulty:** 🔵 High - - **Files:** New files, UI components - - **Notes:** - - Create `SQLGrant` class - - Grant/revoke operations - - Permission matrix UI - - Role-based access control - -### Import/Export Features -- [ ] **Database Dump/Restore** - - **Feature:** Import/Export - - **Difficulty:** 🔵 High - - **Files:** New scripts, UI components - - **Notes:** - - Support for multiple dump formats - - Progress indicators - - Compression options - - Scheduled exports - -- [ ] **Data Import/Export** - - **Feature:** Data Transfer - - **Difficulty:** 🔵 High - - **Files:** New files, UI components - - **Notes:** - - CSV/JSON/XML support - - Bulk operations - - Data mapping interface - - Import validation - -### Advanced PostgreSQL Features -- [ ] **Materialized Views** - - **Engine:** PostgreSQL - - **Difficulty:** 🔵 High - - **Notes:** - - Refresh scheduling - - Performance monitoring - -- [ ] **Table Partitioning** - - **Engine:** PostgreSQL - - **Difficulty:** 🔵 High - - **Notes:** - - Partition strategy UI - - Partition management - -- [ ] **Extensions Management** - - **Engine:** PostgreSQL - - **Difficulty:** 🔵 High - - **Notes:** - - Extension installation/removal - - Version management +- [ ] **MariaDB Procedure implementation** + - **Current blocker:** `build_empty_procedure` not implemented in MariaDB context. + - **Files:** `structures/engines/mariadb/context.py`, `structures/engines/mariadb/database.py` ---- +- [ ] **Database create/drop API parity** + - **Current state:** list/read available, lifecycle operations missing in contexts. + - **Files:** `structures/engines/*/context.py` -## 🛠️ Development Guidelines +--- -### Task Completion Criteria +## 🟢 P2 - UI Completeness -For each task, ensure: +- [x] **View Create/Edit Dialog** + - **Status:** DONE + - **Files:** `windows/main/tabs/view.py`, `helpers/sql.py` -- [ ] **Engine Layer** - - [ ] Dataclass exists with all required fields - - [ ] CRUD methods implemented and tested - - [ ] Integration tests pass with real database - - [ ] Error handling with user-friendly messages +- [ ] **Trigger Create/Edit UI** + - **Current state:** explorer visibility exists, editor panel missing. -- [ ] **UI Layer** - - [ ] Object appears in Explorer tree - - [ ] Create/edit/delete dialogs functional - - [ ] Changes reflect immediately in UI - - [ ] Validation and error handling +- [ ] **Function Create/Edit UI** + - **Current state:** explorer visibility exists, editor panel missing. -- [ ] **Cross-Cutting** - - [ ] Code follows `CODE_STYLE.md` - - [ ] No regressions in existing tests - - [ ] Documentation updated - - [ ] Transaction support where applicable +- [ ] **Procedure Create/Edit UI** + - **Current state:** explorer visibility exists, editor panel missing. -### Testing Strategy +- [ ] **Database Create/Drop UI** + - **Dependency:** engine create/drop API parity. -- **Unit Tests:** Each new class/method -- **Integration Tests:** Real database operations -- **UI Tests:** User interaction workflows -- **Performance Tests:** Large dataset handling +--- -### Review Process +## 🔵 P3 - Advanced Features -1. **Code Review:** Peer review required -2. **Testing:** All tests must pass -3. **Documentation:** Update relevant docs -4. **Changelog:** Add changes to CHANGELOG.md +- [ ] PostgreSQL schema CRUD +- [ ] PostgreSQL sequence CRUD +- [ ] User/role management +- [ ] Privileges/grants management +- [ ] Import/export workflows (dump/restore + structured data) +- [ ] PostgreSQL advanced objects (materialized views, partitioning, extensions) --- -## 📈 Progress Tracking +## 🛠️ Validation and Completion Criteria -### Current Status +Before moving a PARTIAL item to DONE: -- **P0 Tasks:** 1/1 completed (100%) -- **P1 Tasks:** 0/5 completed (0%) -- **P2 Tasks:** 0/6 completed (0%) -- **P3 Tasks:** 3/8 completed (37.5%) - -### Recent Progress - -- ✅ **MySQL SSH Tunnel Tests** (2026-02-11) - - Basic CRUD operations through SSH tunnel - - Transaction support validation - - Error handling scenarios - - Integration with testcontainers - - 3 focused SSH tests added to integration suite - -- ✅ **MariaDB SSH Tunnel Implementation** (2026-02-11) - - SSH tunnel support added to MariaDB context - - Test-driven development approach - - 3 SSH tunnel tests implemented - - Full CRUD operations through SSH tunnel - - Transaction and error handling validation - -- ✅ **PostgreSQL SSH Tunnel Implementation** (2026-02-11) - - SSH tunnel support added to PostgreSQL context - - Test-driven development approach - - 3 SSH tunnel tests implemented - - Full CRUD operations through SSH tunnel - - Transaction and error handling validation - -### Milestones - -- **v0.1.0:** Complete P0 tasks -- **v0.2.0:** Complete P1 tasks -- **v0.3.0:** Complete P2 tasks -- **v1.0.0:** Complete P3 tasks +- [ ] Integration tests pass on supported versions. +- [ ] Behavior is stable in repeated manual workflows. +- [ ] No regressions in current engine/UI suites. +- [ ] Documentation is aligned (`README`, `PROJECT_STATUS`, `ROADMAP`). --- -## 📝 Notes +## 📈 Progress Snapshot + +### Current Status -### Difficulty Legend -- 🟢 **Medium:** Well-defined requirements, existing patterns to follow -- 🟡 **Medium-High:** Complex requirements, some research needed -- 🔵 **High:** Complex requirements, significant research/testing +- **P0 implemented (partial):** 4/4 +- **P1 gaps closed:** 0/3 +- **P2 UI tasks complete:** 1/5 +- **P3 advanced tasks complete:** 0/6 -### Dependencies -- UI tasks depend on corresponding engine implementations -- PostgreSQL features may require additional research -- SSH tunnel implementations should follow existing MySQL pattern +### Recent Highlights -### Time Estimates -- Estimates assume 1 developer working full-time -- Buffer time included for testing and debugging -- UI tasks may take longer due to user experience considerations +- PostgreSQL Function and Procedure engine classes added. +- Check constraint support added for MySQL, MariaDB, PostgreSQL. +- Connection statistics and TLS auto-retry behavior added in connection manager. +- CI workflow split into test/nightly-update/release lanes. --- -*This roadmap is a living document. Update as priorities change and tasks are completed.* +*This roadmap is a living document and should be updated whenever a PARTIAL item is validated or a new gap is identified.* diff --git a/VERSIONING_POLICY.md b/VERSIONING_POLICY.md new file mode 100644 index 0000000..10d7d85 --- /dev/null +++ b/VERSIONING_POLICY.md @@ -0,0 +1,175 @@ +# Versioning Policy + +## 1. Purpose + +This document defines how to bump versions for any project or +repository. If a repository requires different rules, it **MUST** +explicitly define a local override document. + +------------------------------------------------------------------------ + +## 2. Version Format + +All projects **MUST** use the following format: + + MAJOR.MINOR.PATCH + +Optional pre-release suffix: + + -alpha.N + -beta.N + -rc.N + +### Examples + + 1.4.2 + 2.0.0-rc.1 + 0.5.0-alpha.3 + +------------------------------------------------------------------------ + +## 3. Bump Rules + +### 3.1 PATCH (x.y.Z) + +Use PATCH for backward-compatible bug fixes and safe internal +improvements that do **NOT** change public behavior or contracts. + +#### Good examples + + 1.0.0 → 1.0.1 (bug fix) + 1.2.3 → 1.2.4 (security fix) + 2.4.1 → 2.4.2 (internal refactor without behavior change) + +#### Bad examples + + 1.2.3 → 1.2.4 while changing API response schema + 1.2.3 → 1.2.4 while removing a feature + +------------------------------------------------------------------------ + +### 3.2 MINOR (x.Y.0) + +Use MINOR for backward-compatible feature additions. + +#### Good examples + + 1.0.1 → 1.1.0 (new optional feature) + 2.3.4 → 2.4.0 (new endpoint, old clients still work) + 3.2.5 → 3.3.0 (new configuration option, not required) + +#### Bad examples + + 1.2.0 → 1.3.0 if existing clients break + 1.2.0 → 1.3.0 when default behavior changes incompatibly + +------------------------------------------------------------------------ + +### 3.3 MAJOR (X.0.0) + +Use MAJOR for breaking changes. + +A change is breaking if it may require modifications in consumers. + +#### Good examples + + 1.4.3 → 2.0.0 (breaking API change) + 2.1.0 → 3.0.0 (removed feature) + 3.5.2 → 4.0.0 (database schema incompatible change) + 5.0.0 → 6.0.0 (default behavior changed incompatibly) + +#### Bad examples + + 1.4.3 → 2.0.0 for a simple bug fix + 1.4.3 → 2.0.0 for adding an optional feature + +------------------------------------------------------------------------ + +## 4. Reset Rules (Mandatory) + +When bumping: + +- MAJOR → reset MINOR and PATCH to 0 +- MINOR → reset PATCH to 0 +- PATCH → no reset required + +#### Good examples + + 1.0.1 → 1.1.0 + 1.1.9 → 2.0.0 + 2.3.4 → 2.3.5 + +#### Forbidden by policy + + 1.0.1 → 1.1.2 + 1.2.3 → 2.1.0 + +Correct sequence: + + 1.0.1 → 1.1.0 → 1.1.1 → 1.1.2 + +------------------------------------------------------------------------ + +## 5. Pre-1.0.0 Versions (0.x.y) + +Before 1.0.0, breaking changes are expected more frequently. + +### Recommended behavior + +- Breaking change → bump MINOR (0.3.4 → 0.4.0) +- Bug fix → bump PATCH (0.3.4 → 0.3.5) + +### Pre-release usage + + 0.4.0-alpha.1 → early unstable version + 0.4.0-beta.1 → feature-complete preview + 0.4.0-rc.1 → release candidate, only bug fixes allowed + +Nightly builds **SHOULD** use `-alpha.N` (or `-dev.N`) and **MUST NOT** +be published as stable releases. + +------------------------------------------------------------------------ + +## 6. Breaking Change Definition + +A change is breaking if it can require changes in consumers, including: + +- Public API modification +- Response structure change +- Configuration requirement change +- Removed functionality +- Default behavior change +- Schema incompatibility +- Environment variable changes (new required vars, renamed/removed + vars) + +### Desktop / PyInstaller-specific breaking changes + +Also considered breaking: + +- Configuration directory changes without migration +- Workspace/session schema changes without backward compatibility +- Removed or renamed CLI flags +- Removed or renamed required resource files (themes, templates, etc.) + +If in doubt, treat the change as breaking and bump MAJOR (or MINOR in +0.x.y). + +------------------------------------------------------------------------ + +## 7. No Skipping Rule + +Versions **MUST** follow logical semantic transitions. + +#### Good examples + + 1.2.3 → 1.2.4 + 1.2.4 → 1.3.0 + 1.3.0 → 2.0.0 + +#### Forbidden by policy + + 1.2.3 → 1.4.7 + 1.0.1 → 1.1.2 (without intermediate steps) + +All increments **MUST** be semantically justified. diff --git a/assets/trigger_options_matrix.md b/assets/trigger_options_matrix.md new file mode 100644 index 0000000..e21269e --- /dev/null +++ b/assets/trigger_options_matrix.md @@ -0,0 +1,96 @@ +# Trigger Options Matrix + +Legend: +- ✅ Supported +- ❌ Not supported +- ⚠️ Supported with differences / restrictions + +Engines covered: +- MySQL +- MariaDB +- PostgreSQL +- Oracle +- SQLite + +--- + +## Core fields (practical UI) + +| Field / Option | MySQL | MariaDB | PostgreSQL | Oracle | SQLite | +|---|---:|---:|---:|---:|---:| +| Name | ✅ | ✅ | ✅ | ✅ | ✅ | +| Target object type | Table ✅ | Table ✅ | Table/View/Foreign table ✅ | Table/View/Schema/Database ✅ | Table/View ✅ | +| Schema / owner | ⚠️ database name | ⚠️ database name | ✅ schema | ✅ schema | ✅ schema | +| Timing | BEFORE/AFTER ✅ | BEFORE/AFTER ✅ | BEFORE/AFTER/INSTEAD OF ✅ | BEFORE/AFTER/INSTEAD OF ✅ | BEFORE/AFTER/INSTEAD OF ✅ | +| Events | INSERT/UPDATE/DELETE ✅ | INSERT/UPDATE/DELETE ✅ | INSERT/UPDATE/DELETE/TRUNCATE ✅ | INSERT/UPDATE/DELETE + many DDL/system events ⚠️ | INSERT/UPDATE/DELETE ✅ | +| UPDATE OF columns | ❌ | ❌ | ✅ | ✅ | ✅ | +| Level | ROW only ✅ | ROW only ✅ | ROW or STATEMENT ✅ | ROW or STATEMENT ✅ | ROW only ✅ | +| WHEN condition | ❌ | ❌ | ✅ | ✅ | ✅ | +| Body language | SQL (BEGIN..END) ✅ | SQL (BEGIN..END) ✅ | calls FUNCTION/PROCEDURE ✅ | PL/SQL (or call proc) ✅ | SQL statements in BEGIN..END ✅ | +| Create-if-exists handling | ❌ (use DROP) | ✅ OR REPLACE / IF NOT EXISTS | ✅ OR REPLACE | ✅ OR REPLACE | ✅ IF NOT EXISTS | +| Definer / security context | ✅ DEFINER | ✅ DEFINER | ❌ | ❌ | ❌ | +| Execution order control | ✅ FOLLOWS/PRECEDES | ✅ FOLLOWS/PRECEDES | ❌ (name order) | ✅ FOLLOWS/PRECEDES | ❌ | +| Transition tables / REFERENCING | ❌ | ❌ | ✅ REFERENCING NEW/OLD TABLE | ⚠️ (uses :NEW/:OLD, compound triggers) | ❌ | +| Constraint/deferrable trigger | ❌ | ❌ | ✅ CONSTRAINT + DEFERRABLE | ❌ | ❌ | +| TEMP trigger | ❌ | ❌ | ❌ | ❌ | ✅ TEMP/TEMPORARY | +| Enable/disable at create | ❌ | ❌ | ❌ (enable/disable via ALTER) | ✅ ENABLE/DISABLE | ❌ | + +Notes: +- **MySQL triggers are row-level only** (the syntax defines action time BEFORE/AFTER “for each row”). +- **PostgreSQL supports INSTEAD OF triggers on views** and supports `TRUNCATE` triggers. +- **SQLite supports TEMP triggers, `IF NOT EXISTS`, `UPDATE OF col...`, `WHEN expr`, and row-level only.** +- **Oracle has very broad trigger types** (DML, DDL, system/database events, compound triggers, etc.); the UI above is the “DML view” subset unless you decide to expose advanced Oracle-specific triggers. + +--- + +## Engine-specific clauses (what to show/hide) + +### MySQL +- `DEFINER = user` ✅ +- `FOLLOWS other_trigger` / `PRECEDES other_trigger` ✅ +- Timing: `BEFORE` / `AFTER` ✅ +- Events: `INSERT` / `UPDATE` / `DELETE` ✅ +- Level: **ROW only** ✅ + +### MariaDB +- `DEFINER = ...` ✅ +- `OR REPLACE` ✅ and `IF NOT EXISTS` ✅ +- `FOLLOWS` / `PRECEDES` ✅ +- Timing/events/level like MySQL (ROW only) ✅ + +### PostgreSQL +- `CREATE [OR REPLACE] [CONSTRAINT] TRIGGER ...` ✅ +- Timing: `BEFORE` / `AFTER` / `INSTEAD OF` ✅ +- Events: includes `TRUNCATE` ✅ +- `UPDATE OF col...` ✅ +- `FOR EACH ROW` / `FOR EACH STATEMENT` ✅ +- `WHEN (condition)` ✅ +- `REFERENCING ... TABLE AS ...` (transition tables) ✅ +- Deferrable constraint triggers ✅ + +### Oracle (DML-focused subset) +- `CREATE OR REPLACE TRIGGER ...` ✅ +- Timing points (row/statement) ✅ +- `INSTEAD OF` triggers on views ✅ +- `FOLLOWS` / `PRECEDES` ✅ +- `ENABLE` / `DISABLE` ✅ +- Plus **many Oracle-only trigger kinds** (schema/database/system/DDL), which you can keep out of the “common UI”. + +### SQLite +- `CREATE [TEMP|TEMPORARY] TRIGGER [IF NOT EXISTS] ...` ✅ +- Timing: `BEFORE` / `AFTER` / `INSTEAD OF` ✅ +- `UPDATE OF col...` ✅ +- `FOR EACH ROW` only ✅ +- `WHEN expr` ✅ +- No definer/security/order controls. + +--- + +## Sources (for your SQL generator / docs) +- MySQL `CREATE TRIGGER`: DEFINER, timing/events, and FOLLOWS/PRECEDES order clause. +- MySQL security note: triggers have no `SQL SECURITY` characteristic; they always execute in definer context. +- MariaDB `CREATE TRIGGER`: OR REPLACE, IF NOT EXISTS, DEFINER, FOLLOWS/PRECEDES. +- PostgreSQL `CREATE TRIGGER`: OR REPLACE, CONSTRAINT triggers, TRUNCATE, UPDATE OF, ROW/STATEMENT, WHEN, transition tables. +- SQLite `CREATE TRIGGER`: TEMP, IF NOT EXISTS, INSTEAD OF, UPDATE OF, FOR EACH ROW, WHEN. + +(If you want, I can produce a second MD that includes the exact SQL synopsis blocks per engine.) diff --git a/assets/view_options_matrix.md b/assets/view_options_matrix.md new file mode 100644 index 0000000..98f88c6 --- /dev/null +++ b/assets/view_options_matrix.md @@ -0,0 +1,119 @@ +# View Options Matrix + +Legend: - Supported - Not supported - Supported with differences + +------------------------------------------------------------------------ + +## Common Fields + + ----------------------------------------------------------------------------- + Field MySQL MariaDB PostgreSQL Oracle SQLite + ----------- ----------- ------------ ---------------- ----------- ----------- + Name Supported Supported Supported Supported Supported + + SELECT Supported Supported Supported Supported Supported + statement + ----------------------------------------------------------------------------- + +------------------------------------------------------------------------ + +## Schema + + Field MySQL MariaDB PostgreSQL Oracle SQLite + -------- --------- --------- ------------ ----------- --------------- + Schema Partial Partial Supported Supported Not supported + +- MySQL/MariaDB use database-qualified names (db.view), not true + schemas like PostgreSQL/Oracle. + +------------------------------------------------------------------------ + +## DEFINER + + -------------------------------------------------------------------------------- + Field MySQL MariaDB PostgreSQL Oracle SQLite + -------------- ----------- ------------ ---------------- ----------- ----------- + DEFINER=user Supported Supported Not supported Not Not + supported supported + + -------------------------------------------------------------------------------- + +------------------------------------------------------------------------ + +## SQL SECURITY + + ---------------------------------------------------------------------------- + Field MySQL MariaDB PostgreSQL Oracle SQLite + ---------- ----------- ------------ ---------------- ----------- ----------- + SQL Supported Supported Not supported Not Not + SECURITY supported supported + {DEFINER / + INVOKER} + + ---------------------------------------------------------------------------- + +------------------------------------------------------------------------ + +## ALGORITHM + + ----------------------------------------------------------------------------- + Field MySQL MariaDB PostgreSQL Oracle SQLite + ----------- ----------- ------------ ---------------- ----------- ----------- + ALGORITHM Supported Supported Not supported Not Not + supported supported + + ----------------------------------------------------------------------------- + +Values: - UNDEFINED - MERGE - TEMPTABLE + +------------------------------------------------------------------------ + +## CHECK OPTION / CONSTRAINT + + ---------------------------------------------------------------------------- + Option MySQL MariaDB PostgreSQL Oracle SQLite + ---------- ----------- ------------ ---------------- ----------- ----------- + WITH CHECK Supported Supported Supported Partial Not + OPTION supported + + LOCAL Supported Supported Supported Not Not + supported supported + + CASCADED Supported Supported Supported Not Not + supported supported + + READ ONLY Not Not Not supported Supported Not + supported supported supported + + CHECK Not Not Not supported Supported Not + OPTION supported supported supported + ONLY + ---------------------------------------------------------------------------- + +Notes: - MySQL/MariaDB/PostgreSQL: WITH \[CASCADED \| LOCAL\] CHECK +OPTION - Oracle: WITH READ ONLY or WITH CHECK OPTION - SQLite: not +supported + +------------------------------------------------------------------------ + +## SECURITY BARRIER + + ------------------------------------------------------------------------------------ + Field MySQL MariaDB PostgreSQL Oracle SQLite + ------------------ ----------- ------------ ---------------- ----------- ----------- + SECURITY_BARRIER Not Not Supported Not Not + supported supported supported supported + + ------------------------------------------------------------------------------------ + +------------------------------------------------------------------------ + +## FORCE + + ---------------------------------------------------------------------------- + Field MySQL MariaDB PostgreSQL Oracle SQLite + ---------- ----------- ------------ ---------------- ----------- ----------- + FORCE Not Not Not supported Supported Not + supported supported supported + + ---------------------------------------------------------------------------- diff --git a/constants.py b/constants.py new file mode 100644 index 0000000..45eaa7c --- /dev/null +++ b/constants.py @@ -0,0 +1,42 @@ +"""Global constants for PeterSQL application.""" + +import os +from pathlib import Path +from enum import Enum + + +WORKDIR = Path(os.path.abspath(os.path.dirname(__file__))) + + +class LogLevel(Enum): + DEBUG = "DEBUG" + INFO = "INFO" + WARNING = "WARNING" + ERROR = "ERROR" + + +class Language(Enum): + EN_US = ("en_US", "English") + IT_IT = ("it_IT", "Italiano") + FR_FR = ("fr_FR", "Français") + ES_ES = ("es_ES", "Español") + DE_DE = ("de_DE", "Deutsch") + + def __init__(self, code: str, label: str): + self.code = code + self.label = label + + @classmethod + def get_codes(cls) -> list[str]: + return [lang.code for lang in cls] + + @classmethod + def get_labels(cls) -> list[str]: + return [lang.label for lang in cls] + + @classmethod + def from_code(cls, code: str) -> "Language": + for lang in cls: + if lang.code == code: + return lang + return cls.EN_US diff --git a/helpers/__init__.py b/helpers/__init__.py index 6ab07f3..bd09749 100644 --- a/helpers/__init__.py +++ b/helpers/__init__.py @@ -1,11 +1,13 @@ import enum +import os +import sys from typing import Callable +from pathlib import Path from gettext import pgettext -import babel.numbers - import wx +import babel.numbers from helpers.observables import Observable @@ -18,7 +20,11 @@ class SizeUnit(enum.Enum): TERABYTE = pgettext("unit", "TB") -def wx_colour_to_hex(colour: wx.Colour): +def wx_colour_to_hex(colour): + if isinstance(colour, str): + if colour.startswith('#'): + return colour + return f"#{colour}" return f"#{colour.Red():02x}{colour.Green():02x}{colour.Blue():02x}" @@ -39,21 +45,30 @@ def bytes_to_human(bytes: float, locale: str = "en_US") -> str: return f"{formatted_number} {units[index].value}" -def wx_call_after_debounce(*observables: Observable, callback: Callable, wait_time: float = 0.4): - waiting = False +def get_base_path(base_path: Path) -> Path: + if getattr(sys, "frozen", False): + return Path(sys.executable).parent + + return base_path + + +def get_resource_path(base_path: Path, *paths: str) -> Path: + if hasattr(sys, "_MEIPASS"): + return Path(sys._MEIPASS).joinpath(*paths) + + return get_base_path(base_path).joinpath(*paths) + + +def get_config_dir() -> Path: + base: str = os.environ.get("XDG_CONFIG_HOME", str(Path.home() / ".config")) + return Path(base) / "petersql" - def _debounced(*args, **kwargs): - nonlocal waiting - if not waiting: - waiting = True - def call_and_reset(): - nonlocal waiting - callback(*args, **kwargs) - waiting = False +def get_data_dir() -> Path: + base: str = os.environ.get("XDG_DATA_HOME", str(Path.home() / ".local" / "share")) + return Path(base) / "petersql" - wx.CallAfter(call_and_reset) - for obs in observables: - setattr(obs, '_debounce_callback', _debounced) - obs.subscribe(_debounced) +def get_cache_dir() -> Path: + base: str = os.environ.get("XDG_CACHE_HOME", str(Path.home() / ".cache")) + return Path(base) / "petersql" \ No newline at end of file diff --git a/helpers/bindings.py b/helpers/bindings.py index 45e2adb..e05b25c 100644 --- a/helpers/bindings.py +++ b/helpers/bindings.py @@ -4,6 +4,7 @@ from typing import Optional, Union, Any, TypeAlias, Callable import wx +import wx.stc from helpers.observables import Observable, CallbackEvent @@ -11,7 +12,10 @@ CONTROL_BIND_VALUE: TypeAlias = Union[wx.TextCtrl, wx.SpinCtrl, wx.CheckBox] CONTROL_BIND_PATH: TypeAlias = Union[wx.FilePickerCtrl, wx.DirPickerCtrl] CONTROL_BIND_SELECTION: TypeAlias = wx.Choice -CONTROLS: TypeAlias = Union[CONTROL_BIND_LABEL, CONTROL_BIND_VALUE, CONTROL_BIND_PATH, CONTROL_BIND_SELECTION] +CONTROL_BIND_COMBO: TypeAlias = wx.ComboBox +CONTROL_BIND_STC: TypeAlias = wx.stc.StyledTextCtrl +CONTROL_BIND_RADIO_GROUP: TypeAlias = list[wx.RadioButton] +CONTROLS: TypeAlias = Union[CONTROL_BIND_LABEL, CONTROL_BIND_VALUE, CONTROL_BIND_PATH, CONTROL_BIND_SELECTION, CONTROL_BIND_COMBO, CONTROL_BIND_STC, CONTROL_BIND_RADIO_GROUP] class AbstractBindControl(abc.ABC): @@ -77,6 +81,7 @@ def __init__(self, control: CONTROL_BIND_VALUE, observable: Observable): event = wx.EVT_SPINCTRL elif isinstance(control, wx.CheckBox): event = wx.EVT_CHECKBOX + super().__init__(control, observable, event=event) def clear(self) -> None: @@ -151,6 +156,67 @@ def set(self, value: Any) -> None: self.control.SetPath(str(value)) +class BindComboControl(AbstractBindControl): + def __init__(self, control: CONTROL_BIND_COMBO, observable: Observable): + super().__init__(control, observable, event=wx.EVT_TEXT) + + def clear(self) -> None: + self.control.SetValue(self.initial if self.initial is not None else "") + + def get(self) -> str: + return self.control.GetValue() + + def set(self, value: Any) -> None: + self.control.SetValue(str(value)) + + +class BindStyledTextControl(AbstractBindControl): + def __init__(self, control: CONTROL_BIND_STC, observable: Observable): + super().__init__(control, observable, event=wx.stc.EVT_STC_CHANGE) + + def clear(self) -> None: + self.control.SetText(self.initial if self.initial is not None else "") + + def get(self) -> str: + return self.control.GetText() + + def set(self, value: Any) -> None: + self.control.SetText(str(value)) + + +class BindRadioGroupControl(AbstractBindControl): + def __init__(self, radios: CONTROL_BIND_RADIO_GROUP, observable: Observable): + self.radios = radios + self.control = radios[0] if radios else None + self.initial = self.get() + self.observable = observable + + self.observable.subscribe(self._set_value, CallbackEvent.AFTER_CHANGE) + + for radio in self.radios: + radio.Bind(wx.EVT_RADIOBUTTON, self.handle_control_event) + + if (value := self.observable.get_value()) is not None: + self.set(value) + + def clear(self) -> None: + if self.radios: + self.radios[0].SetValue(True) + + def get(self) -> Optional[str]: + for radio in self.radios: + if radio.GetValue(): + return radio.GetLabel() + return None + + def set(self, value: Any) -> None: + value_str = str(value).upper() + for radio in self.radios: + if radio.GetLabel().upper() == value_str: + radio.SetValue(True) + return + + class AbstractMetaModel(abc.ABCMeta): def __init__(cls, name, bases, attrs): super().__init__(name, bases, attrs) @@ -171,6 +237,12 @@ def bind_control(self, control: CONTROLS, observable: Observable): BindPathControl(control, observable) elif isinstance(control, wx.Choice): BindSelectionControl(control, observable) + elif isinstance(control, wx.ComboBox): + BindComboControl(control, observable) + elif isinstance(control, wx.stc.StyledTextCtrl): + BindStyledTextControl(control, observable) + elif isinstance(control, list) and control and isinstance(control[0], wx.RadioButton): + BindRadioGroupControl(control, observable) self.observables.append(observable) @@ -185,4 +257,24 @@ def bind_controls(self, **controls: Union[CONTROLS, Tuple[CONTROLS, Dict]]): def subscribe(self, callback: Callable): for observable in self.observables: - observable.subscribe(callback) \ No newline at end of file + observable.subscribe(callback) + + +def wx_call_after_debounce(*observables: Observable, callback: Callable, wait_time: float = 0.4): + waiting = False + + def _debounced(*args, **kwargs): + nonlocal waiting + if not waiting: + waiting = True + + def call_and_reset(): + nonlocal waiting + callback(*args, **kwargs) + waiting = False + + wx.CallAfter(call_and_reset) + + for obs in observables: + setattr(obs, '_debounce_callback', _debounced) + obs.subscribe(_debounced) diff --git a/helpers/dataview.py b/helpers/dataview.py index 38991f4..67a78a9 100644 --- a/helpers/dataview.py +++ b/helpers/dataview.py @@ -223,7 +223,7 @@ def __init__(self, column_count: Optional[int] = None): AbstractBaseDataModel.__init__(self, column_count) wx.dataview.DataViewIndexListModel.__init__(self) - def _load(self, data: List[Any]): + def _load(self, data: list[Any]): self.clear() AbstractBaseDataModel.load(self, data) diff --git a/helpers/observables.py b/helpers/observables.py index e6b18c5..4fa5d52 100755 --- a/helpers/observables.py +++ b/helpers/observables.py @@ -2,6 +2,7 @@ import enum import inspect import weakref +from threading import Timer from typing import Callable, TypeVar, Generic, Any, SupportsIndex, Union, Optional, cast, Self, Hashable @@ -406,19 +407,14 @@ def get_value(self, *attributes: str) -> Any: def debounce(*observables: Observable, callback: Callable, wait_time: float = 0.4): - waiting = False + timer: Optional[Timer] = None def _debounced(*args, **kwargs): - nonlocal waiting - if not waiting: - waiting = True - - def call_and_reset(): - nonlocal waiting - callback(*args, **kwargs) - waiting = False - - wx.CallAfter(call_and_reset) + nonlocal timer + if timer: + timer.cancel() + timer = Timer(wait_time, callback, args, kwargs) + timer.start() for obs in observables: setattr(obs, '_debounce_callback', _debounced) diff --git a/helpers/repository.py b/helpers/repository.py new file mode 100644 index 0000000..ba54976 --- /dev/null +++ b/helpers/repository.py @@ -0,0 +1,24 @@ +from pathlib import Path +from typing import Any, Generic, TypeVar + +import yaml + + +T = TypeVar('T') + + +class YamlRepository(Generic[T]): + def __init__(self, config_file: Path): + self._config_file = config_file + + def _read_yaml(self) -> dict[str, Any]: + try: + with open(self._config_file, 'r') as file: + data = yaml.full_load(file) + return data or {} + except Exception: + return {} + + def _write_yaml(self, data: Any) -> None: + with open(self._config_file, 'w') as file: + yaml.dump(data, file, sort_keys=False) diff --git a/helpers/sql.py b/helpers/sql.py new file mode 100644 index 0000000..5bcba1c --- /dev/null +++ b/helpers/sql.py @@ -0,0 +1,11 @@ +from typing import Optional + +import sqlglot + + +def format_sql(sql: str, dialect: Optional[str] = None) -> str: + try: + parsed = sqlglot.parse_one(sql, read=dialect) + return parsed.sql(pretty=True) + except Exception: + return sql diff --git a/icons/16x16/server-oracle.png b/icons/16x16/server-oracle.png new file mode 100644 index 0000000..4a1aea9 Binary files /dev/null and b/icons/16x16/server-oracle.png differ diff --git a/icons/__init__.py b/icons/__init__.py index 083896c..183e49f 100755 --- a/icons/__init__.py +++ b/icons/__init__.py @@ -32,11 +32,12 @@ class IconList: FUNCTION = Icon("function", "lightning.png") EVENT = Icon("event", "calendar_view_day.png") - # Engines - SQLITE = Icon("engine_sqlite", "server-sqlite.png") - MYSQL = Icon("engine_mysql", "server-mysql.png") - MARIADB = Icon("engine_mariadb", "server-mariadb.png") - POSTGRESQL = Icon("engine_postgresql", "server-postgresql.png") + # Servers + SQLITE = Icon("server_sqlite", "server-sqlite.png") + MYSQL = Icon("server_mysql", "server-mysql.png") + MARIADB = Icon("server_mariadb", "server-mariadb.png") + POSTGRESQL = Icon("server_postgresql", "server-postgresql.png") + ORACLE = Icon("server_oracle", "server-oracle.png") # Keys KEY_PRIMARY = Icon("key_primary", "key_primary.png") @@ -61,8 +62,8 @@ def __init__(self, base_path: str, size: int = 16): self.base_path = base_path self._imagelist = wx.ImageList(size, size) - self._idx_cache: Dict[Hashable, int] = {} - self._bmp_cache: Dict[Hashable, wx.Bitmap] = {} + self._idx_cache: dict[Hashable, int] = {} + self._bmp_cache: dict[Hashable, wx.Bitmap] = {} @property def imagelist(self) -> wx.ImageList: @@ -91,7 +92,7 @@ def _combine_bitmaps(*bitmaps: wx.Bitmap) -> wx.Bitmap: return img.ConvertToBitmap() @staticmethod - def _key(*icons: "Icon") -> Tuple[Hashable, ...]: + def _key(*icons: "Icon") -> tuple[Hashable, ...]: # single -> (id,), combo -> (id1, id2, ...) return tuple(icon.id for icon in icons) @@ -115,7 +116,7 @@ def get_bitmap(self, *icons: "Icon") -> wx.Bitmap: return bmp # combo: ensure single bitmaps exist (and are cached with (id,)) - parts: List[wx.Bitmap] = [] + parts: list[wx.Bitmap] = [] for icon in icons: part = self.get_bitmap(icon) # caches (id,) if part and part.IsOk(): diff --git a/main.py b/main.py index ea1c74c..0fee084 100755 --- a/main.py +++ b/main.py @@ -6,38 +6,43 @@ import wx -import settings - +from constants import WORKDIR from icons import IconRegistry from helpers.loader import Loader from helpers.logger import logger from helpers.observables import ObservableObject -from windows.components.stc.styles import apply_stc_theme +from windows.dialogs.settings.repository import SettingsRepository + +from windows.components.stc.styles import apply_stc_theme, set_theme_loader from windows.components.stc.themes import ThemeManager from windows.components.stc.registry import SyntaxRegistry from windows.components.stc.profiles import BASE64, CSV, HTML, JSON, MARKDOWN, REGEX, SQL, TEXT, XML, YAML - -WORKDIR = Path(os.path.abspath(os.path.dirname(__file__))) +from windows.components.stc.theme_loader import ThemeLoader class PeterSQL(wx.App): locale: wx.Locale = wx.Locale() - settings: ObservableObject = settings.load(WORKDIR.joinpath("settings.yml")) + settings_repository = SettingsRepository(WORKDIR / "settings.yml") + settings: ObservableObject = settings_repository.load() main_frame: wx.Frame = None icon_registry_16: IconRegistry syntax_registry: SyntaxRegistry + + theme_loader: ThemeLoader def OnInit(self) -> bool: Loader.loading.subscribe(self._on_loading_change) - self.icon_registry_16 = IconRegistry(os.path.join(WORKDIR, "icons"), 16) + self.icon_registry_16 = IconRegistry(WORKDIR / "icons", 16) + self._init_theme_loader() + self.theme_manager = ThemeManager(apply_function=apply_stc_theme) self.syntax_registry = SyntaxRegistry([JSON, SQL, XML, YAML, MARKDOWN, HTML, REGEX, CSV, BASE64, TEXT]) @@ -46,6 +51,17 @@ def OnInit(self) -> bool: self.open_session_manager() return True + + def _init_theme_loader(self) -> None: + theme_name = self.settings.get_value("theme", "current") or "petersql" + self.theme_loader = ThemeLoader(WORKDIR / "themes") + try: + self.theme_loader.load_theme(theme_name) + set_theme_loader(self.theme_loader) + except FileNotFoundError: + logger.warning(f"Theme '{theme_name}' not found, using default colors") + except Exception as ex: + logger.error(f"Error loading theme: {ex}", exc_info=True) def _init_locale(self): _locale = self.settings.get_value("locale") @@ -72,17 +88,17 @@ def gettext_wrapper(message): locale.setlocale(locale.LC_ALL, _locale) def open_session_manager(self) -> None: - from windows.connections.manager import ConnectionsManager + from windows.dialogs.connections.view import ConnectionsManager self.connection_manager = ConnectionsManager(None) self.connection_manager.SetIcon( - wx.Icon(os.path.join(WORKDIR, "icons", "petersql.ico")) + wx.Icon(str(WORKDIR / "icons" / "petersql.ico")) ) self.connection_manager.Show() def open_main_frame(self) -> None: try: - from windows.main.main_frame import MainFrameController + from windows.main.controller import MainFrameController self.main_frame = MainFrameController() size = wx.Size( @@ -98,7 +114,7 @@ def open_main_frame(self) -> None: self.main_frame.SetPosition(position) self.main_frame.Layout() self.main_frame.SetIcon( - wx.Icon(os.path.join(WORKDIR, "icons", "petersql.ico")) + wx.Icon(str(WORKDIR / "icons" / "petersql.ico")) ) self.main_frame.Show() diff --git a/pyproject.toml b/pyproject.toml index e2b1433..d1d0475 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,32 +2,44 @@ name = "petersql" version = "0.1.0" description = "graphical client for database management" +readme = "README.md" requires-python = ">=3.14" dependencies = [ - "babel", - "oracledb", - "psutil", - "psycopg2-binary", - "pymysql", - "pyyaml", - "requests", - "sqlglot", - "wxpython", + "babel>=2.18.0", + "oracledb>=3.4.2", + "psutil>=7.2.2", + "psycopg2-binary>=2.9.11", + "pymysql>=1.1.2", + "pyyaml>=6.0.3", + "sqlglot>=29.0.1", + "wxpython>=4.2.5", ] [project.optional-dependencies] dev = [ - "mypy", - "pre-commit", - "pytest", - "pytest-cov", - "pytest-mock", - "types-pymysql", - "types-pyyaml", - "types-wxpython", - "testcontainers" + "mypy>=1.19.1", + "pre-commit>=4.5.1", + "pytest>=9.0.2", + "pytest-cov>=7.0.0", + "pytest-mock>=3.15.1", + "pytest-xdist>=3.8.0", + "testcontainers>=4.14.1", + "types-pymysql>=1.1.0.20251220", + "types-pyyaml>=6.0.12.20250915", + "types-wxpython>=0.9.7", ] +[tool.pytest.ini_options] +markers = [ + "integration: marks tests as integration tests (testcontainers, slow, deselect with '-m \"not integration\"')", + "skip_engine(*selectors): skip test for specific engines or variants (e.g. sqlite, oracle, mariadb:5)", +] +addopts = "--cov=. --cov-report=term -v -n auto --dist=loadgroup" +testpaths = ["tests"] +python_files = ["test_*.py"] +python_classes = ["Test*"] +python_functions = ["test_*"] +cache_dir = ".cache/pytest" [tool.pyright] typeCheckingMode = "standard" diff --git a/scripts/locale/de_DE/LC_MESSAGES/petersql.po b/scripts/locale/de_DE/LC_MESSAGES/petersql.po new file mode 100644 index 0000000..4370e80 --- /dev/null +++ b/scripts/locale/de_DE/LC_MESSAGES/petersql.po @@ -0,0 +1,952 @@ +#: helpers/__init__.py:16 +msgctxt "unit" +msgid "B" +msgstr "" + +#: helpers/__init__.py:17 +msgctxt "unit" +msgid "KB" +msgstr "" + +#: helpers/__init__.py:18 +msgctxt "unit" +msgid "MB" +msgstr "" + +#: helpers/__init__.py:19 +msgctxt "unit" +msgid "GB" +msgstr "" + +#: helpers/__init__.py:20 +msgctxt "unit" +msgid "TB" +msgstr "" + +#: structures/ssh_tunnel.py:166 +msgid "OpenSSH client not found." +msgstr "" + +#: windows/dialogs/connections/view.py:259 +#: windows/dialogs/connections/view.py:547 windows/main/controller.py:294 +#: windows/views.py:33 +msgid "Connection" +msgstr "" + +#: windows/components/dataview.py:113 windows/components/dataview.py:225 +#: windows/components/dataview.py:238 windows/components/dataview.py:253 +#: windows/views.py:47 windows/views.py:97 windows/views.py:898 +#: windows/views.py:958 windows/views.py:1341 windows/views.py:2246 +#: windows/views.py:2269 windows/views.py:2270 windows/views.py:2271 +#: windows/views.py:2272 windows/views.py:2273 windows/views.py:2274 +#: windows/views.py:2275 windows/views.py:2276 windows/views.py:2277 +#: windows/views.py:2281 windows/views.py:2462 windows/views.py:2663 +msgid "Name" +msgstr "" + +#: windows/views.py:48 windows/views.py:381 +msgid "Last connection" +msgstr "" + +#: windows/dialogs/connections/view.py:452 windows/views.py:61 +msgid "New directory" +msgstr "" + +#: windows/dialogs/connections/model.py:142 +#: windows/dialogs/connections/view.py:384 windows/views.py:65 +msgid "New connection" +msgstr "" + +#: windows/views.py:71 +msgid "Rename" +msgstr "" + +#: windows/views.py:76 +msgid "Clone connection" +msgstr "" + +#: windows/views.py:81 windows/views.py:471 windows/views.py:884 +#: windows/views.py:1251 windows/views.py:1283 windows/views.py:1542 +#: windows/views.py:2799 windows/views.py:2831 +msgid "Delete" +msgstr "" + +#: windows/views.py:111 windows/views.py:903 windows/views.py:1013 +#: windows/views.py:2286 windows/views.py:2718 +msgid "Engine" +msgstr "" + +#: windows/views.py:132 +msgid "Host + port" +msgstr "" + +#: windows/views.py:148 +msgid "Username" +msgstr "" + +#: windows/views.py:161 +msgid "Password" +msgstr "" + +#: windows/views.py:177 +msgid "Use TLS" +msgstr "" + +#: windows/views.py:180 +msgid "Use SSH tunnel" +msgstr "" + +#: windows/views.py:202 windows/views.py:2198 +msgid "Filename" +msgstr "" + +#: windows/views.py:207 windows/views.py:324 windows/views.py:2203 +#: windows/views.py:2395 +msgid "Select a file" +msgstr "" + +#: windows/views.py:207 windows/views.py:324 windows/views.py:2395 +msgid "*.*" +msgstr "" + +#: windows/components/dataview.py:70 windows/components/dataview.py:92 +#: windows/views.py:221 windows/views.py:905 windows/views.py:971 +#: windows/views.py:2287 windows/views.py:2560 windows/views.py:2676 +msgid "Comments" +msgstr "" + +#: windows/main/controller.py:217 windows/views.py:235 windows/views.py:598 +msgid "Settings" +msgstr "" + +#: windows/views.py:244 +msgid "SSH executable" +msgstr "" + +#: windows/views.py:249 +msgid "ssh" +msgstr "" + +#: windows/views.py:257 +msgid "SSH host + port" +msgstr "" + +#: windows/views.py:269 +msgid "SSH host + port (the SSH server that forwards traffic to the DB)" +msgstr "" + +#: windows/views.py:278 +msgid "SSH username" +msgstr "" + +#: windows/views.py:291 +msgid "SSH password" +msgstr "" + +#: windows/views.py:304 +msgid "Local port" +msgstr "" + +#: windows/views.py:310 +msgid "if the value is set to 0, the first available port will be used" +msgstr "" + +#: windows/views.py:319 +msgid "Identity file" +msgstr "" + +#: windows/views.py:335 +msgid "Remote host + port" +msgstr "" + +#: windows/views.py:347 +msgid "Remote host/port is the real DB target (defaults to DB Host/Port)." +msgstr "" + +#: windows/views.py:358 +msgid "SSH Tunnel" +msgstr "" + +#: windows/views.py:364 windows/views.py:901 windows/views.py:2284 +msgid "Created at" +msgstr "" + +#: windows/views.py:398 +msgid "Successful connections" +msgstr "" + +#: windows/views.py:415 +msgid "Unsuccessful connections" +msgstr "" + +#: windows/views.py:434 +msgid "Statistics" +msgstr "" + +#: windows/views.py:452 windows/views.py:1217 +msgid "Create" +msgstr "" + +#: windows/views.py:456 +msgid "Create connection" +msgstr "" + +#: windows/views.py:459 +msgid "Create directory" +msgstr "" + +#: windows/views.py:488 windows/views.py:712 windows/views.py:1286 +#: windows/views.py:1547 windows/views.py:1623 windows/views.py:2607 +#: windows/views.py:2834 +msgid "Cancel" +msgstr "" + +#: windows/views.py:493 windows/views.py:1552 windows/views.py:2617 +#: windows/views.py:2839 +msgid "Save" +msgstr "" + +#: windows/views.py:500 +msgid "Test" +msgstr "" + +#: windows/views.py:507 +msgid "Connect" +msgstr "" + +#: windows/views.py:610 +msgid "Language" +msgstr "" + +#: windows/views.py:615 +msgid "English" +msgstr "" + +#: windows/views.py:615 +msgid "Italian" +msgstr "" + +#: windows/views.py:615 +msgid "French" +msgstr "" + +#: windows/views.py:627 +msgid "Locale" +msgstr "" + +#: windows/views.py:648 +msgid "Edit Value" +msgstr "" + +#: windows/views.py:658 +msgid "Syntax" +msgstr "" + +#: windows/views.py:715 +msgid "Ok" +msgstr "" + +#: windows/views.py:746 +msgid "PeterSQL" +msgstr "" + +#: windows/views.py:752 +msgid "File" +msgstr "" + +#: windows/views.py:755 +msgid "About" +msgstr "" + +#: windows/views.py:758 +msgid "Help" +msgstr "" + +#: windows/views.py:763 +msgid "Open connection manager" +msgstr "" + +#: windows/views.py:765 +msgid "Disconnect from server" +msgstr "" + +#: windows/views.py:769 +msgid "tool" +msgstr "" + +#: windows/views.py:769 +msgid "Refresh" +msgstr "" + +#: windows/views.py:773 windows/views.py:775 +msgid "Add" +msgstr "" + +#: windows/views.py:809 windows/views.py:813 windows/views.py:1711 +#: windows/views.py:1839 +msgid "MyMenuItem" +msgstr "" + +#: windows/views.py:816 windows/views.py:1314 windows/views.py:2862 +msgid "MyMenu" +msgstr "" + +#: windows/views.py:831 +msgid "MyLabel" +msgstr "" + +#: windows/views.py:837 +msgid "Databases" +msgstr "" + +#: windows/views.py:838 windows/views.py:900 windows/views.py:2255 +#: windows/views.py:2283 +msgid "Size" +msgstr "" + +#: windows/views.py:839 +msgid "Elements" +msgstr "" + +#: windows/views.py:840 +msgid "Modified at" +msgstr "" + +#: windows/views.py:841 windows/views.py:912 +msgid "Tables" +msgstr "" + +#: windows/views.py:848 +msgid "System" +msgstr "" + +#: windows/views.py:864 +msgid "Table:" +msgstr "" + +#: windows/views.py:872 windows/views.py:1096 windows/views.py:1140 +#: windows/views.py:1246 windows/views.py:2794 +msgid "Insert" +msgstr "" + +#: windows/views.py:877 +msgid "Clone" +msgstr "" + +#: windows/views.py:899 +msgid "Rows" +msgstr "" + +#: windows/views.py:902 windows/views.py:2285 +msgid "Updated at" +msgstr "" + +#: windows/components/dataview.py:43 windows/components/dataview.py:67 +#: windows/components/dataview.py:89 windows/views.py:904 windows/views.py:2506 +msgid "Collation" +msgstr "" + +#: windows/views.py:920 +msgid "Diagram" +msgstr "" + +#: windows/views.py:931 windows/views.py:2254 +msgid "Database" +msgstr "" + +#: windows/views.py:986 windows/views.py:2691 +msgid "Base" +msgstr "" + +#: windows/views.py:1000 windows/views.py:2705 +msgid "Auto Increment" +msgstr "" + +#: windows/views.py:1028 windows/views.py:2733 +msgid "Default Collation" +msgstr "" + +#: windows/views.py:1048 windows/views.py:1501 windows/views.py:2751 +msgid "Options" +msgstr "" + +#: windows/views.py:1060 windows/views.py:1101 windows/views.py:1145 +msgid "Remove" +msgstr "" + +#: windows/views.py:1067 windows/views.py:1108 windows/views.py:1152 +msgid "Clear" +msgstr "" + +#: windows/views.py:1082 windows/views.py:2765 +msgid "Indexes" +msgstr "" + +#: windows/views.py:1126 +msgid "Foreign Keys" +msgstr "" + +#: windows/views.py:1170 +msgid "Checks" +msgstr "" + +#: windows/views.py:1238 windows/views.py:2786 +msgid "Columns:" +msgstr "" + +#: windows/views.py:1258 windows/views.py:2806 +msgid "Up" +msgstr "" + +#: windows/views.py:1265 windows/views.py:2813 +msgid "Down" +msgstr "" + +#: windows/views.py:1291 windows/views.py:1630 windows/views.py:1685 +msgid "Apply" +msgstr "" + +#: windows/views.py:1304 windows/views.py:1311 windows/views.py:2852 +#: windows/views.py:2859 +msgid "Add Index" +msgstr "" + +#: windows/views.py:1308 windows/views.py:2856 +msgid "Add PrimaryKey" +msgstr "" + +#: windows/views.py:1325 +msgid "Table" +msgstr "" + +#: windows/views.py:1361 +msgid "Definer" +msgstr "" + +#: windows/views.py:1381 +msgid "Schema" +msgstr "" + +#: windows/views.py:1407 +msgid "SQL security" +msgstr "" + +#: windows/views.py:1414 +msgid "DEFINER" +msgstr "" + +#: windows/views.py:1414 +msgid "INVOKER" +msgstr "" + +#: windows/views.py:1426 windows/views.py:2096 windows/views.py:2115 +#: windows/views.py:2359 +msgid "Algorithm" +msgstr "" + +#: windows/views.py:1428 windows/views.py:2081 windows/views.py:2114 +#: windows/views.py:2364 +msgid "UNDEFINED" +msgstr "" + +#: windows/views.py:1431 windows/views.py:2084 windows/views.py:2114 +#: windows/views.py:2367 +msgid "MERGE" +msgstr "" + +#: windows/views.py:1434 windows/views.py:2093 windows/views.py:2114 +#: windows/views.py:2370 +msgid "TEMPTABLE" +msgstr "" + +#: windows/views.py:1444 windows/views.py:2120 +msgid "View constraint" +msgstr "" + +#: windows/views.py:1446 windows/views.py:2119 +msgid "None" +msgstr "" + +#: windows/views.py:1449 windows/views.py:2119 +msgid "LOCAL" +msgstr "" + +#: windows/views.py:1452 +msgid "CASCADE" +msgstr "" + +#: windows/views.py:1455 +msgid "CHECK ONLY" +msgstr "" + +#: windows/views.py:1458 windows/views.py:2119 +msgid "READ ONLY" +msgstr "" + +#: windows/views.py:1470 +msgid "Force" +msgstr "" + +#: windows/views.py:1482 +msgid "Security barrier" +msgstr "" + +#: windows/views.py:1564 +msgid "Views" +msgstr "" + +#: windows/views.py:1572 +msgid "Triggers" +msgstr "" + +#: windows/views.py:1584 +#, python-format +msgid "Table `%(database_name)s`.`%(table_name)s`: %(total_rows) rows total" +msgstr "" + +#: windows/views.py:1594 +msgid "Insert record" +msgstr "" + +#: windows/views.py:1599 +msgid "Duplicate record" +msgstr "" + +#: windows/views.py:1606 +msgid "Delete record" +msgstr "" + +#: windows/views.py:1616 +msgid "Apply changes automatically" +msgstr "" + +#: windows/views.py:1618 windows/views.py:1619 +msgid "" +"If enabled, table edits are applied immediately without pressing Apply or" +" Cancel" +msgstr "" + +#: windows/views.py:1640 +msgid "Next" +msgstr "" + +#: windows/views.py:1648 +msgid "Filters" +msgstr "" + +#: windows/views.py:1688 +msgid "CTRL+ENTER" +msgstr "" + +#: windows/views.py:1708 +msgid "Insert row" +msgstr "" + +#: windows/views.py:1716 +msgid "Data" +msgstr "" + +#: windows/views.py:1770 windows/views.py:1820 +msgid "New" +msgstr "" + +#: windows/views.py:1797 +msgid "Query" +msgstr "" + +#: windows/views.py:1817 +msgid "Close" +msgstr "" + +#: windows/views.py:1830 +msgid "Query #2" +msgstr "" + +#: windows/views.py:2075 +msgid "Column5" +msgstr "" + +#: windows/views.py:2086 +msgid "Import" +msgstr "" + +#: windows/views.py:2111 +msgid "Read only" +msgstr "" + +#: windows/views.py:2119 +msgid "CASCADED" +msgstr "" + +#: windows/views.py:2119 +msgid "CHECK OPTION" +msgstr "" + +#: windows/views.py:2143 +msgid "collapsible" +msgstr "" + +#: windows/views.py:2165 +msgid "Column3" +msgstr "" + +#: windows/views.py:2166 +msgid "Column4" +msgstr "" + +#: windows/views.py:2203 +msgid "" +"Database " +"(*.db;*.db3;*.sdb;*.s3db;*.sqlite;*.sqlite3)|*.db;*.db3;*.sdb;*.s3db;*.sqlite;*.sqlite3" +msgstr "" + +#: windows/views.py:2215 +msgid "Port" +msgstr "" + +#: windows/views.py:2247 +msgid "Usage" +msgstr "" + +#: windows/views.py:2258 +#, python-format +msgid "%(total_rows)s" +msgstr "" + +#: windows/views.py:2263 +msgid "rows total" +msgstr "" + +#: windows/views.py:2282 +msgid "Lines" +msgstr "" + +#: windows/views.py:2314 +msgid "Temporary" +msgstr "" + +#: windows/views.py:2325 +msgid "Engine options" +msgstr "" + +#: windows/views.py:2384 +msgid "RadioBtn" +msgstr "" + +#: windows/views.py:2454 +msgid "Edit Column" +msgstr "" + +#: windows/views.py:2470 +msgid "Datatype" +msgstr "" + +#: windows/components/dataview.py:121 windows/views.py:2485 +msgid "Length/Set" +msgstr "" + +#: windows/components/dataview.py:51 windows/views.py:2524 +msgid "Unsigned" +msgstr "" + +#: windows/components/dataview.py:25 windows/components/dataview.py:52 +#: windows/components/dataview.py:75 windows/views.py:2530 +msgid "Allow NULL" +msgstr "" + +#: windows/views.py:2536 +msgid "Zero Fill" +msgstr "" + +#: windows/components/dataview.py:32 windows/components/dataview.py:56 +#: windows/components/dataview.py:78 windows/views.py:2547 +msgid "Default" +msgstr "" + +#: windows/components/dataview.py:36 windows/components/dataview.py:60 +#: windows/components/dataview.py:82 windows/views.py:2573 +msgid "Virtuality" +msgstr "" + +#: windows/components/dataview.py:39 windows/components/dataview.py:63 +#: windows/components/dataview.py:85 windows/components/dataview.py:241 +#: windows/views.py:2588 +msgid "Expression" +msgstr "" + +#: windows/components/dataview.py:28 +msgid "Check" +msgstr "" + +#: windows/components/dataview.py:53 +msgid "Zerofill" +msgstr "" + +#: windows/components/dataview.py:109 +msgid "#" +msgstr "" + +#: windows/components/dataview.py:117 +msgid "Data type" +msgstr "" + +#: windows/components/dataview.py:155 +msgid "Add column\tCTRL+INS" +msgstr "" + +#: windows/components/dataview.py:161 +msgid "Remove column\tCTRL+DEL" +msgstr "" + +#: windows/components/dataview.py:169 +msgid "Move up\tCTRL+UP" +msgstr "" + +#: windows/components/dataview.py:176 +msgid "Move down\tCTRL+D" +msgstr "" + +#: windows/components/dataview.py:199 +msgid "Create new index" +msgstr "" + +#: windows/components/dataview.py:214 +msgid "Append to index" +msgstr "" + +#: windows/components/dataview.py:228 +msgid "Column(s)/Expression" +msgstr "" + +#: windows/components/dataview.py:229 +msgid "Condition" +msgstr "" + +#: windows/components/dataview.py:259 +msgid "Column(s)" +msgstr "" + +#: windows/components/dataview.py:265 +msgid "Reference table" +msgstr "" + +#: windows/components/dataview.py:271 +msgid "Reference column(s)" +msgstr "" + +#: windows/components/dataview.py:277 +msgid "On UPDATE" +msgstr "" + +#: windows/components/dataview.py:283 +msgid "On DELETE" +msgstr "" + +#: windows/components/dataview.py:299 +msgid "Add foreign key" +msgstr "" + +#: windows/components/dataview.py:305 +msgid "Remove foreign key" +msgstr "" + +#: windows/components/popup.py:26 +msgid "No default value" +msgstr "" + +#: windows/components/popup.py:31 +msgid "NULL" +msgstr "" + +#: windows/components/popup.py:35 +msgid "AUTO INCREMENT" +msgstr "" + +#: windows/components/popup.py:39 +msgid "Text/Expression" +msgstr "" + +#: windows/dialogs/connections/view.py:258 +msgid "Connection established successfully" +msgstr "" + +#: windows/dialogs/connections/view.py:271 +msgid "Confirm save" +msgstr "" + +#: windows/dialogs/connections/view.py:314 +msgid "You have unsaved changes. Do you want to save them before continuing?" +msgstr "" + +#: windows/dialogs/connections/view.py:316 +msgid "Unsaved changes" +msgstr "" + +#: windows/dialogs/connections/view.py:545 +msgid "" +"This connection cannot work without TLS. TLS has been enabled " +"automatically." +msgstr "" + +#: windows/dialogs/connections/view.py:554 +msgid "Connection error" +msgstr "" + +#: windows/dialogs/connections/view.py:580 +#: windows/dialogs/connections/view.py:595 +msgid "Confirm delete" +msgstr "" + +#: windows/main/controller.py:170 +msgid "days" +msgstr "" + +#: windows/main/controller.py:171 +msgid "hours" +msgstr "" + +#: windows/main/controller.py:172 +msgid "minutes" +msgstr "" + +#: windows/main/controller.py:173 +msgid "seconds" +msgstr "" + +#: windows/main/controller.py:181 +#, python-brace-format +msgid "Memory used: {used} ({percentage:.2%})" +msgstr "" + +#: windows/main/controller.py:217 +msgid "Settings saved successfully" +msgstr "" + +#: windows/main/controller.py:296 +msgid "Version" +msgstr "" + +#: windows/main/controller.py:298 +msgid "Uptime" +msgstr "" + +#: windows/main/controller.py:467 +msgid "Delete table" +msgstr "" + +#: windows/main/controller.py:584 +msgid "Do you want delete the records?" +msgstr "" + +#: windows/main/tabs/database.py:71 +msgid "The connection to the database was lost." +msgstr "" + +#: windows/main/tabs/database.py:73 +msgid "Do you want to reconnect?" +msgstr "" + +#: windows/main/tabs/database.py:75 +msgid "Connection lost" +msgstr "" + +#: windows/main/tabs/database.py:85 +msgid "Reconnection failed:" +msgstr "" + +#: windows/main/tabs/database.py:86 windows/main/tabs/query.py:450 +#: windows/main/tabs/view.py:256 windows/main/tabs/view.py:282 +msgid "Error" +msgstr "" + +#: windows/main/tabs/query.py:305 +#, python-brace-format +msgid "{} rows affected" +msgstr "" + +#: windows/main/tabs/query.py:309 windows/main/tabs/query.py:331 +#, python-brace-format +msgid "Query {}" +msgstr "" + +#: windows/main/tabs/query.py:314 +#, python-brace-format +msgid "Query {} (Error)" +msgstr "" + +#: windows/main/tabs/query.py:326 +#, python-brace-format +msgid "Query {} ({} rows × {} cols)" +msgstr "" + +#: windows/main/tabs/query.py:353 +#, python-brace-format +msgid "{} rows" +msgstr "" + +#: windows/main/tabs/query.py:355 +#, python-brace-format +msgid "{:.1f} ms" +msgstr "" + +#: windows/main/tabs/query.py:358 +#, python-brace-format +msgid "{} warnings" +msgstr "" + +#: windows/main/tabs/query.py:370 +msgid "Error:" +msgstr "" + +#: windows/main/tabs/query.py:376 +msgid "Unknown error" +msgstr "" + +#: windows/main/tabs/query.py:449 +msgid "No active database connection" +msgstr "" + +#: windows/main/tabs/view.py:252 +msgid "View created successfully" +msgstr "" + +#: windows/main/tabs/view.py:252 +msgid "View updated successfully" +msgstr "" + +#: windows/main/tabs/view.py:253 windows/main/tabs/view.py:279 +msgid "Success" +msgstr "" + +#: windows/main/tabs/view.py:256 +#, python-brace-format +msgid "Error saving view: {}" +msgstr "" + +#: windows/main/tabs/view.py:269 +#, python-brace-format +msgid "Are you sure you want to delete view '{}'?" +msgstr "" + +#: windows/main/tabs/view.py:270 +msgid "Confirm Delete" +msgstr "" + +#: windows/main/tabs/view.py:279 +msgid "View deleted successfully" +msgstr "" + +#: windows/main/tabs/view.py:282 +#, python-brace-format +msgid "Error deleting view: {}" +msgstr "" + +#~ msgid "New Session" +#~ msgstr "" + +#~ msgid "connection" +#~ msgstr "" + +#~ msgid "directory" +#~ msgstr "" + diff --git a/scripts/locale/en_US/LC_MESSAGES/petersql.po b/scripts/locale/en_US/LC_MESSAGES/petersql.po new file mode 100644 index 0000000..4370e80 --- /dev/null +++ b/scripts/locale/en_US/LC_MESSAGES/petersql.po @@ -0,0 +1,952 @@ +#: helpers/__init__.py:16 +msgctxt "unit" +msgid "B" +msgstr "" + +#: helpers/__init__.py:17 +msgctxt "unit" +msgid "KB" +msgstr "" + +#: helpers/__init__.py:18 +msgctxt "unit" +msgid "MB" +msgstr "" + +#: helpers/__init__.py:19 +msgctxt "unit" +msgid "GB" +msgstr "" + +#: helpers/__init__.py:20 +msgctxt "unit" +msgid "TB" +msgstr "" + +#: structures/ssh_tunnel.py:166 +msgid "OpenSSH client not found." +msgstr "" + +#: windows/dialogs/connections/view.py:259 +#: windows/dialogs/connections/view.py:547 windows/main/controller.py:294 +#: windows/views.py:33 +msgid "Connection" +msgstr "" + +#: windows/components/dataview.py:113 windows/components/dataview.py:225 +#: windows/components/dataview.py:238 windows/components/dataview.py:253 +#: windows/views.py:47 windows/views.py:97 windows/views.py:898 +#: windows/views.py:958 windows/views.py:1341 windows/views.py:2246 +#: windows/views.py:2269 windows/views.py:2270 windows/views.py:2271 +#: windows/views.py:2272 windows/views.py:2273 windows/views.py:2274 +#: windows/views.py:2275 windows/views.py:2276 windows/views.py:2277 +#: windows/views.py:2281 windows/views.py:2462 windows/views.py:2663 +msgid "Name" +msgstr "" + +#: windows/views.py:48 windows/views.py:381 +msgid "Last connection" +msgstr "" + +#: windows/dialogs/connections/view.py:452 windows/views.py:61 +msgid "New directory" +msgstr "" + +#: windows/dialogs/connections/model.py:142 +#: windows/dialogs/connections/view.py:384 windows/views.py:65 +msgid "New connection" +msgstr "" + +#: windows/views.py:71 +msgid "Rename" +msgstr "" + +#: windows/views.py:76 +msgid "Clone connection" +msgstr "" + +#: windows/views.py:81 windows/views.py:471 windows/views.py:884 +#: windows/views.py:1251 windows/views.py:1283 windows/views.py:1542 +#: windows/views.py:2799 windows/views.py:2831 +msgid "Delete" +msgstr "" + +#: windows/views.py:111 windows/views.py:903 windows/views.py:1013 +#: windows/views.py:2286 windows/views.py:2718 +msgid "Engine" +msgstr "" + +#: windows/views.py:132 +msgid "Host + port" +msgstr "" + +#: windows/views.py:148 +msgid "Username" +msgstr "" + +#: windows/views.py:161 +msgid "Password" +msgstr "" + +#: windows/views.py:177 +msgid "Use TLS" +msgstr "" + +#: windows/views.py:180 +msgid "Use SSH tunnel" +msgstr "" + +#: windows/views.py:202 windows/views.py:2198 +msgid "Filename" +msgstr "" + +#: windows/views.py:207 windows/views.py:324 windows/views.py:2203 +#: windows/views.py:2395 +msgid "Select a file" +msgstr "" + +#: windows/views.py:207 windows/views.py:324 windows/views.py:2395 +msgid "*.*" +msgstr "" + +#: windows/components/dataview.py:70 windows/components/dataview.py:92 +#: windows/views.py:221 windows/views.py:905 windows/views.py:971 +#: windows/views.py:2287 windows/views.py:2560 windows/views.py:2676 +msgid "Comments" +msgstr "" + +#: windows/main/controller.py:217 windows/views.py:235 windows/views.py:598 +msgid "Settings" +msgstr "" + +#: windows/views.py:244 +msgid "SSH executable" +msgstr "" + +#: windows/views.py:249 +msgid "ssh" +msgstr "" + +#: windows/views.py:257 +msgid "SSH host + port" +msgstr "" + +#: windows/views.py:269 +msgid "SSH host + port (the SSH server that forwards traffic to the DB)" +msgstr "" + +#: windows/views.py:278 +msgid "SSH username" +msgstr "" + +#: windows/views.py:291 +msgid "SSH password" +msgstr "" + +#: windows/views.py:304 +msgid "Local port" +msgstr "" + +#: windows/views.py:310 +msgid "if the value is set to 0, the first available port will be used" +msgstr "" + +#: windows/views.py:319 +msgid "Identity file" +msgstr "" + +#: windows/views.py:335 +msgid "Remote host + port" +msgstr "" + +#: windows/views.py:347 +msgid "Remote host/port is the real DB target (defaults to DB Host/Port)." +msgstr "" + +#: windows/views.py:358 +msgid "SSH Tunnel" +msgstr "" + +#: windows/views.py:364 windows/views.py:901 windows/views.py:2284 +msgid "Created at" +msgstr "" + +#: windows/views.py:398 +msgid "Successful connections" +msgstr "" + +#: windows/views.py:415 +msgid "Unsuccessful connections" +msgstr "" + +#: windows/views.py:434 +msgid "Statistics" +msgstr "" + +#: windows/views.py:452 windows/views.py:1217 +msgid "Create" +msgstr "" + +#: windows/views.py:456 +msgid "Create connection" +msgstr "" + +#: windows/views.py:459 +msgid "Create directory" +msgstr "" + +#: windows/views.py:488 windows/views.py:712 windows/views.py:1286 +#: windows/views.py:1547 windows/views.py:1623 windows/views.py:2607 +#: windows/views.py:2834 +msgid "Cancel" +msgstr "" + +#: windows/views.py:493 windows/views.py:1552 windows/views.py:2617 +#: windows/views.py:2839 +msgid "Save" +msgstr "" + +#: windows/views.py:500 +msgid "Test" +msgstr "" + +#: windows/views.py:507 +msgid "Connect" +msgstr "" + +#: windows/views.py:610 +msgid "Language" +msgstr "" + +#: windows/views.py:615 +msgid "English" +msgstr "" + +#: windows/views.py:615 +msgid "Italian" +msgstr "" + +#: windows/views.py:615 +msgid "French" +msgstr "" + +#: windows/views.py:627 +msgid "Locale" +msgstr "" + +#: windows/views.py:648 +msgid "Edit Value" +msgstr "" + +#: windows/views.py:658 +msgid "Syntax" +msgstr "" + +#: windows/views.py:715 +msgid "Ok" +msgstr "" + +#: windows/views.py:746 +msgid "PeterSQL" +msgstr "" + +#: windows/views.py:752 +msgid "File" +msgstr "" + +#: windows/views.py:755 +msgid "About" +msgstr "" + +#: windows/views.py:758 +msgid "Help" +msgstr "" + +#: windows/views.py:763 +msgid "Open connection manager" +msgstr "" + +#: windows/views.py:765 +msgid "Disconnect from server" +msgstr "" + +#: windows/views.py:769 +msgid "tool" +msgstr "" + +#: windows/views.py:769 +msgid "Refresh" +msgstr "" + +#: windows/views.py:773 windows/views.py:775 +msgid "Add" +msgstr "" + +#: windows/views.py:809 windows/views.py:813 windows/views.py:1711 +#: windows/views.py:1839 +msgid "MyMenuItem" +msgstr "" + +#: windows/views.py:816 windows/views.py:1314 windows/views.py:2862 +msgid "MyMenu" +msgstr "" + +#: windows/views.py:831 +msgid "MyLabel" +msgstr "" + +#: windows/views.py:837 +msgid "Databases" +msgstr "" + +#: windows/views.py:838 windows/views.py:900 windows/views.py:2255 +#: windows/views.py:2283 +msgid "Size" +msgstr "" + +#: windows/views.py:839 +msgid "Elements" +msgstr "" + +#: windows/views.py:840 +msgid "Modified at" +msgstr "" + +#: windows/views.py:841 windows/views.py:912 +msgid "Tables" +msgstr "" + +#: windows/views.py:848 +msgid "System" +msgstr "" + +#: windows/views.py:864 +msgid "Table:" +msgstr "" + +#: windows/views.py:872 windows/views.py:1096 windows/views.py:1140 +#: windows/views.py:1246 windows/views.py:2794 +msgid "Insert" +msgstr "" + +#: windows/views.py:877 +msgid "Clone" +msgstr "" + +#: windows/views.py:899 +msgid "Rows" +msgstr "" + +#: windows/views.py:902 windows/views.py:2285 +msgid "Updated at" +msgstr "" + +#: windows/components/dataview.py:43 windows/components/dataview.py:67 +#: windows/components/dataview.py:89 windows/views.py:904 windows/views.py:2506 +msgid "Collation" +msgstr "" + +#: windows/views.py:920 +msgid "Diagram" +msgstr "" + +#: windows/views.py:931 windows/views.py:2254 +msgid "Database" +msgstr "" + +#: windows/views.py:986 windows/views.py:2691 +msgid "Base" +msgstr "" + +#: windows/views.py:1000 windows/views.py:2705 +msgid "Auto Increment" +msgstr "" + +#: windows/views.py:1028 windows/views.py:2733 +msgid "Default Collation" +msgstr "" + +#: windows/views.py:1048 windows/views.py:1501 windows/views.py:2751 +msgid "Options" +msgstr "" + +#: windows/views.py:1060 windows/views.py:1101 windows/views.py:1145 +msgid "Remove" +msgstr "" + +#: windows/views.py:1067 windows/views.py:1108 windows/views.py:1152 +msgid "Clear" +msgstr "" + +#: windows/views.py:1082 windows/views.py:2765 +msgid "Indexes" +msgstr "" + +#: windows/views.py:1126 +msgid "Foreign Keys" +msgstr "" + +#: windows/views.py:1170 +msgid "Checks" +msgstr "" + +#: windows/views.py:1238 windows/views.py:2786 +msgid "Columns:" +msgstr "" + +#: windows/views.py:1258 windows/views.py:2806 +msgid "Up" +msgstr "" + +#: windows/views.py:1265 windows/views.py:2813 +msgid "Down" +msgstr "" + +#: windows/views.py:1291 windows/views.py:1630 windows/views.py:1685 +msgid "Apply" +msgstr "" + +#: windows/views.py:1304 windows/views.py:1311 windows/views.py:2852 +#: windows/views.py:2859 +msgid "Add Index" +msgstr "" + +#: windows/views.py:1308 windows/views.py:2856 +msgid "Add PrimaryKey" +msgstr "" + +#: windows/views.py:1325 +msgid "Table" +msgstr "" + +#: windows/views.py:1361 +msgid "Definer" +msgstr "" + +#: windows/views.py:1381 +msgid "Schema" +msgstr "" + +#: windows/views.py:1407 +msgid "SQL security" +msgstr "" + +#: windows/views.py:1414 +msgid "DEFINER" +msgstr "" + +#: windows/views.py:1414 +msgid "INVOKER" +msgstr "" + +#: windows/views.py:1426 windows/views.py:2096 windows/views.py:2115 +#: windows/views.py:2359 +msgid "Algorithm" +msgstr "" + +#: windows/views.py:1428 windows/views.py:2081 windows/views.py:2114 +#: windows/views.py:2364 +msgid "UNDEFINED" +msgstr "" + +#: windows/views.py:1431 windows/views.py:2084 windows/views.py:2114 +#: windows/views.py:2367 +msgid "MERGE" +msgstr "" + +#: windows/views.py:1434 windows/views.py:2093 windows/views.py:2114 +#: windows/views.py:2370 +msgid "TEMPTABLE" +msgstr "" + +#: windows/views.py:1444 windows/views.py:2120 +msgid "View constraint" +msgstr "" + +#: windows/views.py:1446 windows/views.py:2119 +msgid "None" +msgstr "" + +#: windows/views.py:1449 windows/views.py:2119 +msgid "LOCAL" +msgstr "" + +#: windows/views.py:1452 +msgid "CASCADE" +msgstr "" + +#: windows/views.py:1455 +msgid "CHECK ONLY" +msgstr "" + +#: windows/views.py:1458 windows/views.py:2119 +msgid "READ ONLY" +msgstr "" + +#: windows/views.py:1470 +msgid "Force" +msgstr "" + +#: windows/views.py:1482 +msgid "Security barrier" +msgstr "" + +#: windows/views.py:1564 +msgid "Views" +msgstr "" + +#: windows/views.py:1572 +msgid "Triggers" +msgstr "" + +#: windows/views.py:1584 +#, python-format +msgid "Table `%(database_name)s`.`%(table_name)s`: %(total_rows) rows total" +msgstr "" + +#: windows/views.py:1594 +msgid "Insert record" +msgstr "" + +#: windows/views.py:1599 +msgid "Duplicate record" +msgstr "" + +#: windows/views.py:1606 +msgid "Delete record" +msgstr "" + +#: windows/views.py:1616 +msgid "Apply changes automatically" +msgstr "" + +#: windows/views.py:1618 windows/views.py:1619 +msgid "" +"If enabled, table edits are applied immediately without pressing Apply or" +" Cancel" +msgstr "" + +#: windows/views.py:1640 +msgid "Next" +msgstr "" + +#: windows/views.py:1648 +msgid "Filters" +msgstr "" + +#: windows/views.py:1688 +msgid "CTRL+ENTER" +msgstr "" + +#: windows/views.py:1708 +msgid "Insert row" +msgstr "" + +#: windows/views.py:1716 +msgid "Data" +msgstr "" + +#: windows/views.py:1770 windows/views.py:1820 +msgid "New" +msgstr "" + +#: windows/views.py:1797 +msgid "Query" +msgstr "" + +#: windows/views.py:1817 +msgid "Close" +msgstr "" + +#: windows/views.py:1830 +msgid "Query #2" +msgstr "" + +#: windows/views.py:2075 +msgid "Column5" +msgstr "" + +#: windows/views.py:2086 +msgid "Import" +msgstr "" + +#: windows/views.py:2111 +msgid "Read only" +msgstr "" + +#: windows/views.py:2119 +msgid "CASCADED" +msgstr "" + +#: windows/views.py:2119 +msgid "CHECK OPTION" +msgstr "" + +#: windows/views.py:2143 +msgid "collapsible" +msgstr "" + +#: windows/views.py:2165 +msgid "Column3" +msgstr "" + +#: windows/views.py:2166 +msgid "Column4" +msgstr "" + +#: windows/views.py:2203 +msgid "" +"Database " +"(*.db;*.db3;*.sdb;*.s3db;*.sqlite;*.sqlite3)|*.db;*.db3;*.sdb;*.s3db;*.sqlite;*.sqlite3" +msgstr "" + +#: windows/views.py:2215 +msgid "Port" +msgstr "" + +#: windows/views.py:2247 +msgid "Usage" +msgstr "" + +#: windows/views.py:2258 +#, python-format +msgid "%(total_rows)s" +msgstr "" + +#: windows/views.py:2263 +msgid "rows total" +msgstr "" + +#: windows/views.py:2282 +msgid "Lines" +msgstr "" + +#: windows/views.py:2314 +msgid "Temporary" +msgstr "" + +#: windows/views.py:2325 +msgid "Engine options" +msgstr "" + +#: windows/views.py:2384 +msgid "RadioBtn" +msgstr "" + +#: windows/views.py:2454 +msgid "Edit Column" +msgstr "" + +#: windows/views.py:2470 +msgid "Datatype" +msgstr "" + +#: windows/components/dataview.py:121 windows/views.py:2485 +msgid "Length/Set" +msgstr "" + +#: windows/components/dataview.py:51 windows/views.py:2524 +msgid "Unsigned" +msgstr "" + +#: windows/components/dataview.py:25 windows/components/dataview.py:52 +#: windows/components/dataview.py:75 windows/views.py:2530 +msgid "Allow NULL" +msgstr "" + +#: windows/views.py:2536 +msgid "Zero Fill" +msgstr "" + +#: windows/components/dataview.py:32 windows/components/dataview.py:56 +#: windows/components/dataview.py:78 windows/views.py:2547 +msgid "Default" +msgstr "" + +#: windows/components/dataview.py:36 windows/components/dataview.py:60 +#: windows/components/dataview.py:82 windows/views.py:2573 +msgid "Virtuality" +msgstr "" + +#: windows/components/dataview.py:39 windows/components/dataview.py:63 +#: windows/components/dataview.py:85 windows/components/dataview.py:241 +#: windows/views.py:2588 +msgid "Expression" +msgstr "" + +#: windows/components/dataview.py:28 +msgid "Check" +msgstr "" + +#: windows/components/dataview.py:53 +msgid "Zerofill" +msgstr "" + +#: windows/components/dataview.py:109 +msgid "#" +msgstr "" + +#: windows/components/dataview.py:117 +msgid "Data type" +msgstr "" + +#: windows/components/dataview.py:155 +msgid "Add column\tCTRL+INS" +msgstr "" + +#: windows/components/dataview.py:161 +msgid "Remove column\tCTRL+DEL" +msgstr "" + +#: windows/components/dataview.py:169 +msgid "Move up\tCTRL+UP" +msgstr "" + +#: windows/components/dataview.py:176 +msgid "Move down\tCTRL+D" +msgstr "" + +#: windows/components/dataview.py:199 +msgid "Create new index" +msgstr "" + +#: windows/components/dataview.py:214 +msgid "Append to index" +msgstr "" + +#: windows/components/dataview.py:228 +msgid "Column(s)/Expression" +msgstr "" + +#: windows/components/dataview.py:229 +msgid "Condition" +msgstr "" + +#: windows/components/dataview.py:259 +msgid "Column(s)" +msgstr "" + +#: windows/components/dataview.py:265 +msgid "Reference table" +msgstr "" + +#: windows/components/dataview.py:271 +msgid "Reference column(s)" +msgstr "" + +#: windows/components/dataview.py:277 +msgid "On UPDATE" +msgstr "" + +#: windows/components/dataview.py:283 +msgid "On DELETE" +msgstr "" + +#: windows/components/dataview.py:299 +msgid "Add foreign key" +msgstr "" + +#: windows/components/dataview.py:305 +msgid "Remove foreign key" +msgstr "" + +#: windows/components/popup.py:26 +msgid "No default value" +msgstr "" + +#: windows/components/popup.py:31 +msgid "NULL" +msgstr "" + +#: windows/components/popup.py:35 +msgid "AUTO INCREMENT" +msgstr "" + +#: windows/components/popup.py:39 +msgid "Text/Expression" +msgstr "" + +#: windows/dialogs/connections/view.py:258 +msgid "Connection established successfully" +msgstr "" + +#: windows/dialogs/connections/view.py:271 +msgid "Confirm save" +msgstr "" + +#: windows/dialogs/connections/view.py:314 +msgid "You have unsaved changes. Do you want to save them before continuing?" +msgstr "" + +#: windows/dialogs/connections/view.py:316 +msgid "Unsaved changes" +msgstr "" + +#: windows/dialogs/connections/view.py:545 +msgid "" +"This connection cannot work without TLS. TLS has been enabled " +"automatically." +msgstr "" + +#: windows/dialogs/connections/view.py:554 +msgid "Connection error" +msgstr "" + +#: windows/dialogs/connections/view.py:580 +#: windows/dialogs/connections/view.py:595 +msgid "Confirm delete" +msgstr "" + +#: windows/main/controller.py:170 +msgid "days" +msgstr "" + +#: windows/main/controller.py:171 +msgid "hours" +msgstr "" + +#: windows/main/controller.py:172 +msgid "minutes" +msgstr "" + +#: windows/main/controller.py:173 +msgid "seconds" +msgstr "" + +#: windows/main/controller.py:181 +#, python-brace-format +msgid "Memory used: {used} ({percentage:.2%})" +msgstr "" + +#: windows/main/controller.py:217 +msgid "Settings saved successfully" +msgstr "" + +#: windows/main/controller.py:296 +msgid "Version" +msgstr "" + +#: windows/main/controller.py:298 +msgid "Uptime" +msgstr "" + +#: windows/main/controller.py:467 +msgid "Delete table" +msgstr "" + +#: windows/main/controller.py:584 +msgid "Do you want delete the records?" +msgstr "" + +#: windows/main/tabs/database.py:71 +msgid "The connection to the database was lost." +msgstr "" + +#: windows/main/tabs/database.py:73 +msgid "Do you want to reconnect?" +msgstr "" + +#: windows/main/tabs/database.py:75 +msgid "Connection lost" +msgstr "" + +#: windows/main/tabs/database.py:85 +msgid "Reconnection failed:" +msgstr "" + +#: windows/main/tabs/database.py:86 windows/main/tabs/query.py:450 +#: windows/main/tabs/view.py:256 windows/main/tabs/view.py:282 +msgid "Error" +msgstr "" + +#: windows/main/tabs/query.py:305 +#, python-brace-format +msgid "{} rows affected" +msgstr "" + +#: windows/main/tabs/query.py:309 windows/main/tabs/query.py:331 +#, python-brace-format +msgid "Query {}" +msgstr "" + +#: windows/main/tabs/query.py:314 +#, python-brace-format +msgid "Query {} (Error)" +msgstr "" + +#: windows/main/tabs/query.py:326 +#, python-brace-format +msgid "Query {} ({} rows × {} cols)" +msgstr "" + +#: windows/main/tabs/query.py:353 +#, python-brace-format +msgid "{} rows" +msgstr "" + +#: windows/main/tabs/query.py:355 +#, python-brace-format +msgid "{:.1f} ms" +msgstr "" + +#: windows/main/tabs/query.py:358 +#, python-brace-format +msgid "{} warnings" +msgstr "" + +#: windows/main/tabs/query.py:370 +msgid "Error:" +msgstr "" + +#: windows/main/tabs/query.py:376 +msgid "Unknown error" +msgstr "" + +#: windows/main/tabs/query.py:449 +msgid "No active database connection" +msgstr "" + +#: windows/main/tabs/view.py:252 +msgid "View created successfully" +msgstr "" + +#: windows/main/tabs/view.py:252 +msgid "View updated successfully" +msgstr "" + +#: windows/main/tabs/view.py:253 windows/main/tabs/view.py:279 +msgid "Success" +msgstr "" + +#: windows/main/tabs/view.py:256 +#, python-brace-format +msgid "Error saving view: {}" +msgstr "" + +#: windows/main/tabs/view.py:269 +#, python-brace-format +msgid "Are you sure you want to delete view '{}'?" +msgstr "" + +#: windows/main/tabs/view.py:270 +msgid "Confirm Delete" +msgstr "" + +#: windows/main/tabs/view.py:279 +msgid "View deleted successfully" +msgstr "" + +#: windows/main/tabs/view.py:282 +#, python-brace-format +msgid "Error deleting view: {}" +msgstr "" + +#~ msgid "New Session" +#~ msgstr "" + +#~ msgid "connection" +#~ msgstr "" + +#~ msgid "directory" +#~ msgstr "" + diff --git a/scripts/locale/es_ES/LC_MESSAGES/petersql.po b/scripts/locale/es_ES/LC_MESSAGES/petersql.po new file mode 100644 index 0000000..4370e80 --- /dev/null +++ b/scripts/locale/es_ES/LC_MESSAGES/petersql.po @@ -0,0 +1,952 @@ +#: helpers/__init__.py:16 +msgctxt "unit" +msgid "B" +msgstr "" + +#: helpers/__init__.py:17 +msgctxt "unit" +msgid "KB" +msgstr "" + +#: helpers/__init__.py:18 +msgctxt "unit" +msgid "MB" +msgstr "" + +#: helpers/__init__.py:19 +msgctxt "unit" +msgid "GB" +msgstr "" + +#: helpers/__init__.py:20 +msgctxt "unit" +msgid "TB" +msgstr "" + +#: structures/ssh_tunnel.py:166 +msgid "OpenSSH client not found." +msgstr "" + +#: windows/dialogs/connections/view.py:259 +#: windows/dialogs/connections/view.py:547 windows/main/controller.py:294 +#: windows/views.py:33 +msgid "Connection" +msgstr "" + +#: windows/components/dataview.py:113 windows/components/dataview.py:225 +#: windows/components/dataview.py:238 windows/components/dataview.py:253 +#: windows/views.py:47 windows/views.py:97 windows/views.py:898 +#: windows/views.py:958 windows/views.py:1341 windows/views.py:2246 +#: windows/views.py:2269 windows/views.py:2270 windows/views.py:2271 +#: windows/views.py:2272 windows/views.py:2273 windows/views.py:2274 +#: windows/views.py:2275 windows/views.py:2276 windows/views.py:2277 +#: windows/views.py:2281 windows/views.py:2462 windows/views.py:2663 +msgid "Name" +msgstr "" + +#: windows/views.py:48 windows/views.py:381 +msgid "Last connection" +msgstr "" + +#: windows/dialogs/connections/view.py:452 windows/views.py:61 +msgid "New directory" +msgstr "" + +#: windows/dialogs/connections/model.py:142 +#: windows/dialogs/connections/view.py:384 windows/views.py:65 +msgid "New connection" +msgstr "" + +#: windows/views.py:71 +msgid "Rename" +msgstr "" + +#: windows/views.py:76 +msgid "Clone connection" +msgstr "" + +#: windows/views.py:81 windows/views.py:471 windows/views.py:884 +#: windows/views.py:1251 windows/views.py:1283 windows/views.py:1542 +#: windows/views.py:2799 windows/views.py:2831 +msgid "Delete" +msgstr "" + +#: windows/views.py:111 windows/views.py:903 windows/views.py:1013 +#: windows/views.py:2286 windows/views.py:2718 +msgid "Engine" +msgstr "" + +#: windows/views.py:132 +msgid "Host + port" +msgstr "" + +#: windows/views.py:148 +msgid "Username" +msgstr "" + +#: windows/views.py:161 +msgid "Password" +msgstr "" + +#: windows/views.py:177 +msgid "Use TLS" +msgstr "" + +#: windows/views.py:180 +msgid "Use SSH tunnel" +msgstr "" + +#: windows/views.py:202 windows/views.py:2198 +msgid "Filename" +msgstr "" + +#: windows/views.py:207 windows/views.py:324 windows/views.py:2203 +#: windows/views.py:2395 +msgid "Select a file" +msgstr "" + +#: windows/views.py:207 windows/views.py:324 windows/views.py:2395 +msgid "*.*" +msgstr "" + +#: windows/components/dataview.py:70 windows/components/dataview.py:92 +#: windows/views.py:221 windows/views.py:905 windows/views.py:971 +#: windows/views.py:2287 windows/views.py:2560 windows/views.py:2676 +msgid "Comments" +msgstr "" + +#: windows/main/controller.py:217 windows/views.py:235 windows/views.py:598 +msgid "Settings" +msgstr "" + +#: windows/views.py:244 +msgid "SSH executable" +msgstr "" + +#: windows/views.py:249 +msgid "ssh" +msgstr "" + +#: windows/views.py:257 +msgid "SSH host + port" +msgstr "" + +#: windows/views.py:269 +msgid "SSH host + port (the SSH server that forwards traffic to the DB)" +msgstr "" + +#: windows/views.py:278 +msgid "SSH username" +msgstr "" + +#: windows/views.py:291 +msgid "SSH password" +msgstr "" + +#: windows/views.py:304 +msgid "Local port" +msgstr "" + +#: windows/views.py:310 +msgid "if the value is set to 0, the first available port will be used" +msgstr "" + +#: windows/views.py:319 +msgid "Identity file" +msgstr "" + +#: windows/views.py:335 +msgid "Remote host + port" +msgstr "" + +#: windows/views.py:347 +msgid "Remote host/port is the real DB target (defaults to DB Host/Port)." +msgstr "" + +#: windows/views.py:358 +msgid "SSH Tunnel" +msgstr "" + +#: windows/views.py:364 windows/views.py:901 windows/views.py:2284 +msgid "Created at" +msgstr "" + +#: windows/views.py:398 +msgid "Successful connections" +msgstr "" + +#: windows/views.py:415 +msgid "Unsuccessful connections" +msgstr "" + +#: windows/views.py:434 +msgid "Statistics" +msgstr "" + +#: windows/views.py:452 windows/views.py:1217 +msgid "Create" +msgstr "" + +#: windows/views.py:456 +msgid "Create connection" +msgstr "" + +#: windows/views.py:459 +msgid "Create directory" +msgstr "" + +#: windows/views.py:488 windows/views.py:712 windows/views.py:1286 +#: windows/views.py:1547 windows/views.py:1623 windows/views.py:2607 +#: windows/views.py:2834 +msgid "Cancel" +msgstr "" + +#: windows/views.py:493 windows/views.py:1552 windows/views.py:2617 +#: windows/views.py:2839 +msgid "Save" +msgstr "" + +#: windows/views.py:500 +msgid "Test" +msgstr "" + +#: windows/views.py:507 +msgid "Connect" +msgstr "" + +#: windows/views.py:610 +msgid "Language" +msgstr "" + +#: windows/views.py:615 +msgid "English" +msgstr "" + +#: windows/views.py:615 +msgid "Italian" +msgstr "" + +#: windows/views.py:615 +msgid "French" +msgstr "" + +#: windows/views.py:627 +msgid "Locale" +msgstr "" + +#: windows/views.py:648 +msgid "Edit Value" +msgstr "" + +#: windows/views.py:658 +msgid "Syntax" +msgstr "" + +#: windows/views.py:715 +msgid "Ok" +msgstr "" + +#: windows/views.py:746 +msgid "PeterSQL" +msgstr "" + +#: windows/views.py:752 +msgid "File" +msgstr "" + +#: windows/views.py:755 +msgid "About" +msgstr "" + +#: windows/views.py:758 +msgid "Help" +msgstr "" + +#: windows/views.py:763 +msgid "Open connection manager" +msgstr "" + +#: windows/views.py:765 +msgid "Disconnect from server" +msgstr "" + +#: windows/views.py:769 +msgid "tool" +msgstr "" + +#: windows/views.py:769 +msgid "Refresh" +msgstr "" + +#: windows/views.py:773 windows/views.py:775 +msgid "Add" +msgstr "" + +#: windows/views.py:809 windows/views.py:813 windows/views.py:1711 +#: windows/views.py:1839 +msgid "MyMenuItem" +msgstr "" + +#: windows/views.py:816 windows/views.py:1314 windows/views.py:2862 +msgid "MyMenu" +msgstr "" + +#: windows/views.py:831 +msgid "MyLabel" +msgstr "" + +#: windows/views.py:837 +msgid "Databases" +msgstr "" + +#: windows/views.py:838 windows/views.py:900 windows/views.py:2255 +#: windows/views.py:2283 +msgid "Size" +msgstr "" + +#: windows/views.py:839 +msgid "Elements" +msgstr "" + +#: windows/views.py:840 +msgid "Modified at" +msgstr "" + +#: windows/views.py:841 windows/views.py:912 +msgid "Tables" +msgstr "" + +#: windows/views.py:848 +msgid "System" +msgstr "" + +#: windows/views.py:864 +msgid "Table:" +msgstr "" + +#: windows/views.py:872 windows/views.py:1096 windows/views.py:1140 +#: windows/views.py:1246 windows/views.py:2794 +msgid "Insert" +msgstr "" + +#: windows/views.py:877 +msgid "Clone" +msgstr "" + +#: windows/views.py:899 +msgid "Rows" +msgstr "" + +#: windows/views.py:902 windows/views.py:2285 +msgid "Updated at" +msgstr "" + +#: windows/components/dataview.py:43 windows/components/dataview.py:67 +#: windows/components/dataview.py:89 windows/views.py:904 windows/views.py:2506 +msgid "Collation" +msgstr "" + +#: windows/views.py:920 +msgid "Diagram" +msgstr "" + +#: windows/views.py:931 windows/views.py:2254 +msgid "Database" +msgstr "" + +#: windows/views.py:986 windows/views.py:2691 +msgid "Base" +msgstr "" + +#: windows/views.py:1000 windows/views.py:2705 +msgid "Auto Increment" +msgstr "" + +#: windows/views.py:1028 windows/views.py:2733 +msgid "Default Collation" +msgstr "" + +#: windows/views.py:1048 windows/views.py:1501 windows/views.py:2751 +msgid "Options" +msgstr "" + +#: windows/views.py:1060 windows/views.py:1101 windows/views.py:1145 +msgid "Remove" +msgstr "" + +#: windows/views.py:1067 windows/views.py:1108 windows/views.py:1152 +msgid "Clear" +msgstr "" + +#: windows/views.py:1082 windows/views.py:2765 +msgid "Indexes" +msgstr "" + +#: windows/views.py:1126 +msgid "Foreign Keys" +msgstr "" + +#: windows/views.py:1170 +msgid "Checks" +msgstr "" + +#: windows/views.py:1238 windows/views.py:2786 +msgid "Columns:" +msgstr "" + +#: windows/views.py:1258 windows/views.py:2806 +msgid "Up" +msgstr "" + +#: windows/views.py:1265 windows/views.py:2813 +msgid "Down" +msgstr "" + +#: windows/views.py:1291 windows/views.py:1630 windows/views.py:1685 +msgid "Apply" +msgstr "" + +#: windows/views.py:1304 windows/views.py:1311 windows/views.py:2852 +#: windows/views.py:2859 +msgid "Add Index" +msgstr "" + +#: windows/views.py:1308 windows/views.py:2856 +msgid "Add PrimaryKey" +msgstr "" + +#: windows/views.py:1325 +msgid "Table" +msgstr "" + +#: windows/views.py:1361 +msgid "Definer" +msgstr "" + +#: windows/views.py:1381 +msgid "Schema" +msgstr "" + +#: windows/views.py:1407 +msgid "SQL security" +msgstr "" + +#: windows/views.py:1414 +msgid "DEFINER" +msgstr "" + +#: windows/views.py:1414 +msgid "INVOKER" +msgstr "" + +#: windows/views.py:1426 windows/views.py:2096 windows/views.py:2115 +#: windows/views.py:2359 +msgid "Algorithm" +msgstr "" + +#: windows/views.py:1428 windows/views.py:2081 windows/views.py:2114 +#: windows/views.py:2364 +msgid "UNDEFINED" +msgstr "" + +#: windows/views.py:1431 windows/views.py:2084 windows/views.py:2114 +#: windows/views.py:2367 +msgid "MERGE" +msgstr "" + +#: windows/views.py:1434 windows/views.py:2093 windows/views.py:2114 +#: windows/views.py:2370 +msgid "TEMPTABLE" +msgstr "" + +#: windows/views.py:1444 windows/views.py:2120 +msgid "View constraint" +msgstr "" + +#: windows/views.py:1446 windows/views.py:2119 +msgid "None" +msgstr "" + +#: windows/views.py:1449 windows/views.py:2119 +msgid "LOCAL" +msgstr "" + +#: windows/views.py:1452 +msgid "CASCADE" +msgstr "" + +#: windows/views.py:1455 +msgid "CHECK ONLY" +msgstr "" + +#: windows/views.py:1458 windows/views.py:2119 +msgid "READ ONLY" +msgstr "" + +#: windows/views.py:1470 +msgid "Force" +msgstr "" + +#: windows/views.py:1482 +msgid "Security barrier" +msgstr "" + +#: windows/views.py:1564 +msgid "Views" +msgstr "" + +#: windows/views.py:1572 +msgid "Triggers" +msgstr "" + +#: windows/views.py:1584 +#, python-format +msgid "Table `%(database_name)s`.`%(table_name)s`: %(total_rows) rows total" +msgstr "" + +#: windows/views.py:1594 +msgid "Insert record" +msgstr "" + +#: windows/views.py:1599 +msgid "Duplicate record" +msgstr "" + +#: windows/views.py:1606 +msgid "Delete record" +msgstr "" + +#: windows/views.py:1616 +msgid "Apply changes automatically" +msgstr "" + +#: windows/views.py:1618 windows/views.py:1619 +msgid "" +"If enabled, table edits are applied immediately without pressing Apply or" +" Cancel" +msgstr "" + +#: windows/views.py:1640 +msgid "Next" +msgstr "" + +#: windows/views.py:1648 +msgid "Filters" +msgstr "" + +#: windows/views.py:1688 +msgid "CTRL+ENTER" +msgstr "" + +#: windows/views.py:1708 +msgid "Insert row" +msgstr "" + +#: windows/views.py:1716 +msgid "Data" +msgstr "" + +#: windows/views.py:1770 windows/views.py:1820 +msgid "New" +msgstr "" + +#: windows/views.py:1797 +msgid "Query" +msgstr "" + +#: windows/views.py:1817 +msgid "Close" +msgstr "" + +#: windows/views.py:1830 +msgid "Query #2" +msgstr "" + +#: windows/views.py:2075 +msgid "Column5" +msgstr "" + +#: windows/views.py:2086 +msgid "Import" +msgstr "" + +#: windows/views.py:2111 +msgid "Read only" +msgstr "" + +#: windows/views.py:2119 +msgid "CASCADED" +msgstr "" + +#: windows/views.py:2119 +msgid "CHECK OPTION" +msgstr "" + +#: windows/views.py:2143 +msgid "collapsible" +msgstr "" + +#: windows/views.py:2165 +msgid "Column3" +msgstr "" + +#: windows/views.py:2166 +msgid "Column4" +msgstr "" + +#: windows/views.py:2203 +msgid "" +"Database " +"(*.db;*.db3;*.sdb;*.s3db;*.sqlite;*.sqlite3)|*.db;*.db3;*.sdb;*.s3db;*.sqlite;*.sqlite3" +msgstr "" + +#: windows/views.py:2215 +msgid "Port" +msgstr "" + +#: windows/views.py:2247 +msgid "Usage" +msgstr "" + +#: windows/views.py:2258 +#, python-format +msgid "%(total_rows)s" +msgstr "" + +#: windows/views.py:2263 +msgid "rows total" +msgstr "" + +#: windows/views.py:2282 +msgid "Lines" +msgstr "" + +#: windows/views.py:2314 +msgid "Temporary" +msgstr "" + +#: windows/views.py:2325 +msgid "Engine options" +msgstr "" + +#: windows/views.py:2384 +msgid "RadioBtn" +msgstr "" + +#: windows/views.py:2454 +msgid "Edit Column" +msgstr "" + +#: windows/views.py:2470 +msgid "Datatype" +msgstr "" + +#: windows/components/dataview.py:121 windows/views.py:2485 +msgid "Length/Set" +msgstr "" + +#: windows/components/dataview.py:51 windows/views.py:2524 +msgid "Unsigned" +msgstr "" + +#: windows/components/dataview.py:25 windows/components/dataview.py:52 +#: windows/components/dataview.py:75 windows/views.py:2530 +msgid "Allow NULL" +msgstr "" + +#: windows/views.py:2536 +msgid "Zero Fill" +msgstr "" + +#: windows/components/dataview.py:32 windows/components/dataview.py:56 +#: windows/components/dataview.py:78 windows/views.py:2547 +msgid "Default" +msgstr "" + +#: windows/components/dataview.py:36 windows/components/dataview.py:60 +#: windows/components/dataview.py:82 windows/views.py:2573 +msgid "Virtuality" +msgstr "" + +#: windows/components/dataview.py:39 windows/components/dataview.py:63 +#: windows/components/dataview.py:85 windows/components/dataview.py:241 +#: windows/views.py:2588 +msgid "Expression" +msgstr "" + +#: windows/components/dataview.py:28 +msgid "Check" +msgstr "" + +#: windows/components/dataview.py:53 +msgid "Zerofill" +msgstr "" + +#: windows/components/dataview.py:109 +msgid "#" +msgstr "" + +#: windows/components/dataview.py:117 +msgid "Data type" +msgstr "" + +#: windows/components/dataview.py:155 +msgid "Add column\tCTRL+INS" +msgstr "" + +#: windows/components/dataview.py:161 +msgid "Remove column\tCTRL+DEL" +msgstr "" + +#: windows/components/dataview.py:169 +msgid "Move up\tCTRL+UP" +msgstr "" + +#: windows/components/dataview.py:176 +msgid "Move down\tCTRL+D" +msgstr "" + +#: windows/components/dataview.py:199 +msgid "Create new index" +msgstr "" + +#: windows/components/dataview.py:214 +msgid "Append to index" +msgstr "" + +#: windows/components/dataview.py:228 +msgid "Column(s)/Expression" +msgstr "" + +#: windows/components/dataview.py:229 +msgid "Condition" +msgstr "" + +#: windows/components/dataview.py:259 +msgid "Column(s)" +msgstr "" + +#: windows/components/dataview.py:265 +msgid "Reference table" +msgstr "" + +#: windows/components/dataview.py:271 +msgid "Reference column(s)" +msgstr "" + +#: windows/components/dataview.py:277 +msgid "On UPDATE" +msgstr "" + +#: windows/components/dataview.py:283 +msgid "On DELETE" +msgstr "" + +#: windows/components/dataview.py:299 +msgid "Add foreign key" +msgstr "" + +#: windows/components/dataview.py:305 +msgid "Remove foreign key" +msgstr "" + +#: windows/components/popup.py:26 +msgid "No default value" +msgstr "" + +#: windows/components/popup.py:31 +msgid "NULL" +msgstr "" + +#: windows/components/popup.py:35 +msgid "AUTO INCREMENT" +msgstr "" + +#: windows/components/popup.py:39 +msgid "Text/Expression" +msgstr "" + +#: windows/dialogs/connections/view.py:258 +msgid "Connection established successfully" +msgstr "" + +#: windows/dialogs/connections/view.py:271 +msgid "Confirm save" +msgstr "" + +#: windows/dialogs/connections/view.py:314 +msgid "You have unsaved changes. Do you want to save them before continuing?" +msgstr "" + +#: windows/dialogs/connections/view.py:316 +msgid "Unsaved changes" +msgstr "" + +#: windows/dialogs/connections/view.py:545 +msgid "" +"This connection cannot work without TLS. TLS has been enabled " +"automatically." +msgstr "" + +#: windows/dialogs/connections/view.py:554 +msgid "Connection error" +msgstr "" + +#: windows/dialogs/connections/view.py:580 +#: windows/dialogs/connections/view.py:595 +msgid "Confirm delete" +msgstr "" + +#: windows/main/controller.py:170 +msgid "days" +msgstr "" + +#: windows/main/controller.py:171 +msgid "hours" +msgstr "" + +#: windows/main/controller.py:172 +msgid "minutes" +msgstr "" + +#: windows/main/controller.py:173 +msgid "seconds" +msgstr "" + +#: windows/main/controller.py:181 +#, python-brace-format +msgid "Memory used: {used} ({percentage:.2%})" +msgstr "" + +#: windows/main/controller.py:217 +msgid "Settings saved successfully" +msgstr "" + +#: windows/main/controller.py:296 +msgid "Version" +msgstr "" + +#: windows/main/controller.py:298 +msgid "Uptime" +msgstr "" + +#: windows/main/controller.py:467 +msgid "Delete table" +msgstr "" + +#: windows/main/controller.py:584 +msgid "Do you want delete the records?" +msgstr "" + +#: windows/main/tabs/database.py:71 +msgid "The connection to the database was lost." +msgstr "" + +#: windows/main/tabs/database.py:73 +msgid "Do you want to reconnect?" +msgstr "" + +#: windows/main/tabs/database.py:75 +msgid "Connection lost" +msgstr "" + +#: windows/main/tabs/database.py:85 +msgid "Reconnection failed:" +msgstr "" + +#: windows/main/tabs/database.py:86 windows/main/tabs/query.py:450 +#: windows/main/tabs/view.py:256 windows/main/tabs/view.py:282 +msgid "Error" +msgstr "" + +#: windows/main/tabs/query.py:305 +#, python-brace-format +msgid "{} rows affected" +msgstr "" + +#: windows/main/tabs/query.py:309 windows/main/tabs/query.py:331 +#, python-brace-format +msgid "Query {}" +msgstr "" + +#: windows/main/tabs/query.py:314 +#, python-brace-format +msgid "Query {} (Error)" +msgstr "" + +#: windows/main/tabs/query.py:326 +#, python-brace-format +msgid "Query {} ({} rows × {} cols)" +msgstr "" + +#: windows/main/tabs/query.py:353 +#, python-brace-format +msgid "{} rows" +msgstr "" + +#: windows/main/tabs/query.py:355 +#, python-brace-format +msgid "{:.1f} ms" +msgstr "" + +#: windows/main/tabs/query.py:358 +#, python-brace-format +msgid "{} warnings" +msgstr "" + +#: windows/main/tabs/query.py:370 +msgid "Error:" +msgstr "" + +#: windows/main/tabs/query.py:376 +msgid "Unknown error" +msgstr "" + +#: windows/main/tabs/query.py:449 +msgid "No active database connection" +msgstr "" + +#: windows/main/tabs/view.py:252 +msgid "View created successfully" +msgstr "" + +#: windows/main/tabs/view.py:252 +msgid "View updated successfully" +msgstr "" + +#: windows/main/tabs/view.py:253 windows/main/tabs/view.py:279 +msgid "Success" +msgstr "" + +#: windows/main/tabs/view.py:256 +#, python-brace-format +msgid "Error saving view: {}" +msgstr "" + +#: windows/main/tabs/view.py:269 +#, python-brace-format +msgid "Are you sure you want to delete view '{}'?" +msgstr "" + +#: windows/main/tabs/view.py:270 +msgid "Confirm Delete" +msgstr "" + +#: windows/main/tabs/view.py:279 +msgid "View deleted successfully" +msgstr "" + +#: windows/main/tabs/view.py:282 +#, python-brace-format +msgid "Error deleting view: {}" +msgstr "" + +#~ msgid "New Session" +#~ msgstr "" + +#~ msgid "connection" +#~ msgstr "" + +#~ msgid "directory" +#~ msgstr "" + diff --git a/scripts/locale/fr_FR/LC_MESSAGES/petersql.po b/scripts/locale/fr_FR/LC_MESSAGES/petersql.po new file mode 100644 index 0000000..4370e80 --- /dev/null +++ b/scripts/locale/fr_FR/LC_MESSAGES/petersql.po @@ -0,0 +1,952 @@ +#: helpers/__init__.py:16 +msgctxt "unit" +msgid "B" +msgstr "" + +#: helpers/__init__.py:17 +msgctxt "unit" +msgid "KB" +msgstr "" + +#: helpers/__init__.py:18 +msgctxt "unit" +msgid "MB" +msgstr "" + +#: helpers/__init__.py:19 +msgctxt "unit" +msgid "GB" +msgstr "" + +#: helpers/__init__.py:20 +msgctxt "unit" +msgid "TB" +msgstr "" + +#: structures/ssh_tunnel.py:166 +msgid "OpenSSH client not found." +msgstr "" + +#: windows/dialogs/connections/view.py:259 +#: windows/dialogs/connections/view.py:547 windows/main/controller.py:294 +#: windows/views.py:33 +msgid "Connection" +msgstr "" + +#: windows/components/dataview.py:113 windows/components/dataview.py:225 +#: windows/components/dataview.py:238 windows/components/dataview.py:253 +#: windows/views.py:47 windows/views.py:97 windows/views.py:898 +#: windows/views.py:958 windows/views.py:1341 windows/views.py:2246 +#: windows/views.py:2269 windows/views.py:2270 windows/views.py:2271 +#: windows/views.py:2272 windows/views.py:2273 windows/views.py:2274 +#: windows/views.py:2275 windows/views.py:2276 windows/views.py:2277 +#: windows/views.py:2281 windows/views.py:2462 windows/views.py:2663 +msgid "Name" +msgstr "" + +#: windows/views.py:48 windows/views.py:381 +msgid "Last connection" +msgstr "" + +#: windows/dialogs/connections/view.py:452 windows/views.py:61 +msgid "New directory" +msgstr "" + +#: windows/dialogs/connections/model.py:142 +#: windows/dialogs/connections/view.py:384 windows/views.py:65 +msgid "New connection" +msgstr "" + +#: windows/views.py:71 +msgid "Rename" +msgstr "" + +#: windows/views.py:76 +msgid "Clone connection" +msgstr "" + +#: windows/views.py:81 windows/views.py:471 windows/views.py:884 +#: windows/views.py:1251 windows/views.py:1283 windows/views.py:1542 +#: windows/views.py:2799 windows/views.py:2831 +msgid "Delete" +msgstr "" + +#: windows/views.py:111 windows/views.py:903 windows/views.py:1013 +#: windows/views.py:2286 windows/views.py:2718 +msgid "Engine" +msgstr "" + +#: windows/views.py:132 +msgid "Host + port" +msgstr "" + +#: windows/views.py:148 +msgid "Username" +msgstr "" + +#: windows/views.py:161 +msgid "Password" +msgstr "" + +#: windows/views.py:177 +msgid "Use TLS" +msgstr "" + +#: windows/views.py:180 +msgid "Use SSH tunnel" +msgstr "" + +#: windows/views.py:202 windows/views.py:2198 +msgid "Filename" +msgstr "" + +#: windows/views.py:207 windows/views.py:324 windows/views.py:2203 +#: windows/views.py:2395 +msgid "Select a file" +msgstr "" + +#: windows/views.py:207 windows/views.py:324 windows/views.py:2395 +msgid "*.*" +msgstr "" + +#: windows/components/dataview.py:70 windows/components/dataview.py:92 +#: windows/views.py:221 windows/views.py:905 windows/views.py:971 +#: windows/views.py:2287 windows/views.py:2560 windows/views.py:2676 +msgid "Comments" +msgstr "" + +#: windows/main/controller.py:217 windows/views.py:235 windows/views.py:598 +msgid "Settings" +msgstr "" + +#: windows/views.py:244 +msgid "SSH executable" +msgstr "" + +#: windows/views.py:249 +msgid "ssh" +msgstr "" + +#: windows/views.py:257 +msgid "SSH host + port" +msgstr "" + +#: windows/views.py:269 +msgid "SSH host + port (the SSH server that forwards traffic to the DB)" +msgstr "" + +#: windows/views.py:278 +msgid "SSH username" +msgstr "" + +#: windows/views.py:291 +msgid "SSH password" +msgstr "" + +#: windows/views.py:304 +msgid "Local port" +msgstr "" + +#: windows/views.py:310 +msgid "if the value is set to 0, the first available port will be used" +msgstr "" + +#: windows/views.py:319 +msgid "Identity file" +msgstr "" + +#: windows/views.py:335 +msgid "Remote host + port" +msgstr "" + +#: windows/views.py:347 +msgid "Remote host/port is the real DB target (defaults to DB Host/Port)." +msgstr "" + +#: windows/views.py:358 +msgid "SSH Tunnel" +msgstr "" + +#: windows/views.py:364 windows/views.py:901 windows/views.py:2284 +msgid "Created at" +msgstr "" + +#: windows/views.py:398 +msgid "Successful connections" +msgstr "" + +#: windows/views.py:415 +msgid "Unsuccessful connections" +msgstr "" + +#: windows/views.py:434 +msgid "Statistics" +msgstr "" + +#: windows/views.py:452 windows/views.py:1217 +msgid "Create" +msgstr "" + +#: windows/views.py:456 +msgid "Create connection" +msgstr "" + +#: windows/views.py:459 +msgid "Create directory" +msgstr "" + +#: windows/views.py:488 windows/views.py:712 windows/views.py:1286 +#: windows/views.py:1547 windows/views.py:1623 windows/views.py:2607 +#: windows/views.py:2834 +msgid "Cancel" +msgstr "" + +#: windows/views.py:493 windows/views.py:1552 windows/views.py:2617 +#: windows/views.py:2839 +msgid "Save" +msgstr "" + +#: windows/views.py:500 +msgid "Test" +msgstr "" + +#: windows/views.py:507 +msgid "Connect" +msgstr "" + +#: windows/views.py:610 +msgid "Language" +msgstr "" + +#: windows/views.py:615 +msgid "English" +msgstr "" + +#: windows/views.py:615 +msgid "Italian" +msgstr "" + +#: windows/views.py:615 +msgid "French" +msgstr "" + +#: windows/views.py:627 +msgid "Locale" +msgstr "" + +#: windows/views.py:648 +msgid "Edit Value" +msgstr "" + +#: windows/views.py:658 +msgid "Syntax" +msgstr "" + +#: windows/views.py:715 +msgid "Ok" +msgstr "" + +#: windows/views.py:746 +msgid "PeterSQL" +msgstr "" + +#: windows/views.py:752 +msgid "File" +msgstr "" + +#: windows/views.py:755 +msgid "About" +msgstr "" + +#: windows/views.py:758 +msgid "Help" +msgstr "" + +#: windows/views.py:763 +msgid "Open connection manager" +msgstr "" + +#: windows/views.py:765 +msgid "Disconnect from server" +msgstr "" + +#: windows/views.py:769 +msgid "tool" +msgstr "" + +#: windows/views.py:769 +msgid "Refresh" +msgstr "" + +#: windows/views.py:773 windows/views.py:775 +msgid "Add" +msgstr "" + +#: windows/views.py:809 windows/views.py:813 windows/views.py:1711 +#: windows/views.py:1839 +msgid "MyMenuItem" +msgstr "" + +#: windows/views.py:816 windows/views.py:1314 windows/views.py:2862 +msgid "MyMenu" +msgstr "" + +#: windows/views.py:831 +msgid "MyLabel" +msgstr "" + +#: windows/views.py:837 +msgid "Databases" +msgstr "" + +#: windows/views.py:838 windows/views.py:900 windows/views.py:2255 +#: windows/views.py:2283 +msgid "Size" +msgstr "" + +#: windows/views.py:839 +msgid "Elements" +msgstr "" + +#: windows/views.py:840 +msgid "Modified at" +msgstr "" + +#: windows/views.py:841 windows/views.py:912 +msgid "Tables" +msgstr "" + +#: windows/views.py:848 +msgid "System" +msgstr "" + +#: windows/views.py:864 +msgid "Table:" +msgstr "" + +#: windows/views.py:872 windows/views.py:1096 windows/views.py:1140 +#: windows/views.py:1246 windows/views.py:2794 +msgid "Insert" +msgstr "" + +#: windows/views.py:877 +msgid "Clone" +msgstr "" + +#: windows/views.py:899 +msgid "Rows" +msgstr "" + +#: windows/views.py:902 windows/views.py:2285 +msgid "Updated at" +msgstr "" + +#: windows/components/dataview.py:43 windows/components/dataview.py:67 +#: windows/components/dataview.py:89 windows/views.py:904 windows/views.py:2506 +msgid "Collation" +msgstr "" + +#: windows/views.py:920 +msgid "Diagram" +msgstr "" + +#: windows/views.py:931 windows/views.py:2254 +msgid "Database" +msgstr "" + +#: windows/views.py:986 windows/views.py:2691 +msgid "Base" +msgstr "" + +#: windows/views.py:1000 windows/views.py:2705 +msgid "Auto Increment" +msgstr "" + +#: windows/views.py:1028 windows/views.py:2733 +msgid "Default Collation" +msgstr "" + +#: windows/views.py:1048 windows/views.py:1501 windows/views.py:2751 +msgid "Options" +msgstr "" + +#: windows/views.py:1060 windows/views.py:1101 windows/views.py:1145 +msgid "Remove" +msgstr "" + +#: windows/views.py:1067 windows/views.py:1108 windows/views.py:1152 +msgid "Clear" +msgstr "" + +#: windows/views.py:1082 windows/views.py:2765 +msgid "Indexes" +msgstr "" + +#: windows/views.py:1126 +msgid "Foreign Keys" +msgstr "" + +#: windows/views.py:1170 +msgid "Checks" +msgstr "" + +#: windows/views.py:1238 windows/views.py:2786 +msgid "Columns:" +msgstr "" + +#: windows/views.py:1258 windows/views.py:2806 +msgid "Up" +msgstr "" + +#: windows/views.py:1265 windows/views.py:2813 +msgid "Down" +msgstr "" + +#: windows/views.py:1291 windows/views.py:1630 windows/views.py:1685 +msgid "Apply" +msgstr "" + +#: windows/views.py:1304 windows/views.py:1311 windows/views.py:2852 +#: windows/views.py:2859 +msgid "Add Index" +msgstr "" + +#: windows/views.py:1308 windows/views.py:2856 +msgid "Add PrimaryKey" +msgstr "" + +#: windows/views.py:1325 +msgid "Table" +msgstr "" + +#: windows/views.py:1361 +msgid "Definer" +msgstr "" + +#: windows/views.py:1381 +msgid "Schema" +msgstr "" + +#: windows/views.py:1407 +msgid "SQL security" +msgstr "" + +#: windows/views.py:1414 +msgid "DEFINER" +msgstr "" + +#: windows/views.py:1414 +msgid "INVOKER" +msgstr "" + +#: windows/views.py:1426 windows/views.py:2096 windows/views.py:2115 +#: windows/views.py:2359 +msgid "Algorithm" +msgstr "" + +#: windows/views.py:1428 windows/views.py:2081 windows/views.py:2114 +#: windows/views.py:2364 +msgid "UNDEFINED" +msgstr "" + +#: windows/views.py:1431 windows/views.py:2084 windows/views.py:2114 +#: windows/views.py:2367 +msgid "MERGE" +msgstr "" + +#: windows/views.py:1434 windows/views.py:2093 windows/views.py:2114 +#: windows/views.py:2370 +msgid "TEMPTABLE" +msgstr "" + +#: windows/views.py:1444 windows/views.py:2120 +msgid "View constraint" +msgstr "" + +#: windows/views.py:1446 windows/views.py:2119 +msgid "None" +msgstr "" + +#: windows/views.py:1449 windows/views.py:2119 +msgid "LOCAL" +msgstr "" + +#: windows/views.py:1452 +msgid "CASCADE" +msgstr "" + +#: windows/views.py:1455 +msgid "CHECK ONLY" +msgstr "" + +#: windows/views.py:1458 windows/views.py:2119 +msgid "READ ONLY" +msgstr "" + +#: windows/views.py:1470 +msgid "Force" +msgstr "" + +#: windows/views.py:1482 +msgid "Security barrier" +msgstr "" + +#: windows/views.py:1564 +msgid "Views" +msgstr "" + +#: windows/views.py:1572 +msgid "Triggers" +msgstr "" + +#: windows/views.py:1584 +#, python-format +msgid "Table `%(database_name)s`.`%(table_name)s`: %(total_rows) rows total" +msgstr "" + +#: windows/views.py:1594 +msgid "Insert record" +msgstr "" + +#: windows/views.py:1599 +msgid "Duplicate record" +msgstr "" + +#: windows/views.py:1606 +msgid "Delete record" +msgstr "" + +#: windows/views.py:1616 +msgid "Apply changes automatically" +msgstr "" + +#: windows/views.py:1618 windows/views.py:1619 +msgid "" +"If enabled, table edits are applied immediately without pressing Apply or" +" Cancel" +msgstr "" + +#: windows/views.py:1640 +msgid "Next" +msgstr "" + +#: windows/views.py:1648 +msgid "Filters" +msgstr "" + +#: windows/views.py:1688 +msgid "CTRL+ENTER" +msgstr "" + +#: windows/views.py:1708 +msgid "Insert row" +msgstr "" + +#: windows/views.py:1716 +msgid "Data" +msgstr "" + +#: windows/views.py:1770 windows/views.py:1820 +msgid "New" +msgstr "" + +#: windows/views.py:1797 +msgid "Query" +msgstr "" + +#: windows/views.py:1817 +msgid "Close" +msgstr "" + +#: windows/views.py:1830 +msgid "Query #2" +msgstr "" + +#: windows/views.py:2075 +msgid "Column5" +msgstr "" + +#: windows/views.py:2086 +msgid "Import" +msgstr "" + +#: windows/views.py:2111 +msgid "Read only" +msgstr "" + +#: windows/views.py:2119 +msgid "CASCADED" +msgstr "" + +#: windows/views.py:2119 +msgid "CHECK OPTION" +msgstr "" + +#: windows/views.py:2143 +msgid "collapsible" +msgstr "" + +#: windows/views.py:2165 +msgid "Column3" +msgstr "" + +#: windows/views.py:2166 +msgid "Column4" +msgstr "" + +#: windows/views.py:2203 +msgid "" +"Database " +"(*.db;*.db3;*.sdb;*.s3db;*.sqlite;*.sqlite3)|*.db;*.db3;*.sdb;*.s3db;*.sqlite;*.sqlite3" +msgstr "" + +#: windows/views.py:2215 +msgid "Port" +msgstr "" + +#: windows/views.py:2247 +msgid "Usage" +msgstr "" + +#: windows/views.py:2258 +#, python-format +msgid "%(total_rows)s" +msgstr "" + +#: windows/views.py:2263 +msgid "rows total" +msgstr "" + +#: windows/views.py:2282 +msgid "Lines" +msgstr "" + +#: windows/views.py:2314 +msgid "Temporary" +msgstr "" + +#: windows/views.py:2325 +msgid "Engine options" +msgstr "" + +#: windows/views.py:2384 +msgid "RadioBtn" +msgstr "" + +#: windows/views.py:2454 +msgid "Edit Column" +msgstr "" + +#: windows/views.py:2470 +msgid "Datatype" +msgstr "" + +#: windows/components/dataview.py:121 windows/views.py:2485 +msgid "Length/Set" +msgstr "" + +#: windows/components/dataview.py:51 windows/views.py:2524 +msgid "Unsigned" +msgstr "" + +#: windows/components/dataview.py:25 windows/components/dataview.py:52 +#: windows/components/dataview.py:75 windows/views.py:2530 +msgid "Allow NULL" +msgstr "" + +#: windows/views.py:2536 +msgid "Zero Fill" +msgstr "" + +#: windows/components/dataview.py:32 windows/components/dataview.py:56 +#: windows/components/dataview.py:78 windows/views.py:2547 +msgid "Default" +msgstr "" + +#: windows/components/dataview.py:36 windows/components/dataview.py:60 +#: windows/components/dataview.py:82 windows/views.py:2573 +msgid "Virtuality" +msgstr "" + +#: windows/components/dataview.py:39 windows/components/dataview.py:63 +#: windows/components/dataview.py:85 windows/components/dataview.py:241 +#: windows/views.py:2588 +msgid "Expression" +msgstr "" + +#: windows/components/dataview.py:28 +msgid "Check" +msgstr "" + +#: windows/components/dataview.py:53 +msgid "Zerofill" +msgstr "" + +#: windows/components/dataview.py:109 +msgid "#" +msgstr "" + +#: windows/components/dataview.py:117 +msgid "Data type" +msgstr "" + +#: windows/components/dataview.py:155 +msgid "Add column\tCTRL+INS" +msgstr "" + +#: windows/components/dataview.py:161 +msgid "Remove column\tCTRL+DEL" +msgstr "" + +#: windows/components/dataview.py:169 +msgid "Move up\tCTRL+UP" +msgstr "" + +#: windows/components/dataview.py:176 +msgid "Move down\tCTRL+D" +msgstr "" + +#: windows/components/dataview.py:199 +msgid "Create new index" +msgstr "" + +#: windows/components/dataview.py:214 +msgid "Append to index" +msgstr "" + +#: windows/components/dataview.py:228 +msgid "Column(s)/Expression" +msgstr "" + +#: windows/components/dataview.py:229 +msgid "Condition" +msgstr "" + +#: windows/components/dataview.py:259 +msgid "Column(s)" +msgstr "" + +#: windows/components/dataview.py:265 +msgid "Reference table" +msgstr "" + +#: windows/components/dataview.py:271 +msgid "Reference column(s)" +msgstr "" + +#: windows/components/dataview.py:277 +msgid "On UPDATE" +msgstr "" + +#: windows/components/dataview.py:283 +msgid "On DELETE" +msgstr "" + +#: windows/components/dataview.py:299 +msgid "Add foreign key" +msgstr "" + +#: windows/components/dataview.py:305 +msgid "Remove foreign key" +msgstr "" + +#: windows/components/popup.py:26 +msgid "No default value" +msgstr "" + +#: windows/components/popup.py:31 +msgid "NULL" +msgstr "" + +#: windows/components/popup.py:35 +msgid "AUTO INCREMENT" +msgstr "" + +#: windows/components/popup.py:39 +msgid "Text/Expression" +msgstr "" + +#: windows/dialogs/connections/view.py:258 +msgid "Connection established successfully" +msgstr "" + +#: windows/dialogs/connections/view.py:271 +msgid "Confirm save" +msgstr "" + +#: windows/dialogs/connections/view.py:314 +msgid "You have unsaved changes. Do you want to save them before continuing?" +msgstr "" + +#: windows/dialogs/connections/view.py:316 +msgid "Unsaved changes" +msgstr "" + +#: windows/dialogs/connections/view.py:545 +msgid "" +"This connection cannot work without TLS. TLS has been enabled " +"automatically." +msgstr "" + +#: windows/dialogs/connections/view.py:554 +msgid "Connection error" +msgstr "" + +#: windows/dialogs/connections/view.py:580 +#: windows/dialogs/connections/view.py:595 +msgid "Confirm delete" +msgstr "" + +#: windows/main/controller.py:170 +msgid "days" +msgstr "" + +#: windows/main/controller.py:171 +msgid "hours" +msgstr "" + +#: windows/main/controller.py:172 +msgid "minutes" +msgstr "" + +#: windows/main/controller.py:173 +msgid "seconds" +msgstr "" + +#: windows/main/controller.py:181 +#, python-brace-format +msgid "Memory used: {used} ({percentage:.2%})" +msgstr "" + +#: windows/main/controller.py:217 +msgid "Settings saved successfully" +msgstr "" + +#: windows/main/controller.py:296 +msgid "Version" +msgstr "" + +#: windows/main/controller.py:298 +msgid "Uptime" +msgstr "" + +#: windows/main/controller.py:467 +msgid "Delete table" +msgstr "" + +#: windows/main/controller.py:584 +msgid "Do you want delete the records?" +msgstr "" + +#: windows/main/tabs/database.py:71 +msgid "The connection to the database was lost." +msgstr "" + +#: windows/main/tabs/database.py:73 +msgid "Do you want to reconnect?" +msgstr "" + +#: windows/main/tabs/database.py:75 +msgid "Connection lost" +msgstr "" + +#: windows/main/tabs/database.py:85 +msgid "Reconnection failed:" +msgstr "" + +#: windows/main/tabs/database.py:86 windows/main/tabs/query.py:450 +#: windows/main/tabs/view.py:256 windows/main/tabs/view.py:282 +msgid "Error" +msgstr "" + +#: windows/main/tabs/query.py:305 +#, python-brace-format +msgid "{} rows affected" +msgstr "" + +#: windows/main/tabs/query.py:309 windows/main/tabs/query.py:331 +#, python-brace-format +msgid "Query {}" +msgstr "" + +#: windows/main/tabs/query.py:314 +#, python-brace-format +msgid "Query {} (Error)" +msgstr "" + +#: windows/main/tabs/query.py:326 +#, python-brace-format +msgid "Query {} ({} rows × {} cols)" +msgstr "" + +#: windows/main/tabs/query.py:353 +#, python-brace-format +msgid "{} rows" +msgstr "" + +#: windows/main/tabs/query.py:355 +#, python-brace-format +msgid "{:.1f} ms" +msgstr "" + +#: windows/main/tabs/query.py:358 +#, python-brace-format +msgid "{} warnings" +msgstr "" + +#: windows/main/tabs/query.py:370 +msgid "Error:" +msgstr "" + +#: windows/main/tabs/query.py:376 +msgid "Unknown error" +msgstr "" + +#: windows/main/tabs/query.py:449 +msgid "No active database connection" +msgstr "" + +#: windows/main/tabs/view.py:252 +msgid "View created successfully" +msgstr "" + +#: windows/main/tabs/view.py:252 +msgid "View updated successfully" +msgstr "" + +#: windows/main/tabs/view.py:253 windows/main/tabs/view.py:279 +msgid "Success" +msgstr "" + +#: windows/main/tabs/view.py:256 +#, python-brace-format +msgid "Error saving view: {}" +msgstr "" + +#: windows/main/tabs/view.py:269 +#, python-brace-format +msgid "Are you sure you want to delete view '{}'?" +msgstr "" + +#: windows/main/tabs/view.py:270 +msgid "Confirm Delete" +msgstr "" + +#: windows/main/tabs/view.py:279 +msgid "View deleted successfully" +msgstr "" + +#: windows/main/tabs/view.py:282 +#, python-brace-format +msgid "Error deleting view: {}" +msgstr "" + +#~ msgid "New Session" +#~ msgstr "" + +#~ msgid "connection" +#~ msgstr "" + +#~ msgid "directory" +#~ msgstr "" + diff --git a/scripts/locale/it_IT/LC_MESSAGES/petersql.po b/scripts/locale/it_IT/LC_MESSAGES/petersql.po new file mode 100644 index 0000000..4370e80 --- /dev/null +++ b/scripts/locale/it_IT/LC_MESSAGES/petersql.po @@ -0,0 +1,952 @@ +#: helpers/__init__.py:16 +msgctxt "unit" +msgid "B" +msgstr "" + +#: helpers/__init__.py:17 +msgctxt "unit" +msgid "KB" +msgstr "" + +#: helpers/__init__.py:18 +msgctxt "unit" +msgid "MB" +msgstr "" + +#: helpers/__init__.py:19 +msgctxt "unit" +msgid "GB" +msgstr "" + +#: helpers/__init__.py:20 +msgctxt "unit" +msgid "TB" +msgstr "" + +#: structures/ssh_tunnel.py:166 +msgid "OpenSSH client not found." +msgstr "" + +#: windows/dialogs/connections/view.py:259 +#: windows/dialogs/connections/view.py:547 windows/main/controller.py:294 +#: windows/views.py:33 +msgid "Connection" +msgstr "" + +#: windows/components/dataview.py:113 windows/components/dataview.py:225 +#: windows/components/dataview.py:238 windows/components/dataview.py:253 +#: windows/views.py:47 windows/views.py:97 windows/views.py:898 +#: windows/views.py:958 windows/views.py:1341 windows/views.py:2246 +#: windows/views.py:2269 windows/views.py:2270 windows/views.py:2271 +#: windows/views.py:2272 windows/views.py:2273 windows/views.py:2274 +#: windows/views.py:2275 windows/views.py:2276 windows/views.py:2277 +#: windows/views.py:2281 windows/views.py:2462 windows/views.py:2663 +msgid "Name" +msgstr "" + +#: windows/views.py:48 windows/views.py:381 +msgid "Last connection" +msgstr "" + +#: windows/dialogs/connections/view.py:452 windows/views.py:61 +msgid "New directory" +msgstr "" + +#: windows/dialogs/connections/model.py:142 +#: windows/dialogs/connections/view.py:384 windows/views.py:65 +msgid "New connection" +msgstr "" + +#: windows/views.py:71 +msgid "Rename" +msgstr "" + +#: windows/views.py:76 +msgid "Clone connection" +msgstr "" + +#: windows/views.py:81 windows/views.py:471 windows/views.py:884 +#: windows/views.py:1251 windows/views.py:1283 windows/views.py:1542 +#: windows/views.py:2799 windows/views.py:2831 +msgid "Delete" +msgstr "" + +#: windows/views.py:111 windows/views.py:903 windows/views.py:1013 +#: windows/views.py:2286 windows/views.py:2718 +msgid "Engine" +msgstr "" + +#: windows/views.py:132 +msgid "Host + port" +msgstr "" + +#: windows/views.py:148 +msgid "Username" +msgstr "" + +#: windows/views.py:161 +msgid "Password" +msgstr "" + +#: windows/views.py:177 +msgid "Use TLS" +msgstr "" + +#: windows/views.py:180 +msgid "Use SSH tunnel" +msgstr "" + +#: windows/views.py:202 windows/views.py:2198 +msgid "Filename" +msgstr "" + +#: windows/views.py:207 windows/views.py:324 windows/views.py:2203 +#: windows/views.py:2395 +msgid "Select a file" +msgstr "" + +#: windows/views.py:207 windows/views.py:324 windows/views.py:2395 +msgid "*.*" +msgstr "" + +#: windows/components/dataview.py:70 windows/components/dataview.py:92 +#: windows/views.py:221 windows/views.py:905 windows/views.py:971 +#: windows/views.py:2287 windows/views.py:2560 windows/views.py:2676 +msgid "Comments" +msgstr "" + +#: windows/main/controller.py:217 windows/views.py:235 windows/views.py:598 +msgid "Settings" +msgstr "" + +#: windows/views.py:244 +msgid "SSH executable" +msgstr "" + +#: windows/views.py:249 +msgid "ssh" +msgstr "" + +#: windows/views.py:257 +msgid "SSH host + port" +msgstr "" + +#: windows/views.py:269 +msgid "SSH host + port (the SSH server that forwards traffic to the DB)" +msgstr "" + +#: windows/views.py:278 +msgid "SSH username" +msgstr "" + +#: windows/views.py:291 +msgid "SSH password" +msgstr "" + +#: windows/views.py:304 +msgid "Local port" +msgstr "" + +#: windows/views.py:310 +msgid "if the value is set to 0, the first available port will be used" +msgstr "" + +#: windows/views.py:319 +msgid "Identity file" +msgstr "" + +#: windows/views.py:335 +msgid "Remote host + port" +msgstr "" + +#: windows/views.py:347 +msgid "Remote host/port is the real DB target (defaults to DB Host/Port)." +msgstr "" + +#: windows/views.py:358 +msgid "SSH Tunnel" +msgstr "" + +#: windows/views.py:364 windows/views.py:901 windows/views.py:2284 +msgid "Created at" +msgstr "" + +#: windows/views.py:398 +msgid "Successful connections" +msgstr "" + +#: windows/views.py:415 +msgid "Unsuccessful connections" +msgstr "" + +#: windows/views.py:434 +msgid "Statistics" +msgstr "" + +#: windows/views.py:452 windows/views.py:1217 +msgid "Create" +msgstr "" + +#: windows/views.py:456 +msgid "Create connection" +msgstr "" + +#: windows/views.py:459 +msgid "Create directory" +msgstr "" + +#: windows/views.py:488 windows/views.py:712 windows/views.py:1286 +#: windows/views.py:1547 windows/views.py:1623 windows/views.py:2607 +#: windows/views.py:2834 +msgid "Cancel" +msgstr "" + +#: windows/views.py:493 windows/views.py:1552 windows/views.py:2617 +#: windows/views.py:2839 +msgid "Save" +msgstr "" + +#: windows/views.py:500 +msgid "Test" +msgstr "" + +#: windows/views.py:507 +msgid "Connect" +msgstr "" + +#: windows/views.py:610 +msgid "Language" +msgstr "" + +#: windows/views.py:615 +msgid "English" +msgstr "" + +#: windows/views.py:615 +msgid "Italian" +msgstr "" + +#: windows/views.py:615 +msgid "French" +msgstr "" + +#: windows/views.py:627 +msgid "Locale" +msgstr "" + +#: windows/views.py:648 +msgid "Edit Value" +msgstr "" + +#: windows/views.py:658 +msgid "Syntax" +msgstr "" + +#: windows/views.py:715 +msgid "Ok" +msgstr "" + +#: windows/views.py:746 +msgid "PeterSQL" +msgstr "" + +#: windows/views.py:752 +msgid "File" +msgstr "" + +#: windows/views.py:755 +msgid "About" +msgstr "" + +#: windows/views.py:758 +msgid "Help" +msgstr "" + +#: windows/views.py:763 +msgid "Open connection manager" +msgstr "" + +#: windows/views.py:765 +msgid "Disconnect from server" +msgstr "" + +#: windows/views.py:769 +msgid "tool" +msgstr "" + +#: windows/views.py:769 +msgid "Refresh" +msgstr "" + +#: windows/views.py:773 windows/views.py:775 +msgid "Add" +msgstr "" + +#: windows/views.py:809 windows/views.py:813 windows/views.py:1711 +#: windows/views.py:1839 +msgid "MyMenuItem" +msgstr "" + +#: windows/views.py:816 windows/views.py:1314 windows/views.py:2862 +msgid "MyMenu" +msgstr "" + +#: windows/views.py:831 +msgid "MyLabel" +msgstr "" + +#: windows/views.py:837 +msgid "Databases" +msgstr "" + +#: windows/views.py:838 windows/views.py:900 windows/views.py:2255 +#: windows/views.py:2283 +msgid "Size" +msgstr "" + +#: windows/views.py:839 +msgid "Elements" +msgstr "" + +#: windows/views.py:840 +msgid "Modified at" +msgstr "" + +#: windows/views.py:841 windows/views.py:912 +msgid "Tables" +msgstr "" + +#: windows/views.py:848 +msgid "System" +msgstr "" + +#: windows/views.py:864 +msgid "Table:" +msgstr "" + +#: windows/views.py:872 windows/views.py:1096 windows/views.py:1140 +#: windows/views.py:1246 windows/views.py:2794 +msgid "Insert" +msgstr "" + +#: windows/views.py:877 +msgid "Clone" +msgstr "" + +#: windows/views.py:899 +msgid "Rows" +msgstr "" + +#: windows/views.py:902 windows/views.py:2285 +msgid "Updated at" +msgstr "" + +#: windows/components/dataview.py:43 windows/components/dataview.py:67 +#: windows/components/dataview.py:89 windows/views.py:904 windows/views.py:2506 +msgid "Collation" +msgstr "" + +#: windows/views.py:920 +msgid "Diagram" +msgstr "" + +#: windows/views.py:931 windows/views.py:2254 +msgid "Database" +msgstr "" + +#: windows/views.py:986 windows/views.py:2691 +msgid "Base" +msgstr "" + +#: windows/views.py:1000 windows/views.py:2705 +msgid "Auto Increment" +msgstr "" + +#: windows/views.py:1028 windows/views.py:2733 +msgid "Default Collation" +msgstr "" + +#: windows/views.py:1048 windows/views.py:1501 windows/views.py:2751 +msgid "Options" +msgstr "" + +#: windows/views.py:1060 windows/views.py:1101 windows/views.py:1145 +msgid "Remove" +msgstr "" + +#: windows/views.py:1067 windows/views.py:1108 windows/views.py:1152 +msgid "Clear" +msgstr "" + +#: windows/views.py:1082 windows/views.py:2765 +msgid "Indexes" +msgstr "" + +#: windows/views.py:1126 +msgid "Foreign Keys" +msgstr "" + +#: windows/views.py:1170 +msgid "Checks" +msgstr "" + +#: windows/views.py:1238 windows/views.py:2786 +msgid "Columns:" +msgstr "" + +#: windows/views.py:1258 windows/views.py:2806 +msgid "Up" +msgstr "" + +#: windows/views.py:1265 windows/views.py:2813 +msgid "Down" +msgstr "" + +#: windows/views.py:1291 windows/views.py:1630 windows/views.py:1685 +msgid "Apply" +msgstr "" + +#: windows/views.py:1304 windows/views.py:1311 windows/views.py:2852 +#: windows/views.py:2859 +msgid "Add Index" +msgstr "" + +#: windows/views.py:1308 windows/views.py:2856 +msgid "Add PrimaryKey" +msgstr "" + +#: windows/views.py:1325 +msgid "Table" +msgstr "" + +#: windows/views.py:1361 +msgid "Definer" +msgstr "" + +#: windows/views.py:1381 +msgid "Schema" +msgstr "" + +#: windows/views.py:1407 +msgid "SQL security" +msgstr "" + +#: windows/views.py:1414 +msgid "DEFINER" +msgstr "" + +#: windows/views.py:1414 +msgid "INVOKER" +msgstr "" + +#: windows/views.py:1426 windows/views.py:2096 windows/views.py:2115 +#: windows/views.py:2359 +msgid "Algorithm" +msgstr "" + +#: windows/views.py:1428 windows/views.py:2081 windows/views.py:2114 +#: windows/views.py:2364 +msgid "UNDEFINED" +msgstr "" + +#: windows/views.py:1431 windows/views.py:2084 windows/views.py:2114 +#: windows/views.py:2367 +msgid "MERGE" +msgstr "" + +#: windows/views.py:1434 windows/views.py:2093 windows/views.py:2114 +#: windows/views.py:2370 +msgid "TEMPTABLE" +msgstr "" + +#: windows/views.py:1444 windows/views.py:2120 +msgid "View constraint" +msgstr "" + +#: windows/views.py:1446 windows/views.py:2119 +msgid "None" +msgstr "" + +#: windows/views.py:1449 windows/views.py:2119 +msgid "LOCAL" +msgstr "" + +#: windows/views.py:1452 +msgid "CASCADE" +msgstr "" + +#: windows/views.py:1455 +msgid "CHECK ONLY" +msgstr "" + +#: windows/views.py:1458 windows/views.py:2119 +msgid "READ ONLY" +msgstr "" + +#: windows/views.py:1470 +msgid "Force" +msgstr "" + +#: windows/views.py:1482 +msgid "Security barrier" +msgstr "" + +#: windows/views.py:1564 +msgid "Views" +msgstr "" + +#: windows/views.py:1572 +msgid "Triggers" +msgstr "" + +#: windows/views.py:1584 +#, python-format +msgid "Table `%(database_name)s`.`%(table_name)s`: %(total_rows) rows total" +msgstr "" + +#: windows/views.py:1594 +msgid "Insert record" +msgstr "" + +#: windows/views.py:1599 +msgid "Duplicate record" +msgstr "" + +#: windows/views.py:1606 +msgid "Delete record" +msgstr "" + +#: windows/views.py:1616 +msgid "Apply changes automatically" +msgstr "" + +#: windows/views.py:1618 windows/views.py:1619 +msgid "" +"If enabled, table edits are applied immediately without pressing Apply or" +" Cancel" +msgstr "" + +#: windows/views.py:1640 +msgid "Next" +msgstr "" + +#: windows/views.py:1648 +msgid "Filters" +msgstr "" + +#: windows/views.py:1688 +msgid "CTRL+ENTER" +msgstr "" + +#: windows/views.py:1708 +msgid "Insert row" +msgstr "" + +#: windows/views.py:1716 +msgid "Data" +msgstr "" + +#: windows/views.py:1770 windows/views.py:1820 +msgid "New" +msgstr "" + +#: windows/views.py:1797 +msgid "Query" +msgstr "" + +#: windows/views.py:1817 +msgid "Close" +msgstr "" + +#: windows/views.py:1830 +msgid "Query #2" +msgstr "" + +#: windows/views.py:2075 +msgid "Column5" +msgstr "" + +#: windows/views.py:2086 +msgid "Import" +msgstr "" + +#: windows/views.py:2111 +msgid "Read only" +msgstr "" + +#: windows/views.py:2119 +msgid "CASCADED" +msgstr "" + +#: windows/views.py:2119 +msgid "CHECK OPTION" +msgstr "" + +#: windows/views.py:2143 +msgid "collapsible" +msgstr "" + +#: windows/views.py:2165 +msgid "Column3" +msgstr "" + +#: windows/views.py:2166 +msgid "Column4" +msgstr "" + +#: windows/views.py:2203 +msgid "" +"Database " +"(*.db;*.db3;*.sdb;*.s3db;*.sqlite;*.sqlite3)|*.db;*.db3;*.sdb;*.s3db;*.sqlite;*.sqlite3" +msgstr "" + +#: windows/views.py:2215 +msgid "Port" +msgstr "" + +#: windows/views.py:2247 +msgid "Usage" +msgstr "" + +#: windows/views.py:2258 +#, python-format +msgid "%(total_rows)s" +msgstr "" + +#: windows/views.py:2263 +msgid "rows total" +msgstr "" + +#: windows/views.py:2282 +msgid "Lines" +msgstr "" + +#: windows/views.py:2314 +msgid "Temporary" +msgstr "" + +#: windows/views.py:2325 +msgid "Engine options" +msgstr "" + +#: windows/views.py:2384 +msgid "RadioBtn" +msgstr "" + +#: windows/views.py:2454 +msgid "Edit Column" +msgstr "" + +#: windows/views.py:2470 +msgid "Datatype" +msgstr "" + +#: windows/components/dataview.py:121 windows/views.py:2485 +msgid "Length/Set" +msgstr "" + +#: windows/components/dataview.py:51 windows/views.py:2524 +msgid "Unsigned" +msgstr "" + +#: windows/components/dataview.py:25 windows/components/dataview.py:52 +#: windows/components/dataview.py:75 windows/views.py:2530 +msgid "Allow NULL" +msgstr "" + +#: windows/views.py:2536 +msgid "Zero Fill" +msgstr "" + +#: windows/components/dataview.py:32 windows/components/dataview.py:56 +#: windows/components/dataview.py:78 windows/views.py:2547 +msgid "Default" +msgstr "" + +#: windows/components/dataview.py:36 windows/components/dataview.py:60 +#: windows/components/dataview.py:82 windows/views.py:2573 +msgid "Virtuality" +msgstr "" + +#: windows/components/dataview.py:39 windows/components/dataview.py:63 +#: windows/components/dataview.py:85 windows/components/dataview.py:241 +#: windows/views.py:2588 +msgid "Expression" +msgstr "" + +#: windows/components/dataview.py:28 +msgid "Check" +msgstr "" + +#: windows/components/dataview.py:53 +msgid "Zerofill" +msgstr "" + +#: windows/components/dataview.py:109 +msgid "#" +msgstr "" + +#: windows/components/dataview.py:117 +msgid "Data type" +msgstr "" + +#: windows/components/dataview.py:155 +msgid "Add column\tCTRL+INS" +msgstr "" + +#: windows/components/dataview.py:161 +msgid "Remove column\tCTRL+DEL" +msgstr "" + +#: windows/components/dataview.py:169 +msgid "Move up\tCTRL+UP" +msgstr "" + +#: windows/components/dataview.py:176 +msgid "Move down\tCTRL+D" +msgstr "" + +#: windows/components/dataview.py:199 +msgid "Create new index" +msgstr "" + +#: windows/components/dataview.py:214 +msgid "Append to index" +msgstr "" + +#: windows/components/dataview.py:228 +msgid "Column(s)/Expression" +msgstr "" + +#: windows/components/dataview.py:229 +msgid "Condition" +msgstr "" + +#: windows/components/dataview.py:259 +msgid "Column(s)" +msgstr "" + +#: windows/components/dataview.py:265 +msgid "Reference table" +msgstr "" + +#: windows/components/dataview.py:271 +msgid "Reference column(s)" +msgstr "" + +#: windows/components/dataview.py:277 +msgid "On UPDATE" +msgstr "" + +#: windows/components/dataview.py:283 +msgid "On DELETE" +msgstr "" + +#: windows/components/dataview.py:299 +msgid "Add foreign key" +msgstr "" + +#: windows/components/dataview.py:305 +msgid "Remove foreign key" +msgstr "" + +#: windows/components/popup.py:26 +msgid "No default value" +msgstr "" + +#: windows/components/popup.py:31 +msgid "NULL" +msgstr "" + +#: windows/components/popup.py:35 +msgid "AUTO INCREMENT" +msgstr "" + +#: windows/components/popup.py:39 +msgid "Text/Expression" +msgstr "" + +#: windows/dialogs/connections/view.py:258 +msgid "Connection established successfully" +msgstr "" + +#: windows/dialogs/connections/view.py:271 +msgid "Confirm save" +msgstr "" + +#: windows/dialogs/connections/view.py:314 +msgid "You have unsaved changes. Do you want to save them before continuing?" +msgstr "" + +#: windows/dialogs/connections/view.py:316 +msgid "Unsaved changes" +msgstr "" + +#: windows/dialogs/connections/view.py:545 +msgid "" +"This connection cannot work without TLS. TLS has been enabled " +"automatically." +msgstr "" + +#: windows/dialogs/connections/view.py:554 +msgid "Connection error" +msgstr "" + +#: windows/dialogs/connections/view.py:580 +#: windows/dialogs/connections/view.py:595 +msgid "Confirm delete" +msgstr "" + +#: windows/main/controller.py:170 +msgid "days" +msgstr "" + +#: windows/main/controller.py:171 +msgid "hours" +msgstr "" + +#: windows/main/controller.py:172 +msgid "minutes" +msgstr "" + +#: windows/main/controller.py:173 +msgid "seconds" +msgstr "" + +#: windows/main/controller.py:181 +#, python-brace-format +msgid "Memory used: {used} ({percentage:.2%})" +msgstr "" + +#: windows/main/controller.py:217 +msgid "Settings saved successfully" +msgstr "" + +#: windows/main/controller.py:296 +msgid "Version" +msgstr "" + +#: windows/main/controller.py:298 +msgid "Uptime" +msgstr "" + +#: windows/main/controller.py:467 +msgid "Delete table" +msgstr "" + +#: windows/main/controller.py:584 +msgid "Do you want delete the records?" +msgstr "" + +#: windows/main/tabs/database.py:71 +msgid "The connection to the database was lost." +msgstr "" + +#: windows/main/tabs/database.py:73 +msgid "Do you want to reconnect?" +msgstr "" + +#: windows/main/tabs/database.py:75 +msgid "Connection lost" +msgstr "" + +#: windows/main/tabs/database.py:85 +msgid "Reconnection failed:" +msgstr "" + +#: windows/main/tabs/database.py:86 windows/main/tabs/query.py:450 +#: windows/main/tabs/view.py:256 windows/main/tabs/view.py:282 +msgid "Error" +msgstr "" + +#: windows/main/tabs/query.py:305 +#, python-brace-format +msgid "{} rows affected" +msgstr "" + +#: windows/main/tabs/query.py:309 windows/main/tabs/query.py:331 +#, python-brace-format +msgid "Query {}" +msgstr "" + +#: windows/main/tabs/query.py:314 +#, python-brace-format +msgid "Query {} (Error)" +msgstr "" + +#: windows/main/tabs/query.py:326 +#, python-brace-format +msgid "Query {} ({} rows × {} cols)" +msgstr "" + +#: windows/main/tabs/query.py:353 +#, python-brace-format +msgid "{} rows" +msgstr "" + +#: windows/main/tabs/query.py:355 +#, python-brace-format +msgid "{:.1f} ms" +msgstr "" + +#: windows/main/tabs/query.py:358 +#, python-brace-format +msgid "{} warnings" +msgstr "" + +#: windows/main/tabs/query.py:370 +msgid "Error:" +msgstr "" + +#: windows/main/tabs/query.py:376 +msgid "Unknown error" +msgstr "" + +#: windows/main/tabs/query.py:449 +msgid "No active database connection" +msgstr "" + +#: windows/main/tabs/view.py:252 +msgid "View created successfully" +msgstr "" + +#: windows/main/tabs/view.py:252 +msgid "View updated successfully" +msgstr "" + +#: windows/main/tabs/view.py:253 windows/main/tabs/view.py:279 +msgid "Success" +msgstr "" + +#: windows/main/tabs/view.py:256 +#, python-brace-format +msgid "Error saving view: {}" +msgstr "" + +#: windows/main/tabs/view.py:269 +#, python-brace-format +msgid "Are you sure you want to delete view '{}'?" +msgstr "" + +#: windows/main/tabs/view.py:270 +msgid "Confirm Delete" +msgstr "" + +#: windows/main/tabs/view.py:279 +msgid "View deleted successfully" +msgstr "" + +#: windows/main/tabs/view.py:282 +#, python-brace-format +msgid "Error deleting view: {}" +msgstr "" + +#~ msgid "New Session" +#~ msgstr "" + +#~ msgid "connection" +#~ msgstr "" + +#~ msgid "directory" +#~ msgstr "" + diff --git a/scripts/locale/petersql.pot b/scripts/locale/petersql.pot new file mode 100644 index 0000000..9cb1378 --- /dev/null +++ b/scripts/locale/petersql.pot @@ -0,0 +1,943 @@ +#: helpers/__init__.py:16 +msgctxt "unit" +msgid "B" +msgstr "" + +#: helpers/__init__.py:17 +msgctxt "unit" +msgid "KB" +msgstr "" + +#: helpers/__init__.py:18 +msgctxt "unit" +msgid "MB" +msgstr "" + +#: helpers/__init__.py:19 +msgctxt "unit" +msgid "GB" +msgstr "" + +#: helpers/__init__.py:20 +msgctxt "unit" +msgid "TB" +msgstr "" + +#: structures/ssh_tunnel.py:166 +msgid "OpenSSH client not found." +msgstr "" + +#: windows/dialogs/connections/view.py:259 +#: windows/dialogs/connections/view.py:547 windows/main/controller.py:294 +#: windows/views.py:33 +msgid "Connection" +msgstr "" + +#: windows/components/dataview.py:113 windows/components/dataview.py:225 +#: windows/components/dataview.py:238 windows/components/dataview.py:253 +#: windows/views.py:47 windows/views.py:97 windows/views.py:898 +#: windows/views.py:958 windows/views.py:1341 windows/views.py:2246 +#: windows/views.py:2269 windows/views.py:2270 windows/views.py:2271 +#: windows/views.py:2272 windows/views.py:2273 windows/views.py:2274 +#: windows/views.py:2275 windows/views.py:2276 windows/views.py:2277 +#: windows/views.py:2281 windows/views.py:2462 windows/views.py:2663 +msgid "Name" +msgstr "" + +#: windows/views.py:48 windows/views.py:381 +msgid "Last connection" +msgstr "" + +#: windows/dialogs/connections/view.py:452 windows/views.py:61 +msgid "New directory" +msgstr "" + +#: windows/dialogs/connections/model.py:142 +#: windows/dialogs/connections/view.py:384 windows/views.py:65 +msgid "New connection" +msgstr "" + +#: windows/views.py:71 +msgid "Rename" +msgstr "" + +#: windows/views.py:76 +msgid "Clone connection" +msgstr "" + +#: windows/views.py:81 windows/views.py:471 windows/views.py:884 +#: windows/views.py:1251 windows/views.py:1283 windows/views.py:1542 +#: windows/views.py:2799 windows/views.py:2831 +msgid "Delete" +msgstr "" + +#: windows/views.py:111 windows/views.py:903 windows/views.py:1013 +#: windows/views.py:2286 windows/views.py:2718 +msgid "Engine" +msgstr "" + +#: windows/views.py:132 +msgid "Host + port" +msgstr "" + +#: windows/views.py:148 +msgid "Username" +msgstr "" + +#: windows/views.py:161 +msgid "Password" +msgstr "" + +#: windows/views.py:177 +msgid "Use TLS" +msgstr "" + +#: windows/views.py:180 +msgid "Use SSH tunnel" +msgstr "" + +#: windows/views.py:202 windows/views.py:2198 +msgid "Filename" +msgstr "" + +#: windows/views.py:207 windows/views.py:324 windows/views.py:2203 +#: windows/views.py:2395 +msgid "Select a file" +msgstr "" + +#: windows/views.py:207 windows/views.py:324 windows/views.py:2395 +msgid "*.*" +msgstr "" + +#: windows/components/dataview.py:70 windows/components/dataview.py:92 +#: windows/views.py:221 windows/views.py:905 windows/views.py:971 +#: windows/views.py:2287 windows/views.py:2560 windows/views.py:2676 +msgid "Comments" +msgstr "" + +#: windows/main/controller.py:217 windows/views.py:235 windows/views.py:598 +msgid "Settings" +msgstr "" + +#: windows/views.py:244 +msgid "SSH executable" +msgstr "" + +#: windows/views.py:249 +msgid "ssh" +msgstr "" + +#: windows/views.py:257 +msgid "SSH host + port" +msgstr "" + +#: windows/views.py:269 +msgid "SSH host + port (the SSH server that forwards traffic to the DB)" +msgstr "" + +#: windows/views.py:278 +msgid "SSH username" +msgstr "" + +#: windows/views.py:291 +msgid "SSH password" +msgstr "" + +#: windows/views.py:304 +msgid "Local port" +msgstr "" + +#: windows/views.py:310 +msgid "if the value is set to 0, the first available port will be used" +msgstr "" + +#: windows/views.py:319 +msgid "Identity file" +msgstr "" + +#: windows/views.py:335 +msgid "Remote host + port" +msgstr "" + +#: windows/views.py:347 +msgid "Remote host/port is the real DB target (defaults to DB Host/Port)." +msgstr "" + +#: windows/views.py:358 +msgid "SSH Tunnel" +msgstr "" + +#: windows/views.py:364 windows/views.py:901 windows/views.py:2284 +msgid "Created at" +msgstr "" + +#: windows/views.py:398 +msgid "Successful connections" +msgstr "" + +#: windows/views.py:415 +msgid "Unsuccessful connections" +msgstr "" + +#: windows/views.py:434 +msgid "Statistics" +msgstr "" + +#: windows/views.py:452 windows/views.py:1217 +msgid "Create" +msgstr "" + +#: windows/views.py:456 +msgid "Create connection" +msgstr "" + +#: windows/views.py:459 +msgid "Create directory" +msgstr "" + +#: windows/views.py:488 windows/views.py:712 windows/views.py:1286 +#: windows/views.py:1547 windows/views.py:1623 windows/views.py:2607 +#: windows/views.py:2834 +msgid "Cancel" +msgstr "" + +#: windows/views.py:493 windows/views.py:1552 windows/views.py:2617 +#: windows/views.py:2839 +msgid "Save" +msgstr "" + +#: windows/views.py:500 +msgid "Test" +msgstr "" + +#: windows/views.py:507 +msgid "Connect" +msgstr "" + +#: windows/views.py:610 +msgid "Language" +msgstr "" + +#: windows/views.py:615 +msgid "English" +msgstr "" + +#: windows/views.py:615 +msgid "Italian" +msgstr "" + +#: windows/views.py:615 +msgid "French" +msgstr "" + +#: windows/views.py:627 +msgid "Locale" +msgstr "" + +#: windows/views.py:648 +msgid "Edit Value" +msgstr "" + +#: windows/views.py:658 +msgid "Syntax" +msgstr "" + +#: windows/views.py:715 +msgid "Ok" +msgstr "" + +#: windows/views.py:746 +msgid "PeterSQL" +msgstr "" + +#: windows/views.py:752 +msgid "File" +msgstr "" + +#: windows/views.py:755 +msgid "About" +msgstr "" + +#: windows/views.py:758 +msgid "Help" +msgstr "" + +#: windows/views.py:763 +msgid "Open connection manager" +msgstr "" + +#: windows/views.py:765 +msgid "Disconnect from server" +msgstr "" + +#: windows/views.py:769 +msgid "tool" +msgstr "" + +#: windows/views.py:769 +msgid "Refresh" +msgstr "" + +#: windows/views.py:773 windows/views.py:775 +msgid "Add" +msgstr "" + +#: windows/views.py:809 windows/views.py:813 windows/views.py:1711 +#: windows/views.py:1839 +msgid "MyMenuItem" +msgstr "" + +#: windows/views.py:816 windows/views.py:1314 windows/views.py:2862 +msgid "MyMenu" +msgstr "" + +#: windows/views.py:831 +msgid "MyLabel" +msgstr "" + +#: windows/views.py:837 +msgid "Databases" +msgstr "" + +#: windows/views.py:838 windows/views.py:900 windows/views.py:2255 +#: windows/views.py:2283 +msgid "Size" +msgstr "" + +#: windows/views.py:839 +msgid "Elements" +msgstr "" + +#: windows/views.py:840 +msgid "Modified at" +msgstr "" + +#: windows/views.py:841 windows/views.py:912 +msgid "Tables" +msgstr "" + +#: windows/views.py:848 +msgid "System" +msgstr "" + +#: windows/views.py:864 +msgid "Table:" +msgstr "" + +#: windows/views.py:872 windows/views.py:1096 windows/views.py:1140 +#: windows/views.py:1246 windows/views.py:2794 +msgid "Insert" +msgstr "" + +#: windows/views.py:877 +msgid "Clone" +msgstr "" + +#: windows/views.py:899 +msgid "Rows" +msgstr "" + +#: windows/views.py:902 windows/views.py:2285 +msgid "Updated at" +msgstr "" + +#: windows/components/dataview.py:43 windows/components/dataview.py:67 +#: windows/components/dataview.py:89 windows/views.py:904 windows/views.py:2506 +msgid "Collation" +msgstr "" + +#: windows/views.py:920 +msgid "Diagram" +msgstr "" + +#: windows/views.py:931 windows/views.py:2254 +msgid "Database" +msgstr "" + +#: windows/views.py:986 windows/views.py:2691 +msgid "Base" +msgstr "" + +#: windows/views.py:1000 windows/views.py:2705 +msgid "Auto Increment" +msgstr "" + +#: windows/views.py:1028 windows/views.py:2733 +msgid "Default Collation" +msgstr "" + +#: windows/views.py:1048 windows/views.py:1501 windows/views.py:2751 +msgid "Options" +msgstr "" + +#: windows/views.py:1060 windows/views.py:1101 windows/views.py:1145 +msgid "Remove" +msgstr "" + +#: windows/views.py:1067 windows/views.py:1108 windows/views.py:1152 +msgid "Clear" +msgstr "" + +#: windows/views.py:1082 windows/views.py:2765 +msgid "Indexes" +msgstr "" + +#: windows/views.py:1126 +msgid "Foreign Keys" +msgstr "" + +#: windows/views.py:1170 +msgid "Checks" +msgstr "" + +#: windows/views.py:1238 windows/views.py:2786 +msgid "Columns:" +msgstr "" + +#: windows/views.py:1258 windows/views.py:2806 +msgid "Up" +msgstr "" + +#: windows/views.py:1265 windows/views.py:2813 +msgid "Down" +msgstr "" + +#: windows/views.py:1291 windows/views.py:1630 windows/views.py:1685 +msgid "Apply" +msgstr "" + +#: windows/views.py:1304 windows/views.py:1311 windows/views.py:2852 +#: windows/views.py:2859 +msgid "Add Index" +msgstr "" + +#: windows/views.py:1308 windows/views.py:2856 +msgid "Add PrimaryKey" +msgstr "" + +#: windows/views.py:1325 +msgid "Table" +msgstr "" + +#: windows/views.py:1361 +msgid "Definer" +msgstr "" + +#: windows/views.py:1381 +msgid "Schema" +msgstr "" + +#: windows/views.py:1407 +msgid "SQL security" +msgstr "" + +#: windows/views.py:1414 +msgid "DEFINER" +msgstr "" + +#: windows/views.py:1414 +msgid "INVOKER" +msgstr "" + +#: windows/views.py:1426 windows/views.py:2096 windows/views.py:2115 +#: windows/views.py:2359 +msgid "Algorithm" +msgstr "" + +#: windows/views.py:1428 windows/views.py:2081 windows/views.py:2114 +#: windows/views.py:2364 +msgid "UNDEFINED" +msgstr "" + +#: windows/views.py:1431 windows/views.py:2084 windows/views.py:2114 +#: windows/views.py:2367 +msgid "MERGE" +msgstr "" + +#: windows/views.py:1434 windows/views.py:2093 windows/views.py:2114 +#: windows/views.py:2370 +msgid "TEMPTABLE" +msgstr "" + +#: windows/views.py:1444 windows/views.py:2120 +msgid "View constraint" +msgstr "" + +#: windows/views.py:1446 windows/views.py:2119 +msgid "None" +msgstr "" + +#: windows/views.py:1449 windows/views.py:2119 +msgid "LOCAL" +msgstr "" + +#: windows/views.py:1452 +msgid "CASCADE" +msgstr "" + +#: windows/views.py:1455 +msgid "CHECK ONLY" +msgstr "" + +#: windows/views.py:1458 windows/views.py:2119 +msgid "READ ONLY" +msgstr "" + +#: windows/views.py:1470 +msgid "Force" +msgstr "" + +#: windows/views.py:1482 +msgid "Security barrier" +msgstr "" + +#: windows/views.py:1564 +msgid "Views" +msgstr "" + +#: windows/views.py:1572 +msgid "Triggers" +msgstr "" + +#: windows/views.py:1584 +#, python-format +msgid "Table `%(database_name)s`.`%(table_name)s`: %(total_rows) rows total" +msgstr "" + +#: windows/views.py:1594 +msgid "Insert record" +msgstr "" + +#: windows/views.py:1599 +msgid "Duplicate record" +msgstr "" + +#: windows/views.py:1606 +msgid "Delete record" +msgstr "" + +#: windows/views.py:1616 +msgid "Apply changes automatically" +msgstr "" + +#: windows/views.py:1618 windows/views.py:1619 +msgid "" +"If enabled, table edits are applied immediately without pressing Apply or" +" Cancel" +msgstr "" + +#: windows/views.py:1640 +msgid "Next" +msgstr "" + +#: windows/views.py:1648 +msgid "Filters" +msgstr "" + +#: windows/views.py:1688 +msgid "CTRL+ENTER" +msgstr "" + +#: windows/views.py:1708 +msgid "Insert row" +msgstr "" + +#: windows/views.py:1716 +msgid "Data" +msgstr "" + +#: windows/views.py:1770 windows/views.py:1820 +msgid "New" +msgstr "" + +#: windows/views.py:1797 +msgid "Query" +msgstr "" + +#: windows/views.py:1817 +msgid "Close" +msgstr "" + +#: windows/views.py:1830 +msgid "Query #2" +msgstr "" + +#: windows/views.py:2075 +msgid "Column5" +msgstr "" + +#: windows/views.py:2086 +msgid "Import" +msgstr "" + +#: windows/views.py:2111 +msgid "Read only" +msgstr "" + +#: windows/views.py:2119 +msgid "CASCADED" +msgstr "" + +#: windows/views.py:2119 +msgid "CHECK OPTION" +msgstr "" + +#: windows/views.py:2143 +msgid "collapsible" +msgstr "" + +#: windows/views.py:2165 +msgid "Column3" +msgstr "" + +#: windows/views.py:2166 +msgid "Column4" +msgstr "" + +#: windows/views.py:2203 +msgid "" +"Database " +"(*.db;*.db3;*.sdb;*.s3db;*.sqlite;*.sqlite3)|*.db;*.db3;*.sdb;*.s3db;*.sqlite;*.sqlite3" +msgstr "" + +#: windows/views.py:2215 +msgid "Port" +msgstr "" + +#: windows/views.py:2247 +msgid "Usage" +msgstr "" + +#: windows/views.py:2258 +#, python-format +msgid "%(total_rows)s" +msgstr "" + +#: windows/views.py:2263 +msgid "rows total" +msgstr "" + +#: windows/views.py:2282 +msgid "Lines" +msgstr "" + +#: windows/views.py:2314 +msgid "Temporary" +msgstr "" + +#: windows/views.py:2325 +msgid "Engine options" +msgstr "" + +#: windows/views.py:2384 +msgid "RadioBtn" +msgstr "" + +#: windows/views.py:2454 +msgid "Edit Column" +msgstr "" + +#: windows/views.py:2470 +msgid "Datatype" +msgstr "" + +#: windows/components/dataview.py:121 windows/views.py:2485 +msgid "Length/Set" +msgstr "" + +#: windows/components/dataview.py:51 windows/views.py:2524 +msgid "Unsigned" +msgstr "" + +#: windows/components/dataview.py:25 windows/components/dataview.py:52 +#: windows/components/dataview.py:75 windows/views.py:2530 +msgid "Allow NULL" +msgstr "" + +#: windows/views.py:2536 +msgid "Zero Fill" +msgstr "" + +#: windows/components/dataview.py:32 windows/components/dataview.py:56 +#: windows/components/dataview.py:78 windows/views.py:2547 +msgid "Default" +msgstr "" + +#: windows/components/dataview.py:36 windows/components/dataview.py:60 +#: windows/components/dataview.py:82 windows/views.py:2573 +msgid "Virtuality" +msgstr "" + +#: windows/components/dataview.py:39 windows/components/dataview.py:63 +#: windows/components/dataview.py:85 windows/components/dataview.py:241 +#: windows/views.py:2588 +msgid "Expression" +msgstr "" + +#: windows/components/dataview.py:28 +msgid "Check" +msgstr "" + +#: windows/components/dataview.py:53 +msgid "Zerofill" +msgstr "" + +#: windows/components/dataview.py:109 +msgid "#" +msgstr "" + +#: windows/components/dataview.py:117 +msgid "Data type" +msgstr "" + +#: windows/components/dataview.py:155 +msgid "Add column\tCTRL+INS" +msgstr "" + +#: windows/components/dataview.py:161 +msgid "Remove column\tCTRL+DEL" +msgstr "" + +#: windows/components/dataview.py:169 +msgid "Move up\tCTRL+UP" +msgstr "" + +#: windows/components/dataview.py:176 +msgid "Move down\tCTRL+D" +msgstr "" + +#: windows/components/dataview.py:199 +msgid "Create new index" +msgstr "" + +#: windows/components/dataview.py:214 +msgid "Append to index" +msgstr "" + +#: windows/components/dataview.py:228 +msgid "Column(s)/Expression" +msgstr "" + +#: windows/components/dataview.py:229 +msgid "Condition" +msgstr "" + +#: windows/components/dataview.py:259 +msgid "Column(s)" +msgstr "" + +#: windows/components/dataview.py:265 +msgid "Reference table" +msgstr "" + +#: windows/components/dataview.py:271 +msgid "Reference column(s)" +msgstr "" + +#: windows/components/dataview.py:277 +msgid "On UPDATE" +msgstr "" + +#: windows/components/dataview.py:283 +msgid "On DELETE" +msgstr "" + +#: windows/components/dataview.py:299 +msgid "Add foreign key" +msgstr "" + +#: windows/components/dataview.py:305 +msgid "Remove foreign key" +msgstr "" + +#: windows/components/popup.py:26 +msgid "No default value" +msgstr "" + +#: windows/components/popup.py:31 +msgid "NULL" +msgstr "" + +#: windows/components/popup.py:35 +msgid "AUTO INCREMENT" +msgstr "" + +#: windows/components/popup.py:39 +msgid "Text/Expression" +msgstr "" + +#: windows/dialogs/connections/view.py:258 +msgid "Connection established successfully" +msgstr "" + +#: windows/dialogs/connections/view.py:271 +msgid "Confirm save" +msgstr "" + +#: windows/dialogs/connections/view.py:314 +msgid "You have unsaved changes. Do you want to save them before continuing?" +msgstr "" + +#: windows/dialogs/connections/view.py:316 +msgid "Unsaved changes" +msgstr "" + +#: windows/dialogs/connections/view.py:545 +msgid "" +"This connection cannot work without TLS. TLS has been enabled " +"automatically." +msgstr "" + +#: windows/dialogs/connections/view.py:554 +msgid "Connection error" +msgstr "" + +#: windows/dialogs/connections/view.py:580 +#: windows/dialogs/connections/view.py:595 +msgid "Confirm delete" +msgstr "" + +#: windows/main/controller.py:170 +msgid "days" +msgstr "" + +#: windows/main/controller.py:171 +msgid "hours" +msgstr "" + +#: windows/main/controller.py:172 +msgid "minutes" +msgstr "" + +#: windows/main/controller.py:173 +msgid "seconds" +msgstr "" + +#: windows/main/controller.py:181 +#, python-brace-format +msgid "Memory used: {used} ({percentage:.2%})" +msgstr "" + +#: windows/main/controller.py:217 +msgid "Settings saved successfully" +msgstr "" + +#: windows/main/controller.py:296 +msgid "Version" +msgstr "" + +#: windows/main/controller.py:298 +msgid "Uptime" +msgstr "" + +#: windows/main/controller.py:467 +msgid "Delete table" +msgstr "" + +#: windows/main/controller.py:584 +msgid "Do you want delete the records?" +msgstr "" + +#: windows/main/tabs/database.py:71 +msgid "The connection to the database was lost." +msgstr "" + +#: windows/main/tabs/database.py:73 +msgid "Do you want to reconnect?" +msgstr "" + +#: windows/main/tabs/database.py:75 +msgid "Connection lost" +msgstr "" + +#: windows/main/tabs/database.py:85 +msgid "Reconnection failed:" +msgstr "" + +#: windows/main/tabs/database.py:86 windows/main/tabs/query.py:450 +#: windows/main/tabs/view.py:256 windows/main/tabs/view.py:282 +msgid "Error" +msgstr "" + +#: windows/main/tabs/query.py:305 +#, python-brace-format +msgid "{} rows affected" +msgstr "" + +#: windows/main/tabs/query.py:309 windows/main/tabs/query.py:331 +#, python-brace-format +msgid "Query {}" +msgstr "" + +#: windows/main/tabs/query.py:314 +#, python-brace-format +msgid "Query {} (Error)" +msgstr "" + +#: windows/main/tabs/query.py:326 +#, python-brace-format +msgid "Query {} ({} rows × {} cols)" +msgstr "" + +#: windows/main/tabs/query.py:353 +#, python-brace-format +msgid "{} rows" +msgstr "" + +#: windows/main/tabs/query.py:355 +#, python-brace-format +msgid "{:.1f} ms" +msgstr "" + +#: windows/main/tabs/query.py:358 +#, python-brace-format +msgid "{} warnings" +msgstr "" + +#: windows/main/tabs/query.py:370 +msgid "Error:" +msgstr "" + +#: windows/main/tabs/query.py:376 +msgid "Unknown error" +msgstr "" + +#: windows/main/tabs/query.py:449 +msgid "No active database connection" +msgstr "" + +#: windows/main/tabs/view.py:252 +msgid "View created successfully" +msgstr "" + +#: windows/main/tabs/view.py:252 +msgid "View updated successfully" +msgstr "" + +#: windows/main/tabs/view.py:253 windows/main/tabs/view.py:279 +msgid "Success" +msgstr "" + +#: windows/main/tabs/view.py:256 +#, python-brace-format +msgid "Error saving view: {}" +msgstr "" + +#: windows/main/tabs/view.py:269 +#, python-brace-format +msgid "Are you sure you want to delete view '{}'?" +msgstr "" + +#: windows/main/tabs/view.py:270 +msgid "Confirm Delete" +msgstr "" + +#: windows/main/tabs/view.py:279 +msgid "View deleted successfully" +msgstr "" + +#: windows/main/tabs/view.py:282 +#, python-brace-format +msgid "Error deleting view: {}" +msgstr "" + diff --git a/scripts/locales.py b/scripts/locales.py index 7267f38..f8a6d24 100755 --- a/scripts/locales.py +++ b/scripts/locales.py @@ -1,11 +1,17 @@ #!/usr/bin/env python3 import argparse +import os import shutil import subprocess +import sys from pathlib import Path +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from constants import Language + APP_NAME = "petersql" -LANGUAGES = ["fr_FR", "it_IT", "es_ES", "en_US", "de_DE"] +LANGUAGES = Language.get_codes() BASE_DIR = Path(__file__).parent LOCALE_DIR = BASE_DIR.joinpath("locale") diff --git a/scripts/runtest.py b/scripts/runtest.py new file mode 100755 index 0000000..79ceca3 --- /dev/null +++ b/scripts/runtest.py @@ -0,0 +1,198 @@ +#!/usr/bin/env python3 +""" +Unified test runner +By default: runs unit tests only (excludes integration tests) +With --all: runs ALL tests (unit + integration) +With --update: runs ALL tests (unit + integration) and updates README badges +""" + +import argparse +import subprocess +import os +import re +import sys + +# Add project root to Python path for imports +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from structures.connection import ConnectionEngine + +README = "README.md" +TESTS_DIR = "tests/engines" +RESULTS_FILE = "/tmp/pytest_results.txt" + + +def get_engine_color(engine, results_content): + """Determine color from test results for a specific engine.""" + pattern = f"tests/engines/{engine}/" + + has_passed = f"{pattern}" in results_content and "PASSED" in results_content + has_failed = f"{pattern}" in results_content and "FAILED" in results_content + + if has_passed: + if has_failed: + return "orange" + else: + return "green" + elif has_failed: + return "red" + else: + return "lightgrey" + + +def extract_versions_badge(file_path, var_name): + """Extract versions from conftest file by importing the module directly.""" + if not os.path.exists(file_path): + return "" + + try: + import importlib.util + + spec = importlib.util.spec_from_file_location("conftest", file_path) + if spec is None or spec.loader is None: + return "" + + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + + versions = getattr(module, var_name, []) + if not isinstance(versions, list): + return "" + + # Extract version strings after : + versions = [v.split(':', 1)[1] for v in versions if ':' in v] + + # Sort versions using natural sort + def natural_sort_key(s): + return [int(t) if t.isdigit() else t.lower() for t in re.split(r'(\d+)', s)] + + versions.sort(key=natural_sort_key) + + # Join with | + result = '|'.join(versions) + + # URL encode | + result = result.replace('|', '%20%7C%20') + + return result + + except (ImportError, AttributeError, IOError, Exception): + return "" + + +def update_badges(): + """Update README badges based on test results.""" + if not os.path.exists(RESULTS_FILE): + return + + try: + with open(RESULTS_FILE, 'r') as f: + results_content = f.read() + except IOError: + return + + # Extract coverage percentage + match = re.search(r'TOTAL\s+\d+\s+\d+\s+(\d+)%', results_content) + coverage = match.group(1) if match else "0" + + print(f"\nCoverage: {coverage}%") + print("\nAnalyzing results for badge updates...") + + colors = {} + for engine in ConnectionEngine: + colors[engine] = get_engine_color(engine.value.dialect, results_content) + print(f" {engine.value.name}: {colors[engine]}") + + # Update README + if os.path.exists(README): + try: + with open(README, 'r') as f: + content = f.read() + + # Update coverage badge + if coverage: + content = re.sub(r'coverage-\d+%', f'coverage-{coverage}%', content) + print(f" Coverage badge updated: {coverage}%") + + # Update engine badges + + for engine, color in colors.items(): + versions = extract_versions_badge(f"tests/engines/{engine.name.lower()}/conftest.py", f'{engine.name.upper()}_VERSIONS') + content = re.sub( + rf'!\[{engine.value.name}\]\(https://img.shields.io/badge/{engine.value.name}-[^)]*\)', + f'![{engine.value.name}](https://img.shields.io/badge/{engine.value.name}-{versions}-{color})', + content + ) + + with open(README, 'w') as f: + f.write(content) + + print("\nREADME.md updated") + + except IOError as e: + print(f"Error updating README: {e}") + + # Cleanup + try: + os.remove(RESULTS_FILE) + except OSError: + pass + + +def main(): + parser = argparse.ArgumentParser(description='Unified test runner') + parser.add_argument('--all', action='store_true', + help='Run all tests (unit + integration)') + parser.add_argument('--update', action='store_true', + help='Run all tests (unit + integration) and update README badges') + + args = parser.parse_args() + + if args.all: + print("Running ALL tests (unit + integration)...") + result = subprocess.run(['uv', 'run', 'pytest', 'tests/', '--tb=no']) + exit_code = result.returncode + + elif args.update: + print("Running ALL tests (unit + integration) and updating badges...") + + # Run pytest with pipes to capture output in real-time + try: + with open(RESULTS_FILE, 'w') as f: + process = subprocess.Popen( + ['uv', 'run', 'pytest', 'tests/', '--tb=no'], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True + ) + + # Read and print output in real-time + for line in iter(process.stdout.readline, ''): + print(line, end='', flush=True) + f.write(line) + + exit_code = process.wait() + + # Now update badges + update_badges() + + except (IOError, OSError) as e: + print(f"Error running tests: {e}") + exit_code = 1 + + else: + print("Running unit tests...") + result = subprocess.run([ + 'uv', 'run', 'pytest', 'tests/', '--tb=short', '-m', 'not integration' + ]) + exit_code = result.returncode + + print(f"\nLocal tests completed") + print("\nNote: Integration tests excluded. Run with --update for all tests with badge updates, or --all for full test suite.") + + print(f"\nDone. Pytest exit code: {exit_code}") + return exit_code + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/scripts/runtest.sh b/scripts/runtest.sh deleted file mode 100755 index 6d135a5..0000000 --- a/scripts/runtest.sh +++ /dev/null @@ -1,113 +0,0 @@ -#!/bin/bash -# Unified test runner with badge updates -# Runs all tests once and updates README badges based on results -# Replaces: pytest, update_coverage_badge, update_version_badges - -README="README.md" -TESTS_DIR="tests/engines" -RESULTS_FILE="/tmp/pytest_results.txt" -COVERAGE_FILE="/tmp/pytest_coverage.txt" - -echo "Running all tests with coverage..." - -# Run all tests once with coverage, save results -uv run pytest tests/ --cov=. --cov-report=term --tb=no -v 2>&1 | tee "$RESULTS_FILE" -PYTEST_EXIT_CODE=${PIPESTATUS[0]} - -# Extract coverage percentage -COVERAGE=$(grep "TOTAL" "$RESULTS_FILE" | awk '{print $4}' | sed 's/%//' | head -1) -echo "" -echo "Coverage: ${COVERAGE}%" - -echo "" -echo "Analyzing results for badge updates..." - -# Function to determine color from test results -get_engine_color() { - local engine=$1 - local pattern="tests/engines/${engine}/" - - # Count passed and failed for this engine - local passed=$(grep -c "PASSED" "$RESULTS_FILE" | grep -c "$pattern" 2>/dev/null || echo 0) - local failed=$(grep -c "FAILED" "$RESULTS_FILE" | grep -c "$pattern" 2>/dev/null || echo 0) - - # Check if any tests for this engine exist in results - if grep -q "$pattern.*PASSED" "$RESULTS_FILE" 2>/dev/null; then - if grep -q "$pattern.*FAILED" "$RESULTS_FILE" 2>/dev/null; then - echo "orange" - else - echo "green" - fi - elif grep -q "$pattern.*FAILED" "$RESULTS_FILE" 2>/dev/null; then - echo "red" - else - # No tests found for this engine - echo "lightgrey" - fi -} - -# Extract versions from conftest files for badge URL -extract_versions_badge() { - local file=$1 - local var_name=$2 - grep -A 10 "${var_name}.*=" "$file" 2>/dev/null | \ - grep -E 'mariadb:[^"]+|mysql:[^"]+|postgres:[^"]+' | \ - grep -oP '"[^"]+"' | \ - tr -d '"' | \ - sed 's/.*://' | \ - grep -v '^$' | \ - sort -V | \ - paste -sd'|' | \ - sed 's/|/%20%7C%20/g' -} - -# Determine colors from single test run -echo -n " SQLite: " -SQLITE_COLOR=$(get_engine_color "sqlite") -echo "$SQLITE_COLOR" - -echo -n " MySQL: " -MYSQL_COLOR=$(get_engine_color "mysql") -MYSQL_BADGE=$(extract_versions_badge "$TESTS_DIR/mysql/conftest.py" "MYSQL_VERSIONS") -echo "$MYSQL_COLOR" - -echo -n " MariaDB: " -MARIADB_COLOR=$(get_engine_color "mariadb") -MARIADB_BADGE=$(extract_versions_badge "$TESTS_DIR/mariadb/conftest.py" "MARIADB_VERSIONS") -echo "$MARIADB_COLOR" - -echo -n " PostgreSQL: " -POSTGRESQL_COLOR=$(get_engine_color "postgresql") -POSTGRESQL_BADGE=$(extract_versions_badge "$TESTS_DIR/postgresql/conftest.py" "POSTGRESQL_VERSIONS") -echo "$POSTGRESQL_COLOR" - -# Update README badges -if [ -f "$README" ]; then - # Update coverage badge - if [ -n "$COVERAGE" ]; then - sed -i "s/coverage-[0-9]*%/coverage-${COVERAGE}%/g" "$README" - echo " Coverage badge updated: ${COVERAGE}%" - fi - - # Update engine badges - perl -i -pe "s|!\[SQLite\]\(https://img.shields.io/badge/SQLite-[^)]*\)|![SQLite](https://img.shields.io/badge/SQLite-tested-${SQLITE_COLOR})|g" "$README" - perl -i -pe "s|!\[MySQL\]\(https://img.shields.io/badge/MySQL-[^)]*\)|![MySQL](https://img.shields.io/badge/MySQL-${MYSQL_BADGE}-${MYSQL_COLOR})|g" "$README" - perl -i -pe "s|!\[MariaDB\]\(https://img.shields.io/badge/MariaDB-[^)]*\)|![MariaDB](https://img.shields.io/badge/MariaDB-${MARIADB_BADGE}-${MARIADB_COLOR})|g" "$README" - - perl -i -pe "s|!\[PostgreSQL\]\(https://img.shields.io/badge/PostgreSQL-[^)]*\)|![PostgreSQL](https://img.shields.io/badge/PostgreSQL-${POSTGRESQL_BADGE}-${POSTGRESQL_COLOR})|g" "$README" - - # Stage README for commit - git add "$README" - - echo "" - echo "README.md badges updated and staged." -fi - -# Cleanup -rm -f "$RESULTS_FILE" - -echo "" -echo "Done. Pytest exit code: $PYTEST_EXIT_CODE" - -# Exit with original pytest exit code -exit $PYTEST_EXIT_CODE diff --git a/settings.py b/settings.py deleted file mode 100644 index d839825..0000000 --- a/settings.py +++ /dev/null @@ -1,19 +0,0 @@ -import copy -from typing import Any - -import yaml - -from helpers.observables import ObservableObject - - -def load(settings_file): - settings = ObservableObject(yaml.full_load(open(settings_file))) - settings.subscribe(lambda settings: save(settings, settings_file)) - return settings - - -def save(settings: Dict[str, Any], settings_file) -> None: - settings = copy.copy(settings) - - with open(settings_file, "w") as outfile: - yaml.dump(settings, outfile, sort_keys=False) diff --git a/settings.yml b/settings.yml index b79bbdf..9f18920 100755 --- a/settings.yml +++ b/settings.yml @@ -1,3 +1,32 @@ window: - size: 1280,1024 + size: 1920,1048 position: 0,0 +appearance: + theme: petersql + mode: auto +shortcuts: + autocomplete: + force_show: Ctrl+Space + complete: Tab,Enter + cancel: Escape + query_editor: + execute: F5 + execute_selection: Ctrl+Enter +settings: + autocomplete: + debounce_ms: 80 + min_prefix_length: 1 + add_space_after_completion: true + popup_width: 300 + popup_max_height: 10 +language: en_US +query_editor: + statement_separator: ; + trim_whitespace: false + execute_selected_only: false + autocomplete: true + autoformat: true +advanced: + connection_timeout: 10 + query_timeout: 10 + logging_level: INFO diff --git a/structures/configurations.py b/structures/configurations.py index d8cd632..9bcca82 100644 --- a/structures/configurations.py +++ b/structures/configurations.py @@ -6,6 +6,7 @@ class CredentialsConfiguration(NamedTuple): username: str password: Optional[str] port: int + use_tls_enabled: bool = False class SourceConfiguration(NamedTuple): @@ -20,12 +21,17 @@ class SSHTunnelConfiguration(NamedTuple): username: Optional[str] password: Optional[str] local_port: int + remote_host: Optional[str] = None + remote_port: Optional[int] = None + identity_file: Optional[str] = None + extra_args: Optional[list[str]] = None @property def is_enabled(self) -> bool: - return all([ - self.enabled, - self.executable, - self.hostname, - self.local_port - ]) + return all( + [ + self.enabled, + self.executable, + self.hostname, + ] + ) diff --git a/structures/connection.py b/structures/connection.py index c7fe7bb..ab1c10e 100755 --- a/structures/connection.py +++ b/structures/connection.py @@ -2,11 +2,15 @@ import enum from functools import lru_cache -from typing import Union, Optional, Any, NamedTuple +from typing import Any, NamedTuple, Optional, Union from icons import Icon, IconList -from structures.configurations import CredentialsConfiguration, SourceConfiguration, SSHTunnelConfiguration +from structures.configurations import ( + CredentialsConfiguration, + SourceConfiguration, + SSHTunnelConfiguration, +) class Engine(NamedTuple): @@ -20,9 +24,10 @@ class ConnectionEngine(enum.Enum): MARIADB = Engine("MariaDB", "mysql", IconList.MARIADB) MYSQL = Engine("MySQL", "mysql", IconList.MYSQL) POSTGRESQL = Engine("PostgreSQL", "postgres", IconList.POSTGRESQL) + ORACLE = Engine("Oracle", "oracle", IconList.ORACLE) @classmethod - def get_all(cls) -> list["ConnectionEngine"]: + def get_all(cls) -> list[Engine]: return [e.value for e in list(cls)] @classmethod @@ -36,26 +41,49 @@ def from_name(cls, name: str) -> "ConnectionEngine": @dataclasses.dataclass class ConnectionDirectory: + id: int name: str - children: List[Union['ConnectionDirectory', 'Connection']] = dataclasses.field(default_factory=list) + children: list[Union["ConnectionDirectory", "Connection"]] = dataclasses.field( + default_factory=list + ) def to_dict(self): return { - 'type': 'directory', - 'name': self.name, - 'children': [child.to_dict() for child in self.children] + "id": self.id, + "type": "directory", + "name": self.name, + "children": [child.to_dict() for child in self.children], } + @property + def is_new(self) -> bool: + return self.id <= -1 + @dataclasses.dataclass(eq=False) class Connection: """Persistent configuration only. No runtime state.""" + id: int name: str engine: ConnectionEngine configuration: Optional[Union[CredentialsConfiguration, SourceConfiguration]] comments: Optional[str] = "" ssh_tunnel: Optional[SSHTunnelConfiguration] = None + parent: Optional["ConnectionDirectory"] = dataclasses.field( + default=None, + compare=False, + repr=False, + ) + created_at: Optional[str] = None + last_connection_at: Optional[str] = None + last_successful_connection_at: Optional[str] = None + last_failure_reason: Optional[str] = None + successful_connections: int = 0 + unsuccessful_connections: int = 0 + total_connection_attempts: int = 0 + average_connection_time_ms: Optional[int] = None + most_recent_connection_duration_ms: Optional[int] = None def __eq__(self, other: Any): if not isinstance(other, Connection): @@ -75,18 +103,46 @@ def copy(self): def to_dict(self): return { - 'id': self.id, - 'type': 'connection', - 'name': self.name, - 'engine': self.engine.value.name if self.engine else None, - 'configuration': self.configuration._asdict() if self.configuration else None, - 'comments': self.comments, - 'ssh_tunnel': self.ssh_tunnel._asdict() if self.ssh_tunnel else None + "id": self.id, + "type": "connection", + "name": self.name, + "engine": self.engine.value.name if self.engine else None, + "configuration": self.configuration._asdict() + if self.configuration + else None, + "comments": self.comments, + "ssh_tunnel": self.ssh_tunnel._asdict() if self.ssh_tunnel else None, + "created_at": self.created_at, + "last_connection_at": self.last_connection_at, + "last_successful_connection_at": self.last_successful_connection_at, + "last_failure_reason": self.last_failure_reason, + "successful_connections": self.successful_connections, + "unsuccessful_connections": self.unsuccessful_connections, + "total_connection_attempts": self.total_connection_attempts, + "average_connection_time_ms": self.average_connection_time_ms, + "most_recent_connection_duration_ms": self.most_recent_connection_duration_ms, } @property def is_valid(self): - return all([self.name, self.engine]) and all(self.configuration._asdict().values()) + if not self.name or not self.engine or not self.configuration: + return False + + configuration = self.configuration._asdict() + for key, value in configuration.items(): + if isinstance(value, bool): + continue + + if key == "password": + continue + + if value is None: + return False + + if isinstance(value, str) and value == "": + return False + + return True @property def is_new(self): diff --git a/structures/engines/__init__.py b/structures/engines/__init__.py index 054366c..e69de29 100644 --- a/structures/engines/__init__.py +++ b/structures/engines/__init__.py @@ -1,10 +0,0 @@ -import enum - -from functools import lru_cache -from typing import NamedTuple - -import wx - -from icons import IconList, Icon - - diff --git a/structures/engines/builder.py b/structures/engines/builder.py index 8a8cce3..e14d60f 100644 --- a/structures/engines/builder.py +++ b/structures/engines/builder.py @@ -25,7 +25,7 @@ def __init__(self, column: 'SQLColumn', exclude: Optional[list[str]] = None): @property def name(self): - return self.column.sql_safe_name + return self.column.quoted_name @property def datatype(self): @@ -111,13 +111,13 @@ def type(self): @property def name(self): if self.index.name and self.index.name != "PRIMARY KEY": - return self.index.sql_safe_name + return self.index.quoted_name return "" @property def columns(self): - build_sql_safe_name = self.index.table.database.context.build_sql_safe_name - return ", ".join([build_sql_safe_name(col) for col in self.index.columns]) + quote_identifier = self.index.table.database.context.quote_identifier + return ", ".join([quote_identifier(col) for col in self.index.columns]) def __str__(self) -> str: formatted_parts = [] diff --git a/structures/engines/context.py b/structures/engines/context.py index d3f48bd..a0f965d 100755 --- a/structures/engines/context.py +++ b/structures/engines/context.py @@ -4,6 +4,9 @@ from typing import Any, Optional +import yaml + +from constants import WORKDIR from helpers.logger import logger from helpers.observables import ObservableList, ObservableLazyList @@ -11,7 +14,17 @@ from structures.ssh_tunnel import SSHTunnel from structures.connection import Connection from structures.engines.datatype import StandardDataType, SQLDataType -from structures.engines.database import SQLDatabase, SQLTable, SQLColumn, SQLIndex, SQLForeignKey, SQLRecord, SQLView, SQLTrigger +from structures.engines.database import ( + SQLDatabase, + SQLTable, + SQLColumn, + SQLIndex, + SQLCheck, + SQLForeignKey, + SQLRecord, + SQLView, + SQLTrigger, +) from structures.engines.indextype import SQLIndexType, StandardIndexType QUERY_LOGS: ObservableList[str] = ObservableList() @@ -31,7 +44,8 @@ class AbstractContext(abc.ABC): INDEXTYPE: StandardIndexType COLLATIONS: dict[str, str] = {} - IDENTIFIER_QUOTE: str = '"' + IDENTIFIER_QUOTE_CHAR: str = '"' + DEFAULT_STATEMENT_SEPARATOR: str = ";" databases: ObservableLazyList[SQLDatabase] @@ -43,11 +57,225 @@ def __init__(self, connection: Connection): def __del__(self): self.disconnect() - def _on_connect(self, *args, **kwargs): - logger.debug("connected") + def before_connect(self, *args, **kwargs): + # SSH tunnel support via connection configuration + if hasattr(self.connection, "ssh_tunnel") and self.connection.ssh_tunnel: + ssh_config = self.connection.ssh_tunnel + if not ssh_config.is_enabled: + return + + base_host = getattr(self, "_base_host", getattr(self, "host", "127.0.0.1")) + base_port = getattr(self, "_base_port", getattr(self, "port", 0)) + self._base_host = base_host + self._base_port = base_port + + remote_host = getattr(ssh_config, "remote_host", None) or getattr( + self, "_base_host", "127.0.0.1" + ) + remote_port = int( + getattr(ssh_config, "remote_port", 0) or getattr(self, "_base_port", 0) + ) + local_port = int(getattr(ssh_config, "local_port", 0) or 0) + logger.debug( + "Preparing DB SSH tunnel: connection=%s engine=%s base=%s:%s remote=%s:%s requested_local_port=%s", + getattr(self.connection, "name", None), + getattr(self.connection, "engine", None), + self._base_host, + self._base_port, + remote_host, + remote_port, + local_port, + ) + self._ssh_tunnel = SSHTunnel( + ssh_config.hostname, + int(ssh_config.port), + ssh_username=ssh_config.username, + ssh_password=ssh_config.password, + remote_host=remote_host, + remote_port=remote_port, + local_bind_address=("127.0.0.1", local_port), + ssh_executable=getattr(ssh_config, "executable", "ssh"), + identity_file=getattr(ssh_config, "identity_file", None), + extra_args=ssh_config.extra_args, + ) + self._ssh_tunnel.start() + + self.host = "127.0.0.1" + self.port = int(self._ssh_tunnel.local_port) + logger.debug( + "DB connection will use tunnel endpoint: %s:%s", + self.host, + self.port, + ) + + def after_connect(self, *args, **kwargs): + pass + + def before_disconnect(self, *args, **kwargs): + if self._ssh_tunnel is not None: + logger.debug( + "Stopping DB SSH tunnel for connection=%s", + getattr(self.connection, "name", None), + ) + self._ssh_tunnel.stop() + self._ssh_tunnel = None + + if hasattr(self, "_base_host"): + self.host = self._base_host + + if hasattr(self, "_base_port"): + self.port = self._base_port + + def after_disconnect(self): + pass + + @staticmethod + def _extract_spec_names(values: Any) -> list[str]: + if not isinstance(values, list): + return [] + + names: list[str] = [] + for value in values: + if isinstance(value, str): + names.append(value) + continue + + if isinstance(value, dict): + if name := value.get("name"): + names.append(str(name)) + + return names + + @staticmethod + def _load_yaml_file(path: str) -> dict[str, Any]: + file_path = WORKDIR / path + if not file_path.exists(): + return {} + + with open(file_path, encoding="utf-8") as file_handle: + data = yaml.safe_load(file_handle) + + if not isinstance(data, dict): + return {} + + return data + + @staticmethod + def _merge_spec_values( + base_values: list[str], add_values: list[str], remove_values: list[str] + ) -> list[str]: + removed = {value.upper() for value in remove_values} + merged = [value for value in base_values if value.upper() not in removed] + + existing = {value.upper() for value in merged} + for value in add_values: + if value.upper() in existing: + continue + merged.append(value) + existing.add(value.upper()) + + return merged + + @staticmethod + def _extract_major(version: Optional[str]) -> str: + if not version: + return "" - def _on_disconnect(self, *args, **kwargs): - logger.debug("disconnected") + if match := re.search(r"(\d+)", version): + return match.group(1) + + return "" + + @staticmethod + def _select_version_spec( + versions_map: dict[str, Any], major_version: str + ) -> dict[str, Any]: + if not versions_map: + return {} + + if major_version in versions_map and isinstance( + versions_map[major_version], dict + ): + return versions_map[major_version] + + if not major_version.isdigit(): + return {} + + target_major = int(major_version) + available_majors = [ + int(version) + for version, value in versions_map.items() + if version.isdigit() and isinstance(value, dict) + ] + if not available_majors: + return {} + + eligible_majors = [major for major in available_majors if major <= target_major] + if not eligible_majors: + return {} + + selected_major = str(max(eligible_majors)) + selected_spec = versions_map.get(selected_major, {}) + if not isinstance(selected_spec, dict): + return {} + + return selected_spec + + def get_engine_vocabulary( + self, engine: str, server_version: Optional[str] + ) -> tuple[tuple[str, ...], tuple[str, ...]]: + global_spec = self._load_yaml_file("structures/engines/specification.yaml") + engine_spec = self._load_yaml_file( + f"structures/engines/{engine}/specification.yaml" + ) + + global_common = ( + global_spec.get("common", {}) + if isinstance(global_spec.get("common", {}), dict) + else {} + ) + engine_common = ( + engine_spec.get("common", {}) + if isinstance(engine_spec.get("common", {}), dict) + else {} + ) + + keywords = self._extract_spec_names(global_common.get("keywords", [])) + functions = self._extract_spec_names(global_common.get("functions", [])) + + keywords = self._merge_spec_values( + keywords, + self._extract_spec_names(engine_common.get("keywords", [])), + [], + ) + functions = self._merge_spec_values( + functions, + self._extract_spec_names(engine_common.get("functions", [])), + [], + ) + + major_version = self._extract_major(server_version) + versions_map = ( + engine_spec.get("versions", {}) + if isinstance(engine_spec.get("versions", {}), dict) + else {} + ) + version_spec = self._select_version_spec(versions_map, major_version) + + keywords = self._merge_spec_values( + keywords, + [], + self._extract_spec_names(version_spec.get("keywords_remove", [])), + ) + functions = self._merge_spec_values( + functions, + [], + self._extract_spec_names(version_spec.get("functions_remove", [])), + ) + + return tuple(sorted({value.upper() for value in keywords})), tuple( + sorted({value.upper() for value in functions}) + ) @property def is_connected(self): @@ -68,6 +296,10 @@ def connect(self, **connect_kwargs) -> None: """Establish connection to the database using native driver""" raise NotImplementedError + @abc.abstractmethod + def set_database(self, database: SQLDatabase) -> None: + raise NotImplementedError + @abc.abstractmethod def get_server_version(self) -> str: raise NotImplementedError @@ -105,45 +337,112 @@ def get_foreign_keys(self, table: SQLTable) -> list[SQLForeignKey]: raise NotImplementedError @abc.abstractmethod - def build_empty_table(self, database: SQLDatabase, /, name: Optional[str] = None, **default_values) -> SQLTable: + def build_empty_table( + self, database: SQLDatabase, /, name: Optional[str] = None, **default_values + ) -> SQLTable: + raise NotImplementedError + + @abc.abstractmethod + def build_empty_column( + self, + table: SQLTable, + datatype: SQLDataType, + /, + name: Optional[str] = None, + **default_values, + ) -> SQLColumn: + raise NotImplementedError + + @abc.abstractmethod + def build_empty_index( + self, + table: SQLTable, + indextype: SQLIndexType, + columns: list[str], + /, + name: Optional[str] = None, + **default_values, + ) -> SQLIndex: raise NotImplementedError @abc.abstractmethod - def build_empty_column(self, table: SQLTable, datatype: SQLDataType, /, name: Optional[str] = None, **default_values) -> SQLColumn: + def build_empty_check( + self, + table: SQLTable, + /, + name: Optional[str] = None, + expression: Optional[str] = None, + **default_values, + ) -> SQLCheck: raise NotImplementedError @abc.abstractmethod - def build_empty_index(self, table: SQLTable, indextype: SQLIndexType, columns: list[str], /, name: Optional[str] = None, **default_values) -> SQLIndex: + def build_empty_foreign_key( + self, + table: SQLTable, + columns: list[str], + /, + name: Optional[str] = None, + **default_values, + ) -> SQLForeignKey: raise NotImplementedError @abc.abstractmethod - def build_empty_foreign_key(self, table: SQLTable, columns: list[str], /, name: Optional[str] = None, **default_values) -> SQLForeignKey: + def build_empty_record( + self, table: SQLTable, /, *, values: dict[str, Any] + ) -> SQLRecord: raise NotImplementedError @abc.abstractmethod - def build_empty_record(self, table: SQLTable, /, *, values: dict[str, Any]) -> SQLRecord: + def build_empty_view( + self, database: SQLDatabase, /, name: Optional[str] = None, **default_values + ) -> SQLView: raise NotImplementedError @abc.abstractmethod - def build_empty_view(self, database: SQLDatabase, /, name: Optional[str] = None, **default_values) -> SQLView: + def build_empty_function( + self, database: SQLDatabase, /, name: Optional[str] = None, **default_values + ) -> "SQLFunction": raise NotImplementedError @abc.abstractmethod - def build_empty_trigger(self, database: SQLDatabase, /, name: Optional[str] = None, **default_values) -> SQLTrigger: + def build_empty_procedure( + self, database: SQLDatabase, /, name: Optional[str] = None, **default_values + ) -> "SQLProcedure": raise NotImplementedError - def build_sql_safe_name(self, name: Optional[str]) -> str: - value = (name or "").strip() + @abc.abstractmethod + def build_empty_trigger( + self, database: SQLDatabase, /, name: Optional[str] = None, **default_values + ) -> SQLTrigger: + raise NotImplementedError + + def quote_identifier(self, name: str) -> str: + value = name.strip() if not value: - return value + assert False, "Invalid identifier name: %s" % name if SQL_SAFE_NAME_REGEX.match(value): return value - escaped_name = value.replace(self.IDENTIFIER_QUOTE, self.IDENTIFIER_QUOTE * 2) - return f"{self.IDENTIFIER_QUOTE}{escaped_name}{self.IDENTIFIER_QUOTE}" - - def get_records(self, table: SQLTable, /, *, filters: Optional[str] = None, limit: int = 1000, offset: int = 0, orders: Optional[str] = None) -> list[dict[str, Any]]: + escaped_name = value.replace( + self.IDENTIFIER_QUOTE_CHAR, self.IDENTIFIER_QUOTE_CHAR * 2 + ) + return f"{self.IDENTIFIER_QUOTE_CHAR}{escaped_name}{self.IDENTIFIER_QUOTE_CHAR}" + + def qualify(self, *parts): + return ".".join(self.quote_identifier(part) for part in parts) + + def get_records( + self, + table: SQLTable, + /, + *, + filters: Optional[str] = None, + limit: int = 1000, + offset: int = 0, + orders: Optional[str] = None, + ) -> list[dict[str, Any]]: logger.debug(f"get records for table={table.name}") QUERY_LOGS.append(f"/* get_records for table={table.name} */") if table is None or table.is_new: @@ -157,20 +456,21 @@ def get_records(self, table: SQLTable, /, *, filters: Optional[str] = None, limi if orders: order = f"ORDER BY {orders}" - database_identifier = table.database.sql_safe_name if table.database else "" - table_identifier = table.sql_safe_name + database_identifier = table.database.quoted_name if table.database else "" + table_identifier = table.quoted_name if database_identifier: from_clause = f"{database_identifier}.{table_identifier}" else: from_clause = table_identifier - query = [f"SELECT *", - f"FROM {from_clause}", - f"{where}", - f"{order}", - f"LIMIT {limit} OFFSET {offset}", - ] + query = [ + f"SELECT *", + f"FROM {from_clause}", + f"{where}", + f"{order}", + f"LIMIT {limit} OFFSET {offset}", + ] self.execute(" ".join(query)) @@ -178,7 +478,7 @@ def get_records(self, table: SQLTable, /, *, filters: Optional[str] = None, limi # EXECUTION def execute(self, query: str) -> bool: - query_clean = re.sub(r'\s+', ' ', str(query)).strip() + query_clean = re.sub(r"\s+", " ", str(query)).strip() logger.debug("execute query: %s", query_clean) QUERY_LOGS.append(query_clean) @@ -186,7 +486,6 @@ def execute(self, query: str) -> bool: self.cursor.execute(query) except Exception as ex: logger.error(query) - logger.error(ex, exc_info=True) QUERY_LOGS.append(f"/* {str(ex)} */") raise @@ -207,6 +506,8 @@ def fetchall(self) -> list[Any]: raise def disconnect(self) -> None: + self.before_disconnect() + if self._cursor is not None: self._cursor.close() self._cursor = None @@ -215,10 +516,6 @@ def disconnect(self) -> None: self._connection.close() self._connection = None - if self._ssh_tunnel is not None: - self._ssh_tunnel.stop() - self._ssh_tunnel = None - @contextlib.contextmanager def transaction(self): try: diff --git a/structures/engines/database.py b/structures/engines/database.py index 0fa9ed2..58dc527 100755 --- a/structures/engines/database.py +++ b/structures/engines/database.py @@ -66,8 +66,8 @@ def __eq__(self, other: Self) -> bool: return True @property - def sql_safe_name(self): - return self.context.build_sql_safe_name(self.name) + def quoted_name(self): + return self.context.quote_identifier(self.name) def refresh(self): original_database = next((d for d in self.context.databases.get_value() if d.id == self.id), None) @@ -152,8 +152,12 @@ def drop(self): raise NotImplementedError @property - def sql_safe_name(self): - return self.database.context.build_sql_safe_name(self.name) + def quoted_name(self): + return self.database.context.quote_identifier(self.name) + + @property + def fully_qualified_name(self): + return self.database.context.qualify(self.database.name, self.name) @property def is_valid(self) -> bool: @@ -247,14 +251,30 @@ class SQLCheck(abc.ABC): expression: str @property - def sql_safe_name(self): - return self.table.database.context.build_sql_safe_name(self.name) + def quoted_name(self): + return self.table.database.context.quote_identifier(self.name) + + @property + def fully_qualified_name(self): + return self.table.database.context.qualify(self.table.database.name, self.table.name, self.name) def copy(self): cls = self.__class__ field_values = {f.name: getattr(self, f.name) for f in dataclasses.fields(cls)} return cls(**field_values) + @abc.abstractmethod + def create(self) -> bool: + raise NotImplementedError + + @abc.abstractmethod + def drop(self) -> bool: + raise NotImplementedError + + @abc.abstractmethod + def alter(self) -> bool: + raise NotImplementedError + @dataclasses.dataclass(eq=False) class SQLColumn(abc.ABC): @@ -297,8 +317,12 @@ def __str__(self) -> str: return f"{self.__class__.__name__}(id={self.id}, name={self.name}, datatype={self.datatype}, is_nullable={self.is_nullable})" @property - def sql_safe_name(self): - return self.table.database.context.build_sql_safe_name(self.name) + def quoted_name(self): + return self.table.database.context.quote_identifier(self.name) + + @property + def fully_qualified_name(self): + return self.table.database.context.qualify(self.table.database.name, self.table.name, self.name) @property def is_primary_key(self): @@ -436,8 +460,12 @@ def is_valid(self): return all([self.name, self.type, len(self.columns)]) @property - def sql_safe_name(self): - return self.table.database.context.build_sql_safe_name(self.name) + def quoted_name(self): + return self.table.database.context.quote_identifier(self.name) + + @property + def fully_qualified_name(self): + return self.table.database.context.qualify(self.table.database.name, self.table.name, self.name) def copy(self): cls = self.__class__ @@ -483,12 +511,16 @@ def is_valid(self): return all([self.name, len(self.columns), self.reference_table, len(self.reference_columns)]) @property - def sql_safe_name(self): - return self.table.database.context.build_sql_safe_name(self.name) + def quoted_name(self): + return self.table.database.context.quote_identifier(self.name) @property - def reference_table_sql_safe_name(self) -> str: - return self.table.database.context.build_sql_safe_name(self.reference_table) + def fully_qualified_name(self): + return self.table.database.context.qualify(self.table.database.name, self.table.name, self.name) + + @property + def reference_table_quoted_name(self) -> str: + return self.table.database.context.quote_identifier(self.reference_table) def copy(self): cls = self.__class__ @@ -511,6 +543,7 @@ def __eq__(self, other: object) -> bool: def __str__(self) -> str: return f"{self.__class__.__name__}(id={self.id}, table={self.table.name}, values={self.values})" + @property def is_new(self) -> bool: return self.id <= -1 @@ -550,10 +583,10 @@ def _get_identifier_columns(self) -> dict[str, str]: for column in columns: if original_record is not None: - identifier_conditions[column.name] = original_record.values.get(column.sql_safe_name) + identifier_conditions[column.name] = original_record.values.get(column.quoted_name) if column.datatype.format is not None: - identifier_conditions[column.name] = column.datatype.format(identifier_conditions[column.sql_safe_name]) + identifier_conditions[column.name] = column.datatype.format(identifier_conditions[column.quoted_name]) if identifier_index.type.is_primary: break @@ -577,7 +610,7 @@ def delete_many(table: SQLTable, records: list[Self]) -> bool: results = [] with table.database.context.transaction() as transaction: for record in records: - if record.is_new(): + if record.is_new: continue if raw_delete_record := record.raw_delete_record(): @@ -590,7 +623,7 @@ def save(self) -> Optional[bool]: if not self.is_valid(): raise ValueError("Record is not yet valid") - if self.is_new(): + if self.is_new: method = self.insert else: method = self.update @@ -603,7 +636,19 @@ class SQLView(abc.ABC): id: int name: str database: SQLDatabase = dataclasses.field(compare=False) - sql: str + statement: str + + @property + def is_new(self) -> bool: + return self.id <= -1 + + @property + def quoted_name(self) -> str: + return self.database.context.quote_identifier(self.name) + + @property + def fully_qualified_name(self): + return self.database.context.qualify(self.database.name, self.name) def copy(self): field_values = {field.name: getattr(self, field.name) for field in dataclasses.fields(self)} @@ -611,16 +656,25 @@ def copy(self): return new_view + def save(self): + if self.is_new: + result = self.create() + else: + result = self.alter() + + self.database.refresh() + return result + @abc.abstractmethod - def create(self): + def create(self) -> bool: raise NotImplementedError @abc.abstractmethod - def drop(self): + def drop(self) -> bool: raise NotImplementedError @abc.abstractmethod - def alter(self): + def alter(self) -> bool: raise NotImplementedError @@ -629,16 +683,49 @@ class SQLTrigger(abc.ABC): id: int name: str database: SQLDatabase = dataclasses.field(compare=False) - sql: str + statement: str timing: Literal['BEFORE', 'AFTER'] = 'BEFORE' event: Literal['INSERT', 'UPDATE', 'DELETE'] = 'INSERT' + @property + def is_new(self) -> bool: + return self.id <= -1 + + @property + def quoted_name(self) -> str: + return self.database.context.quote_identifier(self.name) + + @property + def fully_qualified_name(self): + return self.database.context.qualify(self.database.name, self.name) + def copy(self): field_values = {field.name: getattr(self, field.name) for field in dataclasses.fields(self)} new_view = self.__class__(**field_values) return new_view + def save(self): + if self.is_new: + result = self.create() + else: + result = self.alter() + + self.database.refresh() + return result + + @abc.abstractmethod + def create(self) -> bool: + raise NotImplementedError + + @abc.abstractmethod + def drop(self) -> bool: + raise NotImplementedError + + @abc.abstractmethod + def alter(self) -> bool: + raise NotImplementedError + @dataclasses.dataclass(eq=False) class SQLProcedure(abc.ABC): @@ -646,12 +733,45 @@ class SQLProcedure(abc.ABC): name: str database: SQLDatabase = dataclasses.field(compare=False) + @property + def is_new(self) -> bool: + return self.id <= -1 + + @property + def quoted_name(self) -> str: + return self.database.context.quote_identifier(self.name) + + @property + def fully_qualified_name(self): + return self.database.context.qualify(self.database.name, self.name) + def copy(self): field_values = {field.name: getattr(self, field.name) for field in dataclasses.fields(self)} new_view = self.__class__(**field_values) return new_view + def save(self): + if self.is_new: + result = self.create() + else: + result = self.alter() + + self.database.refresh() + return result + + @abc.abstractmethod + def create(self) -> bool: + raise NotImplementedError + + @abc.abstractmethod + def drop(self) -> bool: + raise NotImplementedError + + @abc.abstractmethod + def alter(self) -> bool: + raise NotImplementedError + @dataclasses.dataclass(eq=False) class SQLFunction(abc.ABC): @@ -659,12 +779,45 @@ class SQLFunction(abc.ABC): name: str database: SQLDatabase = dataclasses.field(compare=False) + @property + def is_new(self) -> bool: + return self.id <= -1 + + @property + def quoted_name(self) -> str: + return self.database.context.quote_identifier(self.name) + + @property + def fully_qualified_name(self): + return self.database.context.qualify(self.database.name, self.name) + def copy(self): field_values = {field.name: getattr(self, field.name) for field in dataclasses.fields(self)} new_view = self.__class__(**field_values) return new_view + def save(self): + if self.is_new: + result = self.create() + else: + result = self.alter() + + self.database.refresh() + return result + + @abc.abstractmethod + def create(self) -> bool: + raise NotImplementedError + + @abc.abstractmethod + def drop(self) -> bool: + raise NotImplementedError + + @abc.abstractmethod + def alter(self) -> bool: + raise NotImplementedError + @dataclasses.dataclass(eq=False) class SQLEvent(abc.ABC): @@ -672,6 +825,18 @@ class SQLEvent(abc.ABC): name: str database: SQLDatabase = dataclasses.field(compare=False) + @property + def is_new(self) -> bool: + return self.id <= -1 + + @property + def quoted_name(self) -> str: + return self.database.context.quote_identifier(self.name) + + @property + def fully_qualified_name(self): + return self.database.context.qualify(self.database.name, self.name) + def copy(self): field_values = {field.name: getattr(self, field.name) for field in dataclasses.fields(self)} new_view = self.__class__(**field_values) diff --git a/structures/engines/mariadb/context.py b/structures/engines/mariadb/context.py index c149f48..14238b8 100755 --- a/structures/engines/mariadb/context.py +++ b/structures/engines/mariadb/context.py @@ -1,4 +1,5 @@ import re +import ssl from typing import Any, Optional from gettext import gettext as _ @@ -7,13 +8,28 @@ from helpers.logger import logger from structures.connection import Connection -from structures.ssh_tunnel import SSHTunnel from structures.engines.context import QUERY_LOGS, AbstractContext from structures.engines.mariadb import MAP_COLUMN_FIELDS -from structures.engines.database import SQLDatabase, SQLTable, SQLColumn, SQLIndex, SQLForeignKey, SQLTrigger +from structures.engines.database import ( + SQLDatabase, + SQLTable, + SQLColumn, + SQLIndex, + SQLForeignKey, + SQLTrigger, +) from structures.engines.datatype import SQLDataType -from structures.engines.mariadb.database import MariaDBTable, MariaDBColumn, MariaDBIndex, MariaDBForeignKey, MariaDBRecord, MariaDBView, MariaDBTrigger, MariaDBDatabase +from structures.engines.mariadb.database import ( + MariaDBTable, + MariaDBColumn, + MariaDBIndex, + MariaDBForeignKey, + MariaDBRecord, + MariaDBView, + MariaDBTrigger, + MariaDBDatabase, +) from structures.engines.mariadb.datatype import MariaDBDataType from structures.engines.mariadb.indextype import MariaDBIndexType @@ -24,7 +40,8 @@ class MariaDBContext(AbstractContext): DATATYPE = MariaDBDataType INDEXTYPE = MariaDBIndexType - IDENTIFIER_QUOTE = "`" + IDENTIFIER_QUOTE_CHAR = "`" + DEFAULT_STATEMENT_SEPARATOR = ";" def __init__(self, connection: Connection): super().__init__(connection) @@ -32,79 +49,80 @@ def __init__(self, connection: Connection): self.host = connection.configuration.hostname self.user = connection.configuration.username self.password = connection.configuration.password - # self.database = session.configuration.database - self.port = getattr(connection.configuration, 'port', 3306) - self._ssh_tunnel = None + self.port = getattr(connection.configuration, "port", 3306) + + def after_connect(self, *args, **kwargs): + super().after_connect(*args, **kwargs) - def _on_connect(self, *args, **kwargs): - super()._on_connect(*args, **kwargs) self.execute(""" SELECT COLLATION_NAME, CHARACTER_SET_NAME FROM information_schema.COLLATIONS WHERE CHARACTER_SET_NAME IS NOT NULL ORDER BY CHARACTER_SET_NAME, COLLATION_NAME; """) for row in self.fetchall(): - self.COLLATIONS[row['COLLATION_NAME']] = row['CHARACTER_SET_NAME'] + self.COLLATIONS[row["COLLATION_NAME"]] = row["CHARACTER_SET_NAME"] self.execute("""SHOW ENGINES;""") self.ENGINES = [dict(row).get("Engine") for row in self.fetchall()] - try: - self.execute(""" - SELECT WORD FROM information_schema.KEYWORDS - ORDER BY WORD; - """) - self.KEYWORDS = tuple(row["WORD"] for row in self.fetchall()) - except Exception: - self.KEYWORDS = () - - try: - self.execute(""" - SELECT FUNCTION FROM information_schema.SQL_FUNCTIONS - ORDER BY FUNCTION; - """) - builtin_functions = tuple(row["FUNCTION"] for row in self.fetchall()) - except Exception: - builtin_functions = () + server_version = self.get_server_version() + self.KEYWORDS, builtin_functions = self.get_engine_vocabulary( + "mariadb", server_version + ) self.execute(""" SELECT DISTINCT ROUTINE_NAME FROM information_schema.ROUTINES WHERE ROUTINE_TYPE = 'FUNCTION' ORDER BY ROUTINE_NAME; """) - user_functions = tuple(row["ROUTINE_NAME"] for row in self.fetchall()) + user_functions = tuple(row["ROUTINE_NAME"].upper() for row in self.fetchall()) - self.FUNCTIONS = builtin_functions + user_functions + self.FUNCTIONS = tuple(dict.fromkeys(builtin_functions + user_functions)) def _parse_type(self, column_type: str): types = MariaDBDataType.get_all() - type_set = [x.lower() for type in types if type.has_set for x in ([type.name] + type.alias)] - type_length = [x.lower() for type in types if type.has_length for x in ([type.name] + type.alias)] - - if match := re.search(fr"^({'|'.join(type_set)})\((.*)\)$", column_type): - return dict( - name=match.group(1).upper(), - set=[value.strip("'") for value in match.group(2).split(",")] - ) - elif match := re.search(fr"^({'|'.join(type_length)})\((.*)\)$", column_type): + type_set = [ + x.lower() + for type in types + if type.has_set + for x in ([type.name] + type.alias) + ] + type_length = [ + x.lower() + for type in types + if type.has_length + for x in ([type.name] + type.alias) + ] + + if match := re.search(rf"^({'|'.join(type_set)})\((.*)\)$", column_type): return dict( name=match.group(1).upper(), - length=int(match.group(2)) + set=[value.strip("'") for value in match.group(2).split(",")], ) + elif match := re.search(rf"^({'|'.join(type_length)})\((.*)\)$", column_type): + return dict(name=match.group(1).upper(), length=int(match.group(2))) - elif match := re.search(r'(\w+)\s*\((\d+)(?:,\s*(\d+))?\)(\s*unsigned)?(\s*zerofill)?', column_type): + elif match := re.search( + r"(\w+)\s*\((\d+)(?:,\s*(\d+))?\)(\s*unsigned)?(\s*zerofill)?", column_type + ): return dict( name=match.group(1).upper(), precision=int(match.group(2)), scale=int(match.group(3)) if match.group(3) else None, is_unsigned=bool(match.group(4)), - is_zerofill=bool(match.group(5)) + is_zerofill=bool(match.group(5)), ) return dict() def connect(self, **connect_kwargs) -> None: if self._connection is None: + self.before_connect() + + use_tls_enabled = bool( + getattr(self.connection.configuration, "use_tls_enabled", False) + ) + try: base_kwargs = dict( host=self.host, @@ -112,32 +130,75 @@ def connect(self, **connect_kwargs) -> None: password=self.password, cursorclass=pymysql.cursors.DictCursor, port=self.port, - **connect_kwargs + **connect_kwargs, ) - - # SSH tunnel support via connection configuration - if hasattr(self.connection, 'ssh_tunnel') and self.connection.ssh_tunnel: - ssh_config = self.connection.ssh_tunnel - self._ssh_tunnel = SSHTunnel( - ssh_config.hostname, int(ssh_config.port), - ssh_username=ssh_config.username, - ssh_password=ssh_config.password, - remote_port=self.port, - local_bind_address=(self.host, int(getattr(ssh_config, 'local_port', 0))) - ) - self._ssh_tunnel.start() - base_kwargs.update( - host=self.host, - port=self._ssh_tunnel.local_port, - ) + if use_tls_enabled: + base_kwargs["ssl"] = { + "cert_reqs": ssl.CERT_NONE, + "check_hostname": False, + } + logger.debug( + "MariaDB connect target host=%s port=%s user=%s use_tls_enabled=%s", + base_kwargs.get("host"), + base_kwargs.get("port"), + base_kwargs.get("user"), + use_tls_enabled, + ) + # + # # SSH tunnel support via connection configuration + # if hasattr(self.connection, 'ssh_tunnel') and self.connection.ssh_tunnel: + # ssh_config = self.connection.ssh_tunnel + # self._ssh_tunnel = SSHTunnel( + # ssh_config.hostname, int(ssh_config.port), + # ssh_username=ssh_config.username, + # ssh_password=ssh_config.password, + # remote_port=self.port, + # local_bind_address=(self.host, int(getattr(ssh_config, 'local_port', 0))), + # extra_args=ssh_config.extra_args + # ) + # self._ssh_tunnel.start() + # base_kwargs.update( + # host=self.host, + # port=self._ssh_tunnel.local_port, + # ) self._connection = pymysql.connect(**base_kwargs) self._cursor = self._connection.cursor() + except pymysql.err.OperationalError as e: + should_retry_tls = bool(e.args and e.args[0] == 1045) + if not should_retry_tls or "ssl" in base_kwargs: + logger.error(f"Failed to connect to MariaDB: {e}", exc_info=True) + raise + + logger.warning( + "MariaDB connection failed without TLS (%s). Retrying with TLS.", + e, + ) + logger.debug( + "Retrying MariaDB connection with TLS preferred after auth failure" + ) + tls_kwargs = { + **base_kwargs, + "ssl": { + "cert_reqs": ssl.CERT_NONE, + "check_hostname": False, + }, + } + self._connection = pymysql.connect(**tls_kwargs) + self._cursor = self._connection.cursor() + + if hasattr(self.connection, "configuration"): + self.connection.configuration = ( + self.connection.configuration._replace(use_tls_enabled=True) + ) + logger.info( + "MariaDB connection succeeded after enabling TLS automatically." + ) except Exception as e: logger.error(f"Failed to connect to MariaDB: {e}", exc_info=True) raise else: - self._on_connect() + self.after_connect() def disconnect(self) -> None: """Disconnect from database and stop SSH tunnel if active.""" @@ -163,6 +224,9 @@ def disconnect(self) -> None: self._cursor = None self._connection = None + def set_database(self, database: SQLDatabase) -> None: + self.execute(f"USE {database.quoted_name}") + def get_server_version(self) -> str: self.execute("SELECT VERSION() as version") version = self.cursor.fetchone() @@ -171,7 +235,7 @@ def get_server_version(self) -> str: def get_server_uptime(self) -> Optional[int]: self.execute("SHOW STATUS LIKE 'Uptime'") result = self.fetchone() - return int(result['Value']) if result else None + return int(result["Value"]) if result else None def get_databases(self) -> list[SQLDatabase]: self.execute(""" @@ -186,41 +250,59 @@ def get_databases(self) -> list[SQLDatabase]: """) results = [] for i, row in enumerate(self.fetchall()): - results.append(MariaDBDatabase( - id=i, - name=row["database_name"], - default_collation=row["default_collation"], - total_bytes=float(row["total_bytes"]), - context=self, - get_tables_handler=self.get_tables, - get_views_handler=self.get_views, - get_triggers_handler=self.get_triggers, - )) + results.append( + MariaDBDatabase( + id=i, + name=row["database_name"], + default_collation=row["default_collation"], + total_bytes=float(row["total_bytes"]), + context=self, + get_tables_handler=self.get_tables, + get_views_handler=self.get_views, + get_triggers_handler=self.get_triggers, + ) + ) return results def get_views(self, database: SQLDatabase): results: list[MariaDBView] = [] - self.execute(f"SELECT TABLE_NAME, VIEW_DEFINITION FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_SCHEMA = '{database.name}' ORDER BY TABLE_NAME") + self.execute( + f"SELECT TABLE_NAME, VIEW_DEFINITION FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_SCHEMA = '{database.name}' ORDER BY TABLE_NAME" + ) for i, result in enumerate(self.fetchall()): - results.append(MariaDBView( - id=i, - name=result['TABLE_NAME'], - database=database, - sql=result['VIEW_DEFINITION'] - )) + results.append( + MariaDBView( + id=i, + name=result["TABLE_NAME"], + database=database, + statement=result["VIEW_DEFINITION"], + ) + ) return results + def get_definers(self) -> list[str]: + self.execute(""" + SELECT DISTINCT CONCAT(User, '@', Host) as definer + FROM mysql.user + ORDER BY definer + """) + return [row["definer"] for row in self.fetchall()] + def get_triggers(self, database: SQLDatabase) -> list[MariaDBTrigger]: results: list[MariaDBTrigger] = [] - self.execute(f"SELECT TRIGGER_NAME, ACTION_STATEMENT FROM INFORMATION_SCHEMA.TRIGGERS WHERE TRIGGER_SCHEMA = '{database.name}' ORDER BY TRIGGER_NAME") + self.execute( + f"SELECT TRIGGER_NAME, ACTION_STATEMENT FROM INFORMATION_SCHEMA.TRIGGERS WHERE TRIGGER_SCHEMA = '{database.name}' ORDER BY TRIGGER_NAME" + ) for i, result in enumerate(self.fetchall()): - results.append(MariaDBTrigger( - id=i, - name=result['TRIGGER_NAME'], - database=database, - sql=result['ACTION_STATEMENT'] - )) + results.append( + MariaDBTrigger( + id=i, + name=result["TRIGGER_NAME"], + database=database, + statement=result["ACTION_STATEMENT"], + ) + ) return results @@ -241,17 +323,18 @@ def get_tables(self, database: SQLDatabase) -> list[SQLTable]: results.append( MariaDBTable( id=i, - name=row['TABLE_NAME'], + name=row["TABLE_NAME"], database=database, - engine=row['ENGINE'], - collation_name=row['TABLE_COLLATION'], - auto_increment=int(row['AUTO_INCREMENT'] or 0), - total_bytes=row['total_bytes'], + engine=row["ENGINE"], + collation_name=row["TABLE_COLLATION"], + auto_increment=int(row["AUTO_INCREMENT"] or 0), + total_bytes=row["total_bytes"], total_rows=row["TABLE_ROWS"], - created_at=row['CREATE_TIME'], - updated_at=row['UPDATE_TIME'], + created_at=row["CREATE_TIME"], + updated_at=row["UPDATE_TIME"], get_columns_handler=self.get_columns, get_indexes_handler=self.get_indexes, + get_checks_handler=self.get_checks, get_foreign_keys_handler=self.get_foreign_keys, get_records_handler=self.get_records, ) @@ -275,26 +358,26 @@ def get_columns(self, table: SQLTable) -> list[SQLColumn]: """) for i, row in enumerate(self.cursor.fetchall()): - is_auto_increment = 'auto_increment' in (row['EXTRA'] or '').lower() - is_nullable = row['IS_NULLABLE'] == 'YES' - parse_type = self._parse_type(row['COLUMN_TYPE']) - datatype = MariaDBDataType.get_by_name(row['DATA_TYPE']) + is_auto_increment = "auto_increment" in (row["EXTRA"] or "").lower() + is_nullable = row["IS_NULLABLE"] == "YES" + parse_type = self._parse_type(row["COLUMN_TYPE"]) + datatype = MariaDBDataType.get_by_name(row["DATA_TYPE"]) results.append( MariaDBColumn( id=i, - name=row['COLUMN_NAME'], + name=row["COLUMN_NAME"], datatype=datatype, is_nullable=is_nullable, table=table, - server_default=row['COLUMN_DEFAULT'], + server_default=row["COLUMN_DEFAULT"], is_auto_increment=is_auto_increment, - length=parse_type.get('length'), - numeric_precision=parse_type.get('precision'), - numeric_scale=parse_type.get('scale'), - set=parse_type.get('set'), - is_unsigned=parse_type.get('is_unsigned', False), - is_zerofill=parse_type.get('is_zerofill', False), + length=parse_type.get("length"), + numeric_precision=parse_type.get("precision"), + numeric_scale=parse_type.get("scale"), + set=parse_type.get("set"), + is_unsigned=parse_type.get("is_unsigned", False), + is_zerofill=parse_type.get("is_zerofill", False), ) ) @@ -317,7 +400,7 @@ def get_indexes(self, table: SQLTable) -> list[SQLIndex]: WHERE TABLE_SCHEMA = '{table.database.name}' AND TABLE_NAME = '{table.name}' AND CONSTRAINT_NAME = 'PRIMARY' ORDER BY ORDINAL_POSITION """) - pk_columns = [row['COLUMN_NAME'] for row in self.cursor.fetchall()] + pk_columns = [row["COLUMN_NAME"] for row in self.cursor.fetchall()] if pk_columns: results.append( MariaDBIndex( @@ -338,20 +421,62 @@ def get_indexes(self, table: SQLTable) -> list[SQLIndex]: """) index_data = {} for row in self.cursor.fetchall(): - idx_name = row['INDEX_NAME'] + idx_name = row["INDEX_NAME"] if idx_name not in index_data: - index_data[idx_name] = {'columns': [], 'unique': not row['NON_UNIQUE']} - index_data[idx_name]['columns'].append(row['COLUMN_NAME']) + index_data[idx_name] = {"columns": [], "unique": not row["NON_UNIQUE"]} + index_data[idx_name]["columns"].append(row["COLUMN_NAME"]) for i, (idx_name, data) in enumerate(index_data.items(), start=1): - idx_type = MariaDBIndexType.UNIQUE if data['unique'] else MariaDBIndexType.INDEX + idx_type = ( + MariaDBIndexType.UNIQUE if data["unique"] else MariaDBIndexType.INDEX + ) results.append( MariaDBIndex( id=i, name=idx_name, type=idx_type, - columns=data['columns'], + columns=data["columns"], + table=table, + ) + ) + + return results + + def get_checks(self, table: MariaDBTable) -> list[MariaDBCheck]: + from structures.engines.mariadb.database import MariaDBCheck + + if table is None or table.is_new: + return [] + + try: + query = f""" + SELECT + cc.CONSTRAINT_NAME, + cc.CHECK_CLAUSE + FROM information_schema.CHECK_CONSTRAINTS cc + JOIN information_schema.TABLE_CONSTRAINTS tc + ON cc.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA + AND cc.CONSTRAINT_NAME = tc.CONSTRAINT_NAME + WHERE tc.TABLE_SCHEMA = '{table.database.name}' + AND tc.TABLE_NAME = '{table.name}' + AND tc.CONSTRAINT_TYPE = 'CHECK' + ORDER BY cc.CONSTRAINT_NAME + """ + + self.execute(query) + rows = self.fetchall() + except Exception: + # Older MariaDB versions don't have CHECK_CONSTRAINTS table + rows = [] + + results = [] + for i, row in enumerate(rows): + results.append( + MariaDBCheck( + id=i, + name=row["CONSTRAINT_NAME"], table=table, + expression=row["CHECK_CLAUSE"], ) ) @@ -382,29 +507,45 @@ def get_foreign_keys(self, table: SQLTable) -> list[SQLForeignKey]: """) foreign_keys = [] for i, row in enumerate(self.cursor.fetchall()): - foreign_keys.append(MariaDBForeignKey( - id=i, - name=row['CONSTRAINT_NAME'], - columns=row["COLUMNS_NAME"].split(","), - table=table, - reference_table=row['REFERENCED_TABLE_NAME'], - reference_columns=row["REFERENCED_COLUMNS"].split(","), - on_update=row['UPDATE_RULE'], - on_delete=row['DELETE_RULE'], - )) + foreign_keys.append( + MariaDBForeignKey( + id=i, + name=row["CONSTRAINT_NAME"], + columns=row["COLUMNS_NAME"].split(","), + table=table, + reference_table=row["REFERENCED_TABLE_NAME"], + reference_columns=row["REFERENCED_COLUMNS"].split(","), + on_update=row["UPDATE_RULE"], + on_delete=row["DELETE_RULE"], + ) + ) return foreign_keys - def get_records(self, table: SQLTable, /, *, filters: Optional[str] = None, limit: int = 1000, offset: int = 0, orders: Optional[str] = None) -> list[MariaDBRecord]: + def get_records( + self, + table: SQLTable, + /, + *, + filters: Optional[str] = None, + limit: int = 1000, + offset: int = 0, + orders: Optional[str] = None, + ) -> list[MariaDBRecord]: results = [] - for i, record in enumerate(super().get_records(table, filters=filters, limit=limit, offset=offset, orders=orders), start=offset): - results.append( - MariaDBRecord(id=i, table=table, values=dict(record)) - ) + for i, record in enumerate( + super().get_records( + table, filters=filters, limit=limit, offset=offset, orders=orders + ), + start=offset, + ): + results.append(MariaDBRecord(id=i, table=table, values=dict(record))) return results - def build_empty_table(self, database: SQLDatabase, /, name: Optional[str] = None, **default_values) -> MariaDBTable: + def build_empty_table( + self, database: SQLDatabase, /, name: Optional[str] = None, **default_values + ) -> MariaDBTable: id = MariaDBContext.get_temporary_id(database.tables) if name is None: @@ -419,26 +560,38 @@ def build_empty_table(self, database: SQLDatabase, /, name: Optional[str] = None database=database, get_indexes_handler=self.get_indexes, get_columns_handler=self.get_columns, + get_checks_handler=self.get_checks, get_foreign_keys_handler=self.get_foreign_keys, get_records_handler=self.get_records, **default_values, ).copy() - def build_empty_column(self, table: SQLTable, datatype: SQLDataType, /, name: Optional[str] = None, **default_values) -> MariaDBColumn: + def build_empty_column( + self, + table: SQLTable, + datatype: SQLDataType, + /, + name: Optional[str] = None, + **default_values, + ) -> MariaDBColumn: id = MariaDBContext.get_temporary_id(table.columns) if name is None: name = _(f"Column{str(id * -1):03}") return MariaDBColumn( - id=id, - name=name, - table=table, - datatype=datatype, - **default_values + id=id, name=name, table=table, datatype=datatype, **default_values ) - def build_empty_index(self, table: MariaDBTable, indextype: MariaDBIndexType, columns: list[str], /, name: Optional[str] = None, **default_values) -> MariaDBIndex: + def build_empty_index( + self, + table: MariaDBTable, + indextype: MariaDBIndexType, + columns: list[str], + /, + name: Optional[str] = None, + **default_values, + ) -> MariaDBIndex: id = MariaDBContext.get_temporary_id(table.indexes) if name is None: @@ -452,31 +605,64 @@ def build_empty_index(self, table: MariaDBTable, indextype: MariaDBIndexType, co table=table, ) - def build_empty_foreign_key(self, table: MariaDBTable, columns: list[str], /, name: Optional[str] = None, **default_values) -> MariaDBForeignKey: + def build_empty_check( + self, + table: MariaDBTable, + /, + name: Optional[str] = None, + expression: Optional[str] = None, + **default_values, + ) -> MariaDBCheck: + from structures.engines.mariadb.database import MariaDBCheck + + id = MariaDBContext.get_temporary_id(table.checks) + + if name is None: + name = f"check_{abs(id)}" + + return MariaDBCheck( + id=id, name=name, table=table, expression=expression or "", **default_values + ) + + def build_empty_foreign_key( + self, + table: MariaDBTable, + columns: list[str], + /, + name: Optional[str] = None, + **default_values, + ) -> MariaDBForeignKey: id = MariaDBContext.get_temporary_id(table.foreign_keys) if name is None: name = _(f"ForeignKey{str(id * -1):03}") + reference_table = default_values.get("reference_table", "") + reference_columns = default_values.get("reference_columns", []) + return MariaDBForeignKey( id=id, name=name, table=table, columns=columns, - reference_table="", - reference_columns=[], - on_update="", - on_delete="" + reference_table=reference_table, + reference_columns=reference_columns, + on_update=default_values.get("on_update", ""), + on_delete=default_values.get("on_delete", ""), ) - def build_empty_record(self, table: MariaDBTable, /, *, values: dict[str, Any]) -> MariaDBRecord: + def build_empty_record( + self, table: MariaDBTable, /, *, values: dict[str, Any] + ) -> MariaDBRecord: return MariaDBRecord( id=MariaDBContext.get_temporary_id(table.records), table=table, - values=values + values=values, ) - def build_empty_view(self, database: SQLDatabase, /, name: Optional[str] = None, **default_values) -> MariaDBView: + def build_empty_view( + self, database: SQLDatabase, /, name: Optional[str] = None, **default_values + ) -> MariaDBView: id = MariaDBContext.get_temporary_id(database.views) if name is None: @@ -486,10 +672,37 @@ def build_empty_view(self, database: SQLDatabase, /, name: Optional[str] = None, id=id, name=name, database=database, - sql=default_values.get("sql", ""), + statement=default_values.get("statement", ""), ) - def build_empty_trigger(self, database: SQLDatabase, /, name: Optional[str] = None, **default_values) -> MariaDBTrigger: + def build_empty_function( + self, database: SQLDatabase, /, name: Optional[str] = None, **default_values + ) -> "MariaDBFunction": + from structures.engines.mariadb.database import MariaDBFunction + + id = MariaDBContext.get_temporary_id(database.functions) + + if name is None: + name = f"function_{id}" + + return MariaDBFunction( + id=id, + name=name, + database=database, + parameters=default_values.get("parameters", ""), + returns=default_values.get("returns", "INT"), + deterministic=default_values.get("deterministic", False), + statement=default_values.get("statement", ""), + ) + + def build_empty_procedure( + self, database: SQLDatabase, /, name: Optional[str] = None, **default_values + ): + raise NotImplementedError("MariaDB Procedure not implemented yet") + + def build_empty_trigger( + self, database: SQLDatabase, /, name: Optional[str] = None, **default_values + ) -> MariaDBTrigger: id = MariaDBContext.get_temporary_id(database.triggers) if name is None: @@ -499,5 +712,5 @@ def build_empty_trigger(self, database: SQLDatabase, /, name: Optional[str] = No id=id, name=name, database=database, - sql=default_values.get("sql", ""), + statement=default_values.get("statement", ""), ) diff --git a/structures/engines/mariadb/database.py b/structures/engines/mariadb/database.py index b125fad..2a0f2de 100644 --- a/structures/engines/mariadb/database.py +++ b/structures/engines/mariadb/database.py @@ -5,7 +5,7 @@ from structures.helpers import merge_original_current from structures.engines.context import QUERY_LOGS -from structures.engines.database import SQLTable, SQLColumn, SQLIndex, SQLForeignKey, SQLRecord, SQLView, SQLTrigger, SQLFunction, SQLDatabase +from structures.engines.database import SQLTable, SQLColumn, SQLIndex, SQLForeignKey, SQLRecord, SQLView, SQLTrigger, SQLFunction, SQLDatabase, SQLCheck from structures.engines.mariadb.indextype import MariaDBIndexType from structures.engines.mariadb.builder import MariaDBColumnBuilder, MariaDBIndexBuilder @@ -26,7 +26,7 @@ def raw_create(self) -> str: columns_and_indexes = columns + indexes return f""" - CREATE TABLE {self.database.sql_safe_name}.{self.sql_safe_name} ( + CREATE TABLE {self.fully_qualified_name} ( {', '.join(columns_and_indexes)} ) COLLATE='{self.collation_name}' @@ -34,8 +34,8 @@ def raw_create(self) -> str: """ def alter_auto_increment(self, auto_increment: int): - sql = f"ALTER TABLE {self.database.sql_safe_name}.{self.sql_safe_name} AUTO_INCREMENT {auto_increment};" - self.database.context.execute(sql) + statement = f"ALTER TABLE {self.fully_qualified_name} AUTO_INCREMENT {auto_increment};" + self.database.context.execute(statement) return True @@ -43,24 +43,24 @@ def alter_collation(self, collation_name, convert: bool = True): charset = "" if convert: charset = f"CONVERT TO CHARACTER SET {self.database.context.COLLATIONS[collation_name]}" - return self.database.context.execute(f"""ALTER TABLE {self.database.sql_safe_name}.{self.sql_safe_name} {charset} COLLATE {collation_name};""") + return self.database.context.execute(f"""ALTER TABLE {self.fully_qualified_name} {charset} COLLATE {collation_name};""") def alter_engine(self, engine: str): - sql = f"ALTER TABLE {self.database.sql_safe_name}.{self.sql_safe_name} ENGINE {engine};" - self.database.context.execute(sql) + statement = f"ALTER TABLE {self.fully_qualified_name} ENGINE {engine};" + self.database.context.execute(statement) return True def rename(self, table: Self, new_name: str) -> bool: - sql = f"ALTER TABLE {self.database.sql_safe_name}.{table.sql_safe_name} RENAME TO `{new_name}`;" - self.database.context.execute(sql) + statement = f"ALTER TABLE {table.fully_qualified_name} RENAME TO `{new_name}`;" + self.database.context.execute(statement) return True def truncate(self): try: with self.database.context.transaction() as context: - context.execute(f"TRUNCATE TABLE {self.database.sql_safe_name}.{self.sql_safe_name};") + context.execute(f"TRUNCATE TABLE {self.fully_qualified_name};") except Exception as ex: logger.error(ex, exc_info=True) @@ -139,7 +139,22 @@ def alter(self) -> bool: return True def drop(self) -> bool: - return self.database.context.execute(f"DROP TABLE {self.database.sql_safe_name}.{self.sql_safe_name}") + return self.database.context.execute(f"DROP TABLE {self.fully_qualified_name}") + + +@dataclasses.dataclass(eq=False) +class MariaDBCheck(SQLCheck): + def create(self) -> bool: + statement = f"ALTER TABLE {self.table.fully_qualified_name} ADD CONSTRAINT {self.quoted_name} CHECK ({self.expression})" + return self.table.database.context.execute(statement) + + def drop(self) -> bool: + statement = f"ALTER TABLE {self.table.fully_qualified_name} DROP CONSTRAINT {self.quoted_name}" + return self.table.database.context.execute(statement) + + def alter(self) -> bool: + self.drop() + return self.create() @dataclasses.dataclass(eq=False) @@ -152,49 +167,49 @@ class MariaDBColumn(SQLColumn): after_index: Optional[int] = None def add(self) -> bool: - sql = f"ALTER TABLE {self.table.database.sql_safe_name}.{self.table.sql_safe_name} ADD COLUMN {MariaDBColumnBuilder(self)}" + statement = f"ALTER TABLE {self.table.fully_qualified_name} ADD COLUMN {MariaDBColumnBuilder(self)}" if self.after_index: if self.after_index == 0: - sql += " FIRST" + statement += " FIRST" else: - sql += f" AFTER {self.table.columns.get_value()[self.after_index].sql_safe_name}" + statement += f" AFTER {self.table.columns.get_value()[self.after_index - 1].quoted_name}" - return self.table.database.context.execute(sql) + return self.table.database.context.execute(statement) def modify(self, current: Self, after: Optional[SQLColumn] = None): - sql = f"ALTER TABLE {self.table.database.sql_safe_name}.{self.table.sql_safe_name} MODIFY COLUMN {MariaDBColumnBuilder(current)}" + statement = f"ALTER TABLE {self.table.fully_qualified_name} MODIFY COLUMN {MariaDBColumnBuilder(current)}" if after is not None: if after.id == -1: - sql += " FIRST" + statement += " FIRST" else: - sql += f" AFTER {after.sql_safe_name}" + statement += f" AFTER {after.quoted_name}" - self.table.database.context.execute(sql) + self.table.database.context.execute(statement) def change(self, current: Self): - sql = f"ALTER TABLE {self.table.database.sql_safe_name}.{self.table.sql_safe_name} CHANGE {self.sql_safe_name} {MariaDBColumnBuilder(current)}" - self.table.database.context.execute(sql) + statement = f"ALTER TABLE {self.table.fully_qualified_name} CHANGE {self.quoted_name} {MariaDBColumnBuilder(current)}" + self.table.database.context.execute(statement) def rename(self, new_name: str) -> bool: - return self.table.database.context.execute(f"ALTER TABLE {self.table.database.sql_safe_name}.{self.table.sql_safe_name} RENAME COLUMN {self.sql_safe_name} TO `{new_name}`") + return self.table.database.context.execute(f"ALTER TABLE {self.table.fully_qualified_name} RENAME COLUMN {self.quoted_name} TO `{new_name}`") def drop(self) -> bool: - return self.table.database.context.execute(f"ALTER TABLE {self.table.database.sql_safe_name}.{self.table.sql_safe_name} DROP COLUMN {self.sql_safe_name}") + return self.table.database.context.execute(f"ALTER TABLE {self.table.fully_qualified_name} DROP COLUMN {self.quoted_name}") @dataclasses.dataclass(eq=False) class MariaDBIndex(SQLIndex): def create(self) -> bool: if self.type == MariaDBIndexType.PRIMARY: - return self.table.database.context.execute(f"""ALTER TABLE {self.table.database.sql_safe_name}.{self.table.sql_safe_name} ADD PRIMARY KEY ({", ".join(self.columns)})""") + return self.table.database.context.execute(f"""ALTER TABLE {self.table.fully_qualified_name} ADD PRIMARY KEY ({", ".join(self.columns)})""") - return self.table.database.context.execute(f"""ALTER TABLE {self.table.database.sql_safe_name}.{self.table.sql_safe_name} ADD {self.type.name} {self.sql_safe_name} ({", ".join(self.columns)})""") + return self.table.database.context.execute(f"""ALTER TABLE {self.table.fully_qualified_name} ADD {self.type.name} {self.quoted_name} ({", ".join(self.columns)})""") def drop(self) -> bool: if self.type == MariaDBIndexType.PRIMARY: - return self.table.database.context.execute(f"ALTER TABLE {self.table.database.sql_safe_name}.{self.table.sql_safe_name} DROP PRIMARY KEY") + return self.table.database.context.execute(f"ALTER TABLE {self.table.fully_qualified_name} DROP PRIMARY KEY") - return self.table.database.context.execute(f"DROP INDEX {self.sql_safe_name} ON {self.table.database.sql_safe_name}.{self.table.sql_safe_name}") + return self.table.database.context.execute(f"DROP INDEX {self.quoted_name} ON {self.table.fully_qualified_name}") def modify(self, new: Self): self.drop() @@ -206,7 +221,7 @@ def modify(self, new: Self): class MariaDBForeignKey(SQLForeignKey): def create(self) -> bool: query = [ - f"ALTER TABLE {self.table.database.sql_safe_name}.{self.table.sql_safe_name} ADD CONSTRAINT {self.sql_safe_name}", + f"ALTER TABLE {self.table.fully_qualified_name} ADD CONSTRAINT {self.quoted_name}", f"FOREIGN KEY({', '.join(self.columns)})", f"REFERENCES `{self.reference_table}`({', '.join(self.reference_columns)})", ] @@ -221,7 +236,7 @@ def create(self) -> bool: def drop(self) -> bool: return self.table.database.context.execute(f""" - ALTER TABLE {self.table.database.sql_safe_name}.{self.table.sql_safe_name} + ALTER TABLE {self.table.fully_qualified_name} DROP FOREIGN KEY `{self.name}` """) @@ -251,14 +266,14 @@ def raw_insert_record(self) -> str: if not columns_values: assert False, "No columns values" - return f"""INSERT INTO {self.table.database.sql_safe_name}.{self.table.sql_safe_name} ({', '.join(columns_values.keys())}) VALUES ({', '.join(columns_values.values())})""" + return f"""INSERT INTO {self.table.fully_qualified_name} ({', '.join(columns_values.keys())}) VALUES ({', '.join(columns_values.values())})""" def raw_update_record(self) -> Optional[str]: identifier_columns = self._get_identifier_columns() identifier_conditions = " AND ".join([f"""`{identifier_name}` = {identifier_value}""" for identifier_name, identifier_value in identifier_columns.items()]) - sql_select = f"SELECT * FROM {self.table.database.sql_safe_name}.{self.table.sql_safe_name} WHERE {identifier_conditions}" + sql_select = f"SELECT * FROM {self.table.fully_qualified_name} WHERE {identifier_conditions}" self.table.database.context.execute(sql_select) if not (existing_record := self.table.database.context.fetchone()): @@ -283,14 +298,14 @@ def raw_update_record(self) -> Optional[str]: set_clause = ", ".join(changed_columns) - return f"UPDATE {self.table.database.sql_safe_name}.{self.table.sql_safe_name} SET {set_clause} WHERE {identifier_conditions}" + return f"UPDATE {self.table.fully_qualified_name} SET {set_clause} WHERE {identifier_conditions}" def raw_delete_record(self) -> str: identifier_columns = self._get_identifier_columns() identifier_conditions = " AND ".join([f"""`{identifier_name}` = {identifier_value}""" for identifier_name, identifier_value in identifier_columns.items()]) - return f"DELETE FROM {self.table.database.sql_safe_name}.{self.table.sql_safe_name} WHERE {identifier_conditions}" + return f"DELETE FROM {self.table.fully_qualified_name} WHERE {identifier_conditions}" # RECORDS def insert(self) -> bool: @@ -317,21 +332,24 @@ def delete(self) -> bool: class MariaDBView(SQLView): def create(self) -> bool: - return self.database.context.execute(f"CREATE VIEW `{self.name}` AS {self.sql}") + self.database.context.set_database(self.database) + return self.database.context.execute(f"CREATE VIEW {self.fully_qualified_name} AS {self.statement}") def drop(self) -> bool: - return self.database.context.execute(f"DROP VIEW IF EXISTS `{self.name}`") + self.database.context.set_database(self.database) + return self.database.context.execute(f"DROP VIEW IF EXISTS {self.fully_qualified_name}") def alter(self) -> bool: - return self.database.context.execute(f"CREATE OR REPLACE VIEW `{self.name}` AS {self.sql}") + self.database.context.set_database(self.database) + return self.database.context.execute(f"CREATE OR REPLACE VIEW {self.fully_qualified_name} AS {self.statement}") class MariaDBTrigger(SQLTrigger): def create(self) -> bool: - return self.database.context.execute(f"CREATE TRIGGER `{self.name}` {self.sql}") + return self.database.context.execute(f"CREATE TRIGGER {self.fully_qualified_name} {self.statement}") def drop(self) -> bool: - return self.database.context.execute(f"DROP TRIGGER IF EXISTS `{self.name}`") + return self.database.context.execute(f"DROP TRIGGER IF EXISTS {self.fully_qualified_name}") def alter(self) -> bool: self.drop() @@ -343,22 +361,22 @@ class MariaDBFunction(SQLFunction): parameters: str = "" returns: str = "" deterministic: bool = False - sql: str = "" + statement: str = "" def create(self) -> bool: deterministic = "DETERMINISTIC" if self.deterministic else "NOT DETERMINISTIC" query = f""" - CREATE FUNCTION `{self.name}`({self.parameters}) + CREATE FUNCTION {self.fully_qualified_name}({self.parameters}) RETURNS {self.returns} {deterministic} BEGIN - {self.sql}; + {self.statement}; END """ return self.database.context.execute(query) def drop(self) -> bool: - return self.database.context.execute(f"DROP FUNCTION IF EXISTS `{self.name}`") + return self.database.context.execute(f"DROP FUNCTION IF EXISTS {self.fully_qualified_name}") def alter(self) -> bool: self.drop() diff --git a/structures/engines/mariadb/specification.yaml b/structures/engines/mariadb/specification.yaml new file mode 100644 index 0000000..879b032 --- /dev/null +++ b/structures/engines/mariadb/specification.yaml @@ -0,0 +1,12733 @@ +schema_version: 1 +engine: mariadb +documentation: + purpose: MariaDB vocabulary and version deltas. + fields: + - schema_version: Specification schema version. + - engine: Engine name. + - common.keywords: Engine common keywords. + - common.functions: Engine common function specs. + - versions..keywords_remove: Keywords to remove for that major version. + - versions..functions_remove: Function names to remove for that major version. + notes: + - Runtime selection uses exact major match, else highest configured major <= server + major. +example: + schema_version: 1 + engine: mariadb + common: + keywords: + - RETURNING + functions: + - name: ABS + summary: Returns absolute value. + versions: + '10': + functions_remove: + - FEATURE_ONLY_FROM_11 +common: + keywords: [] + functions: + - name: ABS + category_id: numeric + category_label: Numeric Functions + tags: + - numeric + aliases: [] + signature: + display: ABS(X) + args: + - name: X + optional: false + type: any + summary: Returns the absolute (non-negative) value of X. + description: Returns the absolute (non-negative) value of X. If X is not a number, + it is converted to a numeric type. + examples: + - sql: SELECT ABS(42); + result: +---------+ | ABS(42) | +---------+ | 42 | +---------+ + - sql: SELECT ABS(-42); + result: +----------+ | ABS(-42) | +----------+ | 42 | +----------+ + - sql: SELECT ABS(DATE '1994-01-01'); + result: +------------------------+ | ABS(DATE '1994-01-01') | +------------------------+ + | 19940101 | +------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/abs/' + - name: ACOS + category_id: numeric + category_label: Numeric Functions + tags: + - numeric + aliases: [] + signature: + display: ACOS(X) + args: + - name: X + optional: false + type: any + summary: Returns the arc cosine of X, that is, the value whose cosine is X. + description: Returns the arc cosine of X, that is, the value whose cosine is X. + Returns NULL if X is not in the range -1 to 1. + examples: + - sql: SELECT ACOS(1); + result: +---------+ | ACOS(1) | +---------+ | 0 | +---------+ + - sql: SELECT ACOS(1.0001); + result: +--------------+ | ACOS(1.0001) | +--------------+ | NULL | + +--------------+ + - sql: SELECT ACOS(0); + result: +-----------------+ | ACOS(0) | +-----------------+ | 1.5707963267949 + | +-----------------+ + - sql: SELECT ACOS(0.234); + result: +------------------+ | ACOS(0.234) | +------------------+ | 1.33460644244679 + | +------------------+ + - sql: 'URL: https://mariadb.com/kb/en/acos/' + - name: ADDDATE + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: ADDDATE(date,INTERVAL expr unit) + args: + - name: date + optional: false + type: any + - name: INTERVAL expr unit + optional: false + type: any + summary: When invoked with the INTERVAL form of the second argument, ADDDATE() + is a + description: When invoked with the INTERVAL form of the second argument, ADDDATE() + is a synonym for DATE_ADD(). The related function SUBDATE() is a synonym for + DATE_SUB(). For information on the INTERVAL unit argument, see the discussion + for DATE_ADD(). When invoked with the days form of the second argument, MariaDB + treats it as an integer number of days to be added to expr. + examples: + - sql: SELECT DATE_ADD('2008-01-02', INTERVAL 31 DAY); + result: +-----------------------------------------+ | DATE_ADD('2008-01-02', + INTERVAL 31 DAY) | +-----------------------------------------+ | 2008-02-02 | + +-----------------------------------------+ + - sql: SELECT ADDDATE('2008-01-02', INTERVAL 31 DAY); + result: +----------------------------------------+ | ADDDATE('2008-01-02', INTERVAL + 31 DAY) | +----------------------------------------+ | 2008-02-02 | + +----------------------------------------+ + - sql: SELECT ADDDATE('2008-01-02', 31); + result: +---------------------------+ | ADDDATE('2008-01-02', 31) | +---------------------------+ + | 2008-02-02 | +---------------------------+ + - sql: "CREATE TABLE t1 (d DATETIME);\nINSERT INTO t1 VALUES\n (\"2007-01-30\ + \ 21:31:07\"),\n (\"1983-10-15 06:42:51\"),\n (\"2011-04-21 12:34:56\"),\n\ + \ (\"2011-10-30 06:31:41\"),\n (\"2011-01-30 14:03:25\"),\n (\"2004-10-07\ + \ 11:19:34\");" + result: SELECT d, ADDDATE(d, 10) from t1; +---------------------+---------------------+ + | d | ADDDATE(d, 10) | +---------------------+---------------------+ + | 2007-01-30 21:31:07 | 2007-02-09 21:31:07 | | 1983-10-15 06:42:51 | 1983-10-25 + 06:42:51 | | 2011-04-21 12:34:56 | 2011-05-01 12:34:56 | | 2011-10-30 06:31:41 + | 2011-11-09 06:31:41 | | 2011-01-30 14:03:25 | 2011-02-09 14:03:25 | + - name: ADDTIME + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: ADDTIME(expr1,expr2) + args: + - name: expr1 + optional: false + type: any + - name: expr2 + optional: false + type: any + summary: ADDTIME() adds expr2 to expr1 and returns the result. + description: ADDTIME() adds expr2 to expr1 and returns the result. expr1 is a + time or datetime expression, and expr2 is a time expression. + examples: + - sql: SELECT ADDTIME('2007-12-31 23:59:59.999999', '1 1:1:1.000002'); + result: +---------------------------------------------------------+ | ADDTIME('2007-12-31 + 23:59:59.999999', '1 1:1:1.000002') | +---------------------------------------------------------+ + | 2008-01-02 01:01:01.000001 | +---------------------------------------------------------+ + - sql: SELECT ADDTIME('01:00:00.999999', '02:00:00.999998'); + result: +-----------------------------------------------+ | ADDTIME('01:00:00.999999', + '02:00:00.999998') | +-----------------------------------------------+ | 03:00:01.999997 | + +-----------------------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/addtime/' + - name: ADD_MONTHS + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: ADD_MONTHS(date, months) + args: + - name: date + optional: false + type: any + - name: months + optional: false + type: any + summary: ADD_MONTHS adds an integer months to a given date (DATE, DATETIME or + description: ADD_MONTHS adds an integer months to a given date (DATE, DATETIME + or TIMESTAMP), returning the resulting date. months can be positive or negative. + If months is not a whole number, then it will be rounded to the nearest whole + number (not truncated). The resulting day component will remain the same as + that specified in date, unless the resulting month has fewer days than the day + component of the given date, in which case the day will be the last day of the + resulting month. Returns NULL if given an invalid date, or a NULL argument. + examples: + - sql: SELECT ADD_MONTHS('2012-01-31', 2); + result: +-----------------------------+ | ADD_MONTHS('2012-01-31', 2) | +-----------------------------+ + | 2012-03-31 | +-----------------------------+ + - sql: SELECT ADD_MONTHS('2012-01-31', -5); + result: +------------------------------+ | ADD_MONTHS('2012-01-31', -5) | +------------------------------+ + | 2011-08-31 | +------------------------------+ + - sql: SELECT ADD_MONTHS('2011-01-31', 1); + result: +-----------------------------+ | ADD_MONTHS('2011-01-31', 1) | +-----------------------------+ + | 2011-02-28 | +-----------------------------+ + - sql: SELECT ADD_MONTHS('2012-01-31', 1); + result: +-----------------------------+ | ADD_MONTHS('2012-01-31', 1) | +-----------------------------+ + | 2012-02-29 | +-----------------------------+ + - sql: SELECT ADD_MONTHS('2012-01-31', 2); + result: +-----------------------------+ | ADD_MONTHS('2012-01-31', 2) | +-----------------------------+ + | 2012-03-31 | +-----------------------------+ + - sql: '...' + - name: AES_DECRYPT + category_id: encryption + category_label: Encryption Functions + tags: + - encryption + aliases: [] + signature: + display: AES_DECRYPT(crypt_str,key_str) + args: + - name: crypt_str + optional: false + type: any + - name: key_str + optional: false + type: any + summary: This function allows decryption of data using the official AES (Advanced + description: 'This function allows decryption of data using the official AES (Advanced + Encryption Standard) algorithm. For more information, see the description of + AES_ENCRYPT(). MariaDB starting with 11.2 -------------------------- From MariaDB + 11.2, the function supports an initialization vector, and control of the block + encryption mode. The default mode is specified by the block_encryption_mode + system variable, which can be changed when calling the function with a mode. + mode is aes-{128,192,256}-{ecb,cbc,ctr} for example: "AES-128-cbc". For modes + that require it, the initialization_vector iv should be 16 bytes (it can be + longer, but the extra bytes are ignored). A shorter iv, where one is required, + results in the function returning NULL. Calling RANDOM_BYTES(16) will generate + a random series of bytes that can be used for the iv.' + examples: + - sql: 'From MariaDB 11.2.0:' + result: SELECT HEX(AES_ENCRYPT('foo', 'bar', '0123456789abcdef', 'aes-128-ctr')) + AS x; +--------+ | x | +--------+ | C57C4B | +--------+ + - sql: SELECT AES_DECRYPT(x'C57C4B', 'bar', '0123456789abcdef', 'aes-128-ctr'); + result: +------------------------------------------------------------------+ + | AES_DECRYPT(x'C57C4B', 'bar', '0123456789abcdef', 'aes-128-ctr') | +------------------------------------------------------------------+ + | foo | +------------------------------------------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/aes_decrypt/' + - name: AES_ENCRYPT + category_id: encryption + category_label: Encryption Functions + tags: + - encryption + aliases: [] + signature: + display: AES_ENCRYPT(str,key_str) + args: + - name: str + optional: false + type: any + - name: key_str + optional: false + type: any + summary: AES_ENCRYPT() and AES_DECRYPT() allow encryption and decryption of data + using + description: 'AES_ENCRYPT() and AES_DECRYPT() allow encryption and decryption + of data using the official AES (Advanced Encryption Standard) algorithm, previously + known as "Rijndael." Encoding with a 128-bit key length is used (from MariaDB + 11.2.0, this is the default, and can be changed). 128 bits is much faster and + is secure enough for most purposes. AES_ENCRYPT() encrypts a string str using + the key key_str, and returns a binary string. AES_DECRYPT() decrypts the encrypted + string and returns the original string. The input arguments may be any length. + If either argument is NULL, the result of this function is also NULL. Because + AES is a block-level algorithm, padding is used to encode uneven length strings + and so the result string length may be calculated using this formula: 16 x (trunc(string_length + / 16) + 1) If AES_DECRYPT() detects invalid data or incorrect padding, it returns + NULL. However, it is possible for AES_DECRYPT() to return a non-NULL value (possibly + garbage) if the input data or the key is invalid. MariaDB starting with 11.2 + -------------------------- From MariaDB 11.2, the function supports an initialization + vector, and control of the block encryption mode. The default mode is specified + by the block_encryption_mode system variable, which can be changed when calling + the function with a mode. mode is aes-{128,192,256}-{ecb,cbc,ctr} for example: + "AES-128-cbc". AES_ENCRYPT(str, key) can no longer be used in persistent virtual + columns (and the like).' + examples: + - sql: INSERT INTO t VALUES (AES_ENCRYPT('text',SHA2('password',512))); + result: 'From MariaDB 11.2.0:' + - sql: SELECT HEX(AES_ENCRYPT('foo', 'bar', '0123456789abcdef', 'aes-256-cbc')) + AS x; + result: +----------------------------------+ | x | + +----------------------------------+ | 42A3EB91E6DFC40A900D278F99E0726E | + +----------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/aes_encrypt/' + - name: ASCII + category_id: string + category_label: String Functions + tags: + - string + aliases: [] + signature: + display: ASCII(str) + args: + - name: str + optional: false + type: any + summary: Returns the numeric ASCII value of the leftmost character of the string + description: Returns the numeric ASCII value of the leftmost character of the + string argument. Returns 0 if the given string is empty and NULL if it is NULL. + ASCII() works for 8-bit characters. + examples: + - sql: SELECT ASCII(9); + result: +----------+ | ASCII(9) | +----------+ | 57 | +----------+ + - sql: SELECT ASCII('9'); + result: +------------+ | ASCII('9') | +------------+ | 57 | +------------+ + - sql: SELECT ASCII('abc'); + result: +--------------+ | ASCII('abc') | +--------------+ | 97 | + +--------------+ + - sql: 'URL: https://mariadb.com/kb/en/ascii/' + - name: ASIN + category_id: numeric + category_label: Numeric Functions + tags: + - numeric + aliases: [] + signature: + display: ASIN(X) + args: + - name: X + optional: false + type: any + summary: Returns the arc sine of X, that is, the value whose sine is X. + description: Returns the arc sine of X, that is, the value whose sine is X. Returns + NULL if X is not in the range -1 to 1. + examples: + - sql: SELECT ASIN(0.2); + result: +--------------------+ | ASIN(0.2) | +--------------------+ + | 0.2013579207903308 | +--------------------+ + - sql: SELECT ASIN('foo'); + result: +-------------+ | ASIN('foo') | +-------------+ | 0 | +-------------+ + - sql: SHOW WARNINGS; + result: '+---------+------+-----------------------------------------+ | Level | + Code | Message | +---------+------+-----------------------------------------+ + | Warning | 1292 | Truncated incorrect DOUBLE value: ''foo'' | +---------+------+-----------------------------------------+' + - sql: 'URL: https://mariadb.com/kb/en/asin/' + - name: ATAN + category_id: numeric + category_label: Numeric Functions + tags: + - numeric + aliases: [] + signature: + display: ATAN(X) + args: + - name: X + optional: false + type: any + summary: Returns the arc tangent of X, that is, the value whose tangent is X. + description: Returns the arc tangent of X, that is, the value whose tangent is + X. + examples: + - sql: SELECT ATAN(2); + result: +--------------------+ | ATAN(2) | +--------------------+ + | 1.1071487177940904 | +--------------------+ + - sql: SELECT ATAN(-2); + result: +---------------------+ | ATAN(-2) | +---------------------+ + | -1.1071487177940904 | +---------------------+ + - sql: 'URL: https://mariadb.com/kb/en/atan/' + - name: ATAN2 + category_id: numeric + category_label: Numeric Functions + tags: + - numeric + aliases: [] + signature: + display: ATAN2(Y,X) + args: + - name: Y + optional: false + type: any + - name: X + optional: false + type: any + summary: Returns the arc tangent of the two variables X and Y. + description: Returns the arc tangent of the two variables X and Y. It is similar + to calculating the arc tangent of Y / X, except that the signs of both arguments + are used to determine the quadrant of the result. + examples: + - sql: SELECT ATAN(-2,2); + result: +---------------------+ | ATAN(-2,2) | +---------------------+ + | -0.7853981633974483 | +---------------------+ + - sql: SELECT ATAN2(PI(),0); + result: +--------------------+ | ATAN2(PI(),0) | +--------------------+ + | 1.5707963267948966 | +--------------------+ + - sql: 'URL: https://mariadb.com/kb/en/atan2/' + - name: AVG + category_id: group_by + category_label: Functions and Modifiers for Use with GROUP BY + tags: + - group_by + aliases: [] + signature: + display: AVG([DISTINCT] expr) + args: + - name: '[DISTINCT] expr' + optional: false + type: any + summary: Returns the average value of expr. + description: Returns the average value of expr. The DISTINCT option can be used + to return the average of the distinct values of expr. NULL values are ignored. + It is an aggregate function, and so can be used with the GROUP BY clause. AVG() + returns NULL if there were no matching rows. AVG() can be used as a window function. + examples: + - sql: CREATE TABLE sales (sales_value INT); + result: INSERT INTO sales VALUES(10),(20),(20),(40); + - sql: SELECT AVG(sales_value) FROM sales; + result: +------------------+ | AVG(sales_value) | +------------------+ | 22.5000 + | +------------------+ + - sql: SELECT AVG(DISTINCT(sales_value)) FROM sales; + result: +----------------------------+ | AVG(DISTINCT(sales_value)) | +----------------------------+ + | 23.3333 | +----------------------------+ + - sql: 'Commonly, AVG() is used with a GROUP BY clause:' + result: CREATE TABLE student (name CHAR(10), test CHAR(10), score TINYINT); + - sql: "INSERT INTO student VALUES\n ('Chun', 'SQL', 75), ('Chun', 'Tuning', 73),\n\ + \ ('Esben', 'SQL', 43), ('Esben', 'Tuning', 31),\n ('Kaolin', 'SQL', 56),\ + \ ('Kaolin', 'Tuning', 88),\n ('Tatiana', 'SQL', 87), ('Tatiana', 'Tuning',\ + \ 83);" + result: SELECT name, AVG(score) FROM student GROUP BY name; +---------+------------+ + | name | AVG(score) | +---------+------------+ | Chun | 74.0000 | + | Esben | 37.0000 | | Kaolin | 72.0000 | | Tatiana | 85.0000 | + +---------+------------+ + - sql: "Be careful to avoid this common mistake, not grouping correctly and returning\n\ + \ ..." + - name: BENCHMARK + category_id: information + category_label: Information Functions + tags: + - information + aliases: [] + signature: + display: BENCHMARK(count,expr) + args: + - name: count + optional: false + type: any + - name: expr + optional: false + type: any + summary: The BENCHMARK() function executes the expression expr repeatedly count + times. + description: The BENCHMARK() function executes the expression expr repeatedly + count times. It may be used to time how quickly MariaDB processes the expression. + The result value is always 0. The intended use is from within the mariadb client, + which reports query execution times. + examples: + - sql: SELECT BENCHMARK(1000000,ENCODE('hello','goodbye')); + result: +----------------------------------------------+ | BENCHMARK(1000000,ENCODE('hello','goodbye')) + | +----------------------------------------------+ | 0 + | +----------------------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/benchmark/' + - name: BIGINT + category_id: data_types + category_label: Data Types + tags: + - data_types + aliases: [] + signature: + display: BIGINT(M) + args: + - name: M + optional: false + type: any + summary: A large integer. + description: 'A large integer. The signed range is -9223372036854775808 to 9223372036854775807. + The unsigned range is 0 to 18446744073709551615. If a column has been set to + ZEROFILL, all values will be prepended by zeros so that the BIGINT value contains + a number of M digits. Note: If the ZEROFILL attribute has been specified, the + column will automatically become UNSIGNED. For more details on the attributes, + see Numeric Data Type Overview. SERIAL is an alias for: BIGINT UNSIGNED NOT + NULL AUTO_INCREMENT UNIQUE INT8 is a synonym for BIGINT.' + examples: + - sql: CREATE TABLE bigints (a BIGINT,b BIGINT UNSIGNED,c BIGINT ZEROFILL); + result: 'With strict_mode set, the default from MariaDB 10.2.4:' + - sql: 'INSERT INTO bigints VALUES (-10,-10,-10); ERROR 1264 (22003): Out of range + value for column ''b'' at row 1' + result: INSERT INTO bigints VALUES (-10,10,-10); + - sql: INSERT INTO bigints VALUES (-10,10,10); + result: INSERT INTO bigints VALUES + - sql: 'ERROR 1264 (22003): Out of range value for column ''a'' at row 1' + result: INSERT INTO bigints VALUES + - sql: SELECT * FROM bigints; + result: +---------------------+---------------------+----------------------+ + | a | b | c | +---------------------+---------------------+----------------------+ + | -10 | 10 | 00000000000000000010 | | 9223372036854775807 + | 9223372036854775808 | 09223372036854775808 | +---------------------+---------------------+----------------------+ + - sql: 'With strict_mode unset, the default until MariaDB 10.2.3:' + result: INSERT INTO bigints VALUES (-10,-10,-10); + - name: BIN + category_id: string + category_label: String Functions + tags: + - string + aliases: [] + signature: + display: BIN(N) + args: + - name: N + optional: false + type: any + summary: Returns a string representation of the binary value of the given longlong + description: Returns a string representation of the binary value of the given + longlong (that is, BIGINT) number. This is equivalent to CONV(N,10,2). The argument + should be positive. If it is a FLOAT, it will be truncated. Returns NULL if + the argument is NULL. + examples: + - sql: SELECT BIN(12); + result: +---------+ | BIN(12) | +---------+ | 1100 | +---------+ + - sql: 'URL: https://mariadb.com/kb/en/bin/' + - name: BINARY + category_id: data_types + category_label: Data Types + tags: + - data_types + aliases: [] + signature: + display: BINARY(M) + args: + - name: M + optional: false + type: any + summary: The BINARY type is similar to the CHAR type, but stores binary byte strings + description: The BINARY type is similar to the CHAR type, but stores binary byte + strings rather than non-binary character strings. M represents the column length + in bytes. It contains no character set, and comparison and sorting are based + on the numeric value of the bytes. If the maximum length is exceeded, and SQL + strict mode is not enabled , the extra characters will be dropped with a warning. + If strict mode is enabled, an error will occur. BINARY values are right-padded + with 0x00 (the zero byte) to the specified length when inserted. The padding + is not removed on select, so this needs to be taken into account when sorting + and comparing, where all bytes are significant. The zero byte, 0x00 is less + than a space for comparison purposes. + examples: + - sql: 'Inserting too many characters, first with strict mode off, then with it + on:' + result: CREATE TABLE bins (a BINARY(10)); + - sql: INSERT INTO bins VALUES('12345678901'); Query OK, 1 row affected, 1 warning + (0.04 sec) + result: SELECT * FROM bins; +------------+ | a | +------------+ | 1234567890 + | +------------+ + - sql: SET sql_mode='STRICT_ALL_TABLES'; + result: INSERT INTO bins VALUES('12345678901'); + - sql: 'Sorting is performed with the byte value:' + result: TRUNCATE bins; + - sql: INSERT INTO bins VALUES('A'),('B'),('a'),('b'); + result: SELECT * FROM bins ORDER BY a; +------+ | a | +------+ | A | | + B | + - name: BINLOG_GTID_POS + category_id: information + category_label: Information Functions + tags: + - information + aliases: [] + signature: + display: BINLOG_GTID_POS(binlog_filename,binlog_offset) + args: + - name: binlog_filename + optional: false + type: any + - name: binlog_offset + optional: false + type: any + summary: The BINLOG_GTID_POS() function takes as input an old-style binary log + position + description: The BINLOG_GTID_POS() function takes as input an old-style binary + log position in the form of a file name and a file offset. It looks up the position + in the current binlog, and returns a string representation of the corresponding + GTID position. If the position is not found in the current binlog, NULL is returned. + examples: + - sql: SELECT BINLOG_GTID_POS("master-bin.000001", 600); + result: 'URL: https://mariadb.com/kb/en/binlog_gtid_pos/' + - name: BIT + category_id: data_types + category_label: Data Types + tags: + - data_types + aliases: [] + signature: + display: BIT(M) + args: + - name: M + optional: false + type: any + summary: A bit-field type. + description: A bit-field type. M indicates the number of bits per value, from + 1 to 64. The default is 1 if M is omitted. Bit values can be inserted with b'value' + notation, where value is the bit value in 0's and 1's. Bit fields are automatically + zero-padded from the left to the full length of the bit, so for example in a + BIT(4) field, '10' is equivalent to '0010'. Bits are returned as binary, so + to display them, either add 0, or use a function such as HEX, OCT or BIN to + convert them. + examples: + - sql: CREATE TABLE b ( b1 BIT(8) ); + result: 'With strict_mode set, the default from MariaDB 10.2.4:' + - sql: INSERT INTO b VALUES (b'11111111'); + result: INSERT INTO b VALUES (b'01010101'); + - sql: 'INSERT INTO b VALUES (b''1111111111111''); ERROR 1406 (22001): Data too + long for column ''b1'' at row 1' + result: SELECT b1+0, HEX(b1), OCT(b1), BIN(b1) FROM b; +------+---------+---------+----------+ + | b1+0 | HEX(b1) | OCT(b1) | BIN(b1) | +------+---------+---------+----------+ + | 255 | FF | 377 | 11111111 | | 85 | 55 | 125 | 1010101 | + +------+---------+---------+----------+ + - sql: 'With strict_mode unset, the default until MariaDB 10.2.3:' + result: INSERT INTO b VALUES (b'11111111'),(b'01010101'),(b'1111111111111'); + - sql: 'Records: 3 Duplicates: 0 Warnings: 1' + result: SHOW WARNINGS; +---------+------+---------------------------------------------+ + | Level | Code | Message | +---------+------+---------------------------------------------+ + | Warning | 1264 | Out of range value for column 'b1' at row 3 | +---------+------+---------------------------------------------+ + - sql: SELECT b1+0, HEX(b1), OCT(b1), BIN(b1) FROM b; + result: +------+---------+---------+----------+ | b1+0 | HEX(b1) | OCT(b1) | + BIN(b1) | + - name: BIT_AND + category_id: group_by + category_label: Functions and Modifiers for Use with GROUP BY + tags: + - group_by + aliases: [] + signature: + display: BIT_AND(expr) + args: + - name: expr + optional: false + type: any + summary: Returns the bitwise AND of all bits in expr. + description: Returns the bitwise AND of all bits in expr. The calculation is performed + with 64-bit (BIGINT) precision. It is an aggregate function, and so can be used + with the GROUP BY clause. If no rows match, BIT_AND will return a value with + all bits set to 1. NULL values have no effect on the result unless all results + are NULL, which is treated as no match. BIT_AND can be used as a window function + with the addition of the over_clause. + examples: + - sql: CREATE TABLE vals (x INT); + result: INSERT INTO vals VALUES(111),(110),(100); + - sql: SELECT BIT_AND(x), BIT_OR(x), BIT_XOR(x) FROM vals; + result: +------------+-----------+------------+ | BIT_AND(x) | BIT_OR(x) | BIT_XOR(x) + | +------------+-----------+------------+ | 100 | 111 | 101 + | +------------+-----------+------------+ + - sql: 'As an aggregate function:' + result: CREATE TABLE vals2 (category VARCHAR(1), x INT); + - sql: "INSERT INTO vals2 VALUES\n ('a',111),('a',110),('a',100),\n ('b','000'),('b',001),('b',011);" + result: SELECT category, BIT_AND(x), BIT_OR(x), BIT_XOR(x) + - sql: +----------+------------+-----------+------------+ + result: '| category | BIT_AND(x) | BIT_OR(x) | BIT_XOR(x) | +----------+------------+-----------+------------+ + | a | 100 | 111 | 101 | | b | 0 + | 11 | 10 | +----------+------------+-----------+------------+' + - sql: 'No match:' + result: SELECT BIT_AND(NULL); +----------------------+ | BIT_AND(NULL) | + +----------------------+ | 18446744073709551615 | +----------------------+ + - sql: 'URL: https://mariadb.com/kb/en/bit_and/' + - name: BIT_COUNT + category_id: bit + category_label: Bit Functions + tags: + - bit + aliases: [] + signature: + display: BIT_COUNT(N) + args: + - name: N + optional: false + type: any + summary: Returns the number of bits that are set in the argument N. + description: Returns the number of bits that are set in the argument N. + examples: + - sql: SELECT BIT_COUNT(29), BIT_COUNT(b'101010'); + result: +---------------+----------------------+ | BIT_COUNT(29) | BIT_COUNT(b'101010') + | +---------------+----------------------+ | 4 | 3 + | +---------------+----------------------+ + - sql: 'URL: https://mariadb.com/kb/en/bit_count/' + - name: BIT_LENGTH + category_id: string + category_label: String Functions + tags: + - string + aliases: [] + signature: + display: BIT_LENGTH(str) + args: + - name: str + optional: false + type: any + summary: Returns the length of the given string argument in bits. + description: Returns the length of the given string argument in bits. If the argument + is not a string, it will be converted to string. If the argument is NULL, it + returns NULL. + examples: + - sql: SELECT BIT_LENGTH('text'); + result: +--------------------+ | BIT_LENGTH('text') | +--------------------+ + | 32 | +--------------------+ + - sql: SELECT BIT_LENGTH(''); + result: +----------------+ | BIT_LENGTH('') | +----------------+ | 0 + | +----------------+ + - sql: Compatibility ------------- + result: PostgreSQL and Sybase support BIT_LENGTH(). + - sql: 'URL: https://mariadb.com/kb/en/bit_length/' + - name: BIT_OR + category_id: group_by + category_label: Functions and Modifiers for Use with GROUP BY + tags: + - group_by + aliases: [] + signature: + display: BIT_OR(expr) + args: + - name: expr + optional: false + type: any + summary: Returns the bitwise OR of all bits in expr. + description: Returns the bitwise OR of all bits in expr. The calculation is performed + with 64-bit (BIGINT) precision. It is an aggregate function, and so can be used + with the GROUP BY clause. If no rows match, BIT_OR will return a value with + all bits set to 0. NULL values have no effect on the result unless all results + are NULL, which is treated as no match. BIT_OR can be used as a window function + with the addition of the over_clause. + examples: + - sql: CREATE TABLE vals (x INT); + result: INSERT INTO vals VALUES(111),(110),(100); + - sql: SELECT BIT_AND(x), BIT_OR(x), BIT_XOR(x) FROM vals; + result: +------------+-----------+------------+ | BIT_AND(x) | BIT_OR(x) | BIT_XOR(x) + | +------------+-----------+------------+ | 100 | 111 | 101 + | +------------+-----------+------------+ + - sql: 'As an aggregate function:' + result: CREATE TABLE vals2 (category VARCHAR(1), x INT); + - sql: "INSERT INTO vals2 VALUES\n ('a',111),('a',110),('a',100),\n ('b','000'),('b',001),('b',011);" + result: SELECT category, BIT_AND(x), BIT_OR(x), BIT_XOR(x) + - sql: +----------+------------+-----------+------------+ + result: '| category | BIT_AND(x) | BIT_OR(x) | BIT_XOR(x) | +----------+------------+-----------+------------+ + | a | 100 | 111 | 101 | | b | 0 + | 11 | 10 | +----------+------------+-----------+------------+' + - sql: 'No match:' + result: SELECT BIT_OR(NULL); +--------------+ | BIT_OR(NULL) | +--------------+ + | 0 | +--------------+ + - sql: 'URL: https://mariadb.com/kb/en/bit_or/' + - name: BIT_XOR + category_id: group_by + category_label: Functions and Modifiers for Use with GROUP BY + tags: + - group_by + aliases: [] + signature: + display: BIT_XOR(expr) + args: + - name: expr + optional: false + type: any + summary: Returns the bitwise XOR of all bits in expr. + description: Returns the bitwise XOR of all bits in expr. The calculation is performed + with 64-bit (BIGINT) precision. It is an aggregate function, and so can be used + with the GROUP BY clause. If no rows match, BIT_XOR will return a value with + all bits set to 0. NULL values have no effect on the result unless all results + are NULL, which is treated as no match. BIT_XOR can be used as a window function + with the addition of the over_clause. + examples: + - sql: CREATE TABLE vals (x INT); + result: INSERT INTO vals VALUES(111),(110),(100); + - sql: SELECT BIT_AND(x), BIT_OR(x), BIT_XOR(x) FROM vals; + result: +------------+-----------+------------+ | BIT_AND(x) | BIT_OR(x) | BIT_XOR(x) + | +------------+-----------+------------+ | 100 | 111 | 101 + | +------------+-----------+------------+ + - sql: 'As an aggregate function:' + result: CREATE TABLE vals2 (category VARCHAR(1), x INT); + - sql: "INSERT INTO vals2 VALUES\n ('a',111),('a',110),('a',100),\n ('b','000'),('b',001),('b',011);" + result: SELECT category, BIT_AND(x), BIT_OR(x), BIT_XOR(x) + - sql: +----------+------------+-----------+------------+ + result: '| category | BIT_AND(x) | BIT_OR(x) | BIT_XOR(x) | +----------+------------+-----------+------------+ + | a | 100 | 111 | 101 | | b | 0 + | 11 | 10 | +----------+------------+-----------+------------+' + - sql: 'No match:' + result: SELECT BIT_XOR(NULL); +---------------+ | BIT_XOR(NULL) | +---------------+ + | 0 | +---------------+ + - sql: 'URL: https://mariadb.com/kb/en/bit_xor/' + - name: BLOB + category_id: data_types + category_label: Data Types + tags: + - data_types + aliases: [] + signature: + display: BLOB(M) + args: + - name: M + optional: false + type: any + summary: A BLOB column with a maximum length of 65,535 (216 - 1) bytes. + description: 'A BLOB column with a maximum length of 65,535 (216 - 1) bytes. Each + BLOB value is stored using a two-byte length prefix that indicates the number + of bytes in the value. An optional length M can be given for this type. If this + is done, MariaDB creates the column as the smallest BLOB type large enough to + hold values M bytes long. BLOBS can also be used to store dynamic columns. BLOB + and TEXT columns can both be assigned a DEFAULT value. Indexing -------- MariaDB + starting with 10.4 -------------------------- From MariaDB 10.4, it is possible + to set a unique index on a column that uses the BLOB data type. In previous + releases this was not possible, as the index would only guarantee the uniqueness + of a fixed number of characters. Oracle Mode ----------- In Oracle mode from + MariaDB 10.3, BLOB is a synonym for LONGBLOB. URL: https://mariadb.com/kb/en/blob/' + examples: [] + - name: CAST + category_id: string + category_label: String Functions + tags: + - string + aliases: [] + signature: + display: CAST(expr AS type) + args: + - name: expr AS type + optional: false + type: any + summary: The CAST() function takes a value of one type and produces a value of + another + description: 'The CAST() function takes a value of one type and produces a value + of another type, similar to the CONVERT() function. The type can be one of the + following values: * BINARY * CHAR * DATE * DATETIME * DECIMAL[(M[,D])] * DOUBLE + * FLOAT (from MariaDB 10.4.5) * INTEGER Short for SIGNED INTEGER * SIGNED [INTEGER] + * UNSIGNED [INTEGER] * TIME * VARCHAR (in Oracle mode, from MariaDB 10.3) The + main difference between CAST and CONVERT() is that CONVERT(expr,type) is ODBC + syntax while CAST(expr as type) and CONVERT(... USING ...) are SQL92 syntax. + In MariaDB 10.4 and later, you can use the CAST() function with the INTERVAL + keyword. Until MariaDB 5.5.31, X''HHHH'', the standard SQL syntax for binary + string literals, erroneously worked in the same way as 0xHHHH. In 5.5.31 it + was intentionally changed to behave as a string in all contexts (and never as + a number). This introduced an incompatibility with previous versions of MariaDB, + and all versions of MySQL (see the example below).' + examples: + - sql: 'Simple casts:' + result: SELECT CAST("abc" AS BINARY); + - sql: SELECT CAST(123 AS CHAR CHARACTER SET utf8) + result: Note that when one casts to CHAR without specifying the character set, + the + - sql: CHARACTER SET, the default collation for that character set will be used. + result: SELECT COLLATION(CAST(123 AS CHAR)); +------------------------------+ + - name: CEIL + category_id: numeric + category_label: Numeric Functions + tags: + - numeric + aliases: [] + signature: + display: CEIL(X) + args: + - name: X + optional: false + type: any + summary: CEIL() is a synonym for CEILING(). + description: 'CEIL() is a synonym for CEILING(). URL: https://mariadb.com/kb/en/ceil/' + examples: [] + - name: CEILING + category_id: numeric + category_label: Numeric Functions + tags: + - numeric + aliases: [] + signature: + display: CEILING(X) + args: + - name: X + optional: false + type: any + summary: Returns the smallest integer value not less than X. + description: Returns the smallest integer value not less than X. + examples: + - sql: SELECT CEILING(1.23); + result: +---------------+ | CEILING(1.23) | +---------------+ | 2 + | +---------------+ + - sql: SELECT CEILING(-1.23); + result: +----------------+ | CEILING(-1.23) | +----------------+ | -1 + | +----------------+ + - sql: 'URL: https://mariadb.com/kb/en/ceiling/' + - name: CHAR + category_id: data_types + category_label: Data Types + tags: + - data_types + aliases: [] + signature: + display: CHAR(M) + args: + - name: M + optional: false + type: any + summary: A fixed-length string that is always right-padded with spaces to the + specified + description: 'A fixed-length string that is always right-padded with spaces to + the specified length when stored. M represents the column length in characters. + The range of M is 0 to 255. If M is omitted, the length is 1. CHAR(0) columns + can contain 2 values: an empty string or NULL. Such columns cannot be part of + an index. The CONNECT storage engine does not support CHAR(0). Note: Trailing + spaces are removed when CHAR values are retrieved unless the PAD_CHAR_TO_FULL_LENGTH + SQL mode is enabled. Before MariaDB 10.2, all collations were of type PADSPACE, + meaning that CHAR (as well as VARCHAR and TEXT) values are compared without + regard for trailing spaces. This does not apply to the LIKE pattern-matching + operator, which takes into account trailing spaces. If a unique index consists + of a column where trailing pad characters are stripped or ignored, inserts into + that column where values differ only by the number of trailing pad characters + will result in a duplicate-key error.' + examples: + - sql: 'Trailing spaces:' + result: CREATE TABLE strtest (c CHAR(10)); + - sql: SELECT c='Maria',c='Maria ' FROM strtest; + result: +-----------+--------------+ | c='Maria' | c='Maria ' | +-----------+--------------+ + | 1 | 1 | +-----------+--------------+ + - sql: SELECT c LIKE 'Maria',c LIKE 'Maria ' FROM strtest; + result: +----------------+-------------------+ | c LIKE 'Maria' | c LIKE 'Maria ' + | +----------------+-------------------+ | 1 | 0 + | +----------------+-------------------+ + - sql: NO PAD Collations ----------------- + result: NO PAD collations regard trailing spaces as normal characters. You can + get a + - sql: 'table, for example:' + result: SELECT collation_name FROM information_schema.collations + - name: CHARSET + category_id: information + category_label: Information Functions + tags: + - information + aliases: [] + signature: + display: CHARSET(str) + args: + - name: str + optional: false + type: any + summary: Returns the character set of the string argument. + description: Returns the character set of the string argument. If str is not a + string, it is considered as a binary string (so the function returns 'binary'). + This applies to NULL, too. The return value is a string in the utf8 character + set. + examples: + - sql: SELECT CHARSET('abc'); + result: +----------------+ | CHARSET('abc') | +----------------+ | latin1 | + +----------------+ + - sql: SELECT CHARSET(CONVERT('abc' USING utf8)); + result: +------------------------------------+ | CHARSET(CONVERT('abc' USING + utf8)) | +------------------------------------+ | utf8 | + +------------------------------------+ + - sql: SELECT CHARSET(USER()); + result: +-----------------+ | CHARSET(USER()) | +-----------------+ | utf8 | + +-----------------+ + - sql: 'URL: https://mariadb.com/kb/en/charset/' + - name: CHAR_LENGTH + category_id: string + category_label: String Functions + tags: + - string + aliases: + - LENGTH + signature: + display: CHAR_LENGTH(str) + args: + - name: str + optional: false + type: any + summary: Returns the length of the given string argument, measured in characters. + description: Returns the length of the given string argument, measured in characters. + A multi-byte character counts as a single character. This means that for a string + containing five two-byte characters, LENGTH() (or OCTET_LENGTH() in Oracle mode) + returns 10, whereas CHAR_LENGTH() returns 5. If the argument is NULL, it returns + NULL. If the argument is not a string value, it is converted into a string. + It is synonymous with the CHARACTER_LENGTH() function. + examples: + - sql: SELECT CHAR_LENGTH('MariaDB'); + result: +------------------------+ | CHAR_LENGTH('MariaDB') | +------------------------+ + | 7 | +------------------------+ + - sql: 'When Oracle mode from MariaDB 10.3 is not set:' + result: "SELECT CHAR_LENGTH('\u03C0'), LENGTH('\u03C0'), LENGTHB('\u03C0'),\ + \ OCTET_LENGTH('\u03C0');\n+-------------------+--------------+---------------+--------------------+\n\ + | CHAR_LENGTH('\u03C0') | LENGTH('\u03C0') | LENGTHB('\u03C0') | OCTET_LENGTH('\u03C0\ + ') |\n+-------------------+--------------+---------------+--------------------+\n\ + | 1 | 2 | 2 | 2 |\n\ + +-------------------+--------------+---------------+--------------------+" + - sql: 'In Oracle mode from MariaDB 10.3:' + result: "SELECT CHAR_LENGTH('\u03C0'), LENGTH('\u03C0'), LENGTHB('\u03C0'),\ + \ OCTET_LENGTH('\u03C0');\n+-------------------+--------------+---------------+--------------------+\n\ + | CHAR_LENGTH('\u03C0') | LENGTH('\u03C0') | LENGTHB('\u03C0') | OCTET_LENGTH('\u03C0\ + ') |\n+-------------------+--------------+---------------+--------------------+\n\ + | 1 | 1 | 2 | 2 |\n\ + +-------------------+--------------+---------------+--------------------+" + - sql: 'URL: https://mariadb.com/kb/en/char_length/' + - name: CHR + category_id: string + category_label: String Functions + tags: + - string + aliases: [] + signature: + display: CHR(N) + args: + - name: N + optional: false + type: any + summary: CHR() interprets each argument N as an integer and returns a VARCHAR(1) + string + description: CHR() interprets each argument N as an integer and returns a VARCHAR(1) + string consisting of the character given by the code values of the integer. + The character set and collation of the string are set according to the values + of the character_set_database and collation_database system variables. CHR() + is similar to the CHAR() function, but only accepts a single argument. CHR() + is available in all sql_modes. + examples: + - sql: SELECT CHR(67); + result: +---------+ | CHR(67) | +---------+ | C | +---------+ + - sql: SELECT CHR('67'); + result: +-----------+ | CHR('67') | +-----------+ | C | +-----------+ + - sql: SELECT CHR('C'); + result: +----------+ | CHR('C') | +----------+ | | +----------+ + - sql: SHOW WARNINGS; + result: '+---------+------+----------------------------------------+ | Level | + Code | Message | +---------+------+----------------------------------------+ + | Warning | 1292 | Truncated incorrect INTEGER value: ''C'' | +---------+------+----------------------------------------+' + - sql: 'URL: https://mariadb.com/kb/en/chr/' + - name: COALESCE + category_id: comparison_operators + category_label: Comparison Operators + tags: + - comparison_operators + aliases: [] + signature: + display: COALESCE(value,...) + args: + - name: value + optional: false + type: any + - name: '...' + optional: false + type: any + summary: Returns the first non-NULL value in the list, or NULL if there are no + non-NULL + description: Returns the first non-NULL value in the list, or NULL if there are + no non-NULL values. At least one parameter must be passed. The function is useful + when substituting a default value for null values when displaying data. See + also NULL Values in MariaDB. + examples: + - sql: SELECT COALESCE(NULL,1); + result: +------------------+ | COALESCE(NULL,1) | +------------------+ | 1 + | +------------------+ + - sql: SELECT COALESCE(NULL,NULL,NULL); + result: +--------------------------+ | COALESCE(NULL,NULL,NULL) | +--------------------------+ + | NULL | +--------------------------+ + - sql: 'When two arguments are given, COALESCE() is the same as IFNULL():' + result: SET @a=NULL, @b=1; + - sql: SELECT COALESCE(@a, @b), IFNULL(@a, @b); + result: +------------------+----------------+ | COALESCE(@a, @b) | IFNULL(@a, + @b) | +------------------+----------------+ | 1 | 1 + | +------------------+----------------+ + - sql: 'Hex type confusion:' + result: CREATE TABLE t1 (a INT, b VARCHAR(10)); + - sql: SELECT * FROM t1; + result: +------+------+ | a | b | +------+------+ | 49 | a | | 1 + | a | +------+------+ + - sql: "The reason for the differing results above is that when 0x31 is inserted\n\ + \ ..." + - name: COERCIBILITY + category_id: information + category_label: Information Functions + tags: + - information + aliases: [] + signature: + display: COERCIBILITY(str) + args: + - name: str + optional: false + type: any + summary: Returns the collation coercibility value of the string argument. + description: Returns the collation coercibility value of the string argument. + Coercibility defines what will be converted to what in case of collation conflict, + with an expression with higher coercibility being converted to the collation + of an expression with lower coercibility. +-----------------------------+---------------------------+------------------+ + | Coercibility | Description | Example | + +-----------------------------+---------------------------+------------------+ + | 0 | Explicit | Value using a | + | | | COLLATE clause | + +-----------------------------+---------------------------+------------------+ + | 1 | No collation | Concatenated | + | | | strings using | + | | | different | + | | | collations | + +-----------------------------+---------------------------+------------------+ + | 2 | Implicit | A string data | + | | | type column | + | | | value, CAST to | + | | | a string data | + | | | type | + +-----------------------------+---------------------------+------------------+ + | 3 | System constant | DATABASE(), | + | | | USER() return | + | | | value | + +-----------------------------+---------------------------+------------------+ + | 4 | Coercible | Literal string | + +-----------------------------+---------------------------+------------------+ + | 5 | Numeric | Numeric and | + | | | temporal values | + +-----------------------------+---------------------------+------------------+ + | 6 | Ignorable | NULL or derived | + | | | from NULL | + +-----------------------------+---------------------------+------------------+ + examples: + - sql: SELECT COERCIBILITY('abc' COLLATE latin1_swedish_ci); + result: +-----------------------------------------------+ | COERCIBILITY('abc' + COLLATE latin1_swedish_ci) | +-----------------------------------------------+ + | 0 | +-----------------------------------------------+ + - sql: SELECT COERCIBILITY(CAST(1 AS CHAR)); + result: +-------------------------------+ | COERCIBILITY(CAST(1 AS CHAR)) | + +-------------------------------+ | 2 | + - name: COLLATION + category_id: information + category_label: Information Functions + tags: + - information + aliases: [] + signature: + display: COLLATION(str) + args: + - name: str + optional: false + type: any + summary: Returns the collation of the string argument. + description: Returns the collation of the string argument. If str is not a string, + it is considered as a binary string (so the function returns 'binary'). This + applies to NULL, too. The return value is a string in the utf8 character set. + See Character Sets and Collations. + examples: + - sql: SELECT COLLATION('abc'); + result: +-------------------+ | COLLATION('abc') | +-------------------+ | + latin1_swedish_ci | +-------------------+ + - sql: SELECT COLLATION(_utf8'abc'); + result: +-----------------------+ | COLLATION(_utf8'abc') | +-----------------------+ + | utf8_general_ci | +-----------------------+ + - sql: 'URL: https://mariadb.com/kb/en/collation/' + - name: COLUMN_ADD + category_id: dynamic_column + category_label: Dynamic Column Functions + tags: + - dynamic_column + aliases: [] + signature: + display: COLUMN_ADD(dyncol_blob, column_nr, value [as type], [column_nr, value + [as type]]...) + args: + - name: dyncol_blob + optional: false + type: any + - name: column_nr + optional: false + type: any + - name: value [as type] + optional: false + type: any + - name: '[column_nr' + optional: false + type: any + - name: value [as type]]... + optional: false + type: any + summary: Adds or updates dynamic columns. + description: 'Adds or updates dynamic columns. * dyncol_blob must be either a + valid dynamic columns blob (for example, COLUMN_CREATE returns such blob), or + an empty string. * column_name specifies the name of the column to be added. + If dyncol_blob already has a column with this name, it will be overwritten. + * value specifies the new value for the column. Passing a NULL value will cause + the column to be deleted. * as type is optional. See #datatypes section for + a discussion about types. The return value is a dynamic column blob after the + modifications.' + examples: + - sql: UPDATE t1 SET dyncol_blob=COLUMN_ADD(dyncol_blob, "column_name", "value") + WHERE id=1; + result: 'Note: COLUMN_ADD() is a regular function (just like CONCAT()), hence, + in order' + - sql: dynamic_col=COLUMN_ADD(dynamic_col, ....) pattern. + result: 'URL: https://mariadb.com/kb/en/column_add/' + - name: COLUMN_CHECK + category_id: dynamic_column + category_label: Dynamic Column Functions + tags: + - dynamic_column + aliases: [] + signature: + display: COLUMN_CHECK(dyncol_blob) + args: + - name: dyncol_blob + optional: false + type: any + summary: Check if dyncol_blob is a valid packed dynamic columns blob. + description: 'Check if dyncol_blob is a valid packed dynamic columns blob. Return + value of 1 means the blob is valid, return value of 0 means it is not. Rationale: + Normally, one works with valid dynamic column blobs. Functions like COLUMN_CREATE, + COLUMN_ADD, COLUMN_DELETE always return valid dynamic column blobs. However, + if a dynamic column blob is accidentally truncated, or transcoded from one character + set to another, it will be corrupted. This function can be used to check if + a value in a blob field is a valid dynamic column blob. URL: https://mariadb.com/kb/en/column_check/' + examples: [] + - name: COLUMN_CREATE + category_id: dynamic_column + category_label: Dynamic Column Functions + tags: + - dynamic_column + aliases: [] + signature: + display: COLUMN_CREATE(column_nr, value [as type], [column_nr, value [as type]]...) + args: + - name: column_nr + optional: false + type: any + - name: value [as type] + optional: false + type: any + - name: '[column_nr' + optional: false + type: any + - name: value [as type]]... + optional: false + type: any + summary: Returns a dynamic columns blob that stores the specified columns with + values. + description: Returns a dynamic columns blob that stores the specified columns + with values. The return value is suitable for * storing in a table * further + modification with other dynamic columns functions The as type part allows one + to specify the value type. In most cases, this is redundant because MariaDB + will be able to deduce the type of the value. Explicit type specification may + be needed when the type of the value is not apparent. For example, a literal + '2012-12-01' has a CHAR type by default, one will need to specify '2012-12-01' + AS DATE to have it stored as a date. See Dynamic Columns:Datatypes for further + details. + examples: + - sql: INSERT INTO tbl SET dyncol_blob=COLUMN_CREATE("column_name", "value"); + result: 'URL: https://mariadb.com/kb/en/column_create/' + - name: COLUMN_DELETE + category_id: dynamic_column + category_label: Dynamic Column Functions + tags: + - dynamic_column + aliases: [] + signature: + display: COLUMN_DELETE(dyncol_blob, column_nr, column_nr...) + args: + - name: dyncol_blob + optional: false + type: any + - name: column_nr + optional: false + type: any + - name: column_nr... + optional: false + type: any + summary: Deletes a dynamic column with the specified name. + description: 'Deletes a dynamic column with the specified name. Multiple names + can be given. The return value is a dynamic column blob after the modification. + URL: https://mariadb.com/kb/en/column_delete/' + examples: [] + - name: COLUMN_EXISTS + category_id: dynamic_column + category_label: Dynamic Column Functions + tags: + - dynamic_column + aliases: [] + signature: + display: COLUMN_EXISTS(dyncol_blob, column_nr) + args: + - name: dyncol_blob + optional: false + type: any + - name: column_nr + optional: false + type: any + summary: Checks if a column with name column_name exists in dyncol_blob. + description: 'Checks if a column with name column_name exists in dyncol_blob. + If yes, return 1, otherwise return 0. See dynamic columns for more information. + URL: https://mariadb.com/kb/en/column_exists/' + examples: [] + - name: COLUMN_GET + category_id: dynamic_column + category_label: Dynamic Column Functions + tags: + - dynamic_column + aliases: [] + signature: + display: COLUMN_GET(dyncol_blob, column_nr as type) + args: + - name: dyncol_blob + optional: false + type: any + - name: column_nr as type + optional: false + type: any + summary: Gets the value of a dynamic column by its name. + description: 'Gets the value of a dynamic column by its name. If no column with + the given name exists, NULL will be returned. column_name as type requires that + one specify the datatype of the dynamic column they are reading. This may seem + counter-intuitive: why would one need to specify which datatype they''re retrieving? + Can''t the dynamic columns system figure the datatype from the data being stored? + The answer is: SQL is a statically-typed language. The SQL interpreter needs + to know the datatypes of all expressions before the query is run (for example, + when one is using prepared statements and runs "select COLUMN_GET(...)", the + prepared statement API requires the server to inform the client about the datatype + of the column being read before the query is executed and the server can see + what datatype the column actually has). Lengths ------- If you''re running queries + like: SELECT COLUMN_GET(blob, ''colname'' as CHAR) ... without specifying a + maximum length (i.e. using as CHAR, not as CHAR(n)), MariaDB will report the + maximum length of the resultset column to be 16,777,216. This may cause excessive + memory usage in some client libraries, because they try to pre-allocate a buffer + of maximum resultset width. To avoid this problem, use CHAR(n) whenever you''re + using COLUMN_GET in the select list. See Dynamic Columns:Datatypes for more + information about datatypes. URL: https://mariadb.com/kb/en/column_get/' + examples: [] + - name: COLUMN_JSON + category_id: dynamic_column + category_label: Dynamic Column Functions + tags: + - dynamic_column + aliases: [] + signature: + display: COLUMN_JSON(dyncol_blob) + args: + - name: dyncol_blob + optional: false + type: any + summary: Returns a JSON representation of data in dyncol_blob. + description: 'Returns a JSON representation of data in dyncol_blob. Can also be + used to display nested columns. See dynamic columns for more information. Example + ------- select item_name, COLUMN_JSON(dynamic_cols) from assets; +-----------------+----------------------------------------+ + | item_name | COLUMN_JSON(dynamic_cols) | +-----------------+----------------------------------------+ + | MariaDB T-shirt | {"size":"XL","color":"blue"} | | Thinkpad Laptop + | {"color":"black","warranty":"3 years"} | +-----------------+----------------------------------------+ + Limitation: COLUMN_JSON will decode nested dynamic columns at a nesting level + of not more than 10 levels deep. Dynamic columns that are nested deeper than + 10 levels will be shown as BINARY string, without encoding. URL: https://mariadb.com/kb/en/column_json/' + examples: [] + - name: COLUMN_LIST + category_id: dynamic_column + category_label: Dynamic Column Functions + tags: + - dynamic_column + aliases: [] + signature: + display: COLUMN_LIST(dyncol_blob) + args: + - name: dyncol_blob + optional: false + type: any + summary: Returns a comma-separated list of column names. + description: 'Returns a comma-separated list of column names. The names are quoted + with backticks. See dynamic columns for more information. URL: https://mariadb.com/kb/en/column_list/' + examples: [] + - name: COMMIT + category_id: transactions + category_label: Transactions + tags: + - transactions + aliases: [] + signature: + display: COMMIT(the keyword WORK is simply noise and can be omitted without + changing the effect) + args: + - name: the keyword WORK is simply noise and can be omitted without changing + the effect + optional: false + type: any + summary: The optional AND CHAIN clause is a convenience for initiating a new + description: 'The optional AND CHAIN clause is a convenience for initiating a + new transaction as soon as the old transaction terminates. If AND CHAIN is specified, + then there is effectively nothing between the old and new transactions, although + they remain separate. The characteristics of the new transaction will be the + same as the characteristics of the old one - that is, the new transaction will + have the same access mode, isolation level and diagnostics area size (we''ll + discuss all of these shortly) as the transaction just terminated. RELEASE tells + the server to disconnect the client immediately after the current transaction. + There are NO RELEASE and AND NO CHAIN options. By default, commits do not RELEASE + or CHAIN, but it''s possible to change this default behavior with the completion_type + server system variable. In this case, the AND NO CHAIN and NO RELEASE options + override the server default. URL: https://mariadb.com/kb/en/commit/' + examples: [] + - name: COMPRESS + category_id: encryption + category_label: Encryption Functions + tags: + - encryption + aliases: [] + signature: + display: COMPRESS(string_to_compress) + args: + - name: string_to_compress + optional: false + type: any + summary: Compresses a string and returns the result as a binary string. + description: Compresses a string and returns the result as a binary string. This + function requires MariaDB to have been compiled with a compression library such + as zlib. Otherwise, the return value is always NULL. The compressed string can + be uncompressed with UNCOMPRESS(). The have_compress server system variable + indicates whether a compression library is present. + examples: + - sql: SELECT LENGTH(COMPRESS(REPEAT('a',1000))); + result: +------------------------------------+ | LENGTH(COMPRESS(REPEAT('a',1000))) + | +------------------------------------+ | 21 + | +------------------------------------+ + - sql: SELECT LENGTH(COMPRESS('')); + result: +----------------------+ | LENGTH(COMPRESS('')) | +----------------------+ + | 0 | +----------------------+ + - sql: SELECT LENGTH(COMPRESS('a')); + result: +-----------------------+ | LENGTH(COMPRESS('a')) | +-----------------------+ + | 13 | +-----------------------+ + - sql: SELECT LENGTH(COMPRESS(REPEAT('a',16))); + result: +----------------------------------+ | LENGTH(COMPRESS(REPEAT('a',16))) + | +----------------------------------+ | 15 + | +----------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/compress/' + - name: CONCAT + category_id: string + category_label: String Functions + tags: + - string + aliases: [] + signature: + display: CONCAT(str1,str2,...) + args: + - name: str1 + optional: false + type: any + - name: str2 + optional: false + type: any + - name: '...' + optional: false + type: any + summary: Returns the string that results from concatenating the arguments. + description: 'Returns the string that results from concatenating the arguments. + May have one or more arguments. If all arguments are non-binary strings, the + result is a non-binary string. If the arguments include any binary strings, + the result is a binary string. A numeric argument is converted to its equivalent + binary string form; if you want to avoid that, you can use an explicit type + cast, as in this example: SELECT CONCAT(CAST(int_col AS CHAR), char_col); CONCAT() + returns NULL if any argument is NULL. A NULL parameter hides all information + contained in other parameters from the result. Sometimes this is not desirable; + to avoid this, you can: * Use the CONCAT_WS() function with an empty separator, + because that function is NULL-safe. * Use IFNULL() to turn NULLs into empty + strings. Oracle Mode ----------- In Oracle mode, CONCAT ignores NULL.' + examples: + - sql: SELECT CONCAT('Ma', 'ria', 'DB'); + result: +---------------------------+ | CONCAT('Ma', 'ria', 'DB') | +---------------------------+ + | MariaDB | +---------------------------+ + - sql: SELECT CONCAT('Ma', 'ria', NULL, 'DB'); + result: +---------------------------------+ | CONCAT('Ma', 'ria', NULL, 'DB') + | +---------------------------------+ | NULL | + +---------------------------------+ + - sql: SELECT CONCAT(42.0); + result: +--------------+ | CONCAT(42.0) | +--------------+ | 42.0 | + +--------------+ + - sql: 'Using IFNULL() to handle NULLs:' + result: 'SELECT CONCAT(''The value of @v is: '', IFNULL(@v, ''''));' + - name: CONCAT_WS + category_id: string + category_label: String Functions + tags: + - string + aliases: [] + signature: + display: CONCAT_WS(separator,str1,str2,...) + args: + - name: separator + optional: false + type: any + - name: str1 + optional: false + type: any + - name: str2 + optional: false + type: any + - name: '...' + optional: false + type: any + summary: CONCAT_WS() stands for Concatenate With Separator and is a special form + of + description: CONCAT_WS() stands for Concatenate With Separator and is a special + form of CONCAT(). The first argument is the separator for the rest of the arguments. + The separator is added between the strings to be concatenated. The separator + can be a string, as can the rest of the arguments. If the separator is NULL, + the result is NULL; all other NULL values are skipped. This makes CONCAT_WS() + suitable when you want to concatenate some values and avoid losing all information + if one of them is NULL. + examples: + - sql: SELECT CONCAT_WS(',','First name','Second name','Last Name'); + result: +-------------------------------------------------------+ | CONCAT_WS(',','First + name','Second name','Last Name') | +-------------------------------------------------------+ + | First name,Second name,Last Name | +-------------------------------------------------------+ + - sql: SELECT CONCAT_WS('-','Floor',NULL,'Room'); + result: +------------------------------------+ | CONCAT_WS('-','Floor',NULL,'Room') + | +------------------------------------+ | Floor-Room | + +------------------------------------+ + - sql: 'In some cases, remember to include a space in the separator string:' + result: SET @a = 'gnu', @b = 'penguin', @c = 'sea lion'; + - sql: SELECT CONCAT_WS(', ', @a, @b, @c); + result: +-----------------------------+ | CONCAT_WS(', ', @a, @b, @c) | +-----------------------------+ + | gnu, penguin, sea lion | +-----------------------------+ + - sql: 'Using CONCAT_WS() to handle NULLs:' + result: SET @a = 'a', @b = NULL, @c = 'c'; + - sql: SELECT CONCAT_WS('', @a, @b, @c); + result: +---------------------------+ | CONCAT_WS('', @a, @b, @c) | +---------------------------+ + | ac | +---------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/concat_ws/' + - name: CONNECTION_ID + category_id: information + category_label: Information Functions + tags: + - information + aliases: [] + signature: + display: CONNECTION_ID + args: [] + summary: Returns the connection ID for the connection. + description: Returns the connection ID for the connection. Every connection (including + events) has an ID that is unique among the set of currently connected clients. + Until MariaDB 10.3.1, returns MYSQL_TYPE_LONGLONG, or bigint(10), in all cases. + From MariaDB 10.3.1, returns MYSQL_TYPE_LONG, or int(10), when the result would + fit within 32-bits. + examples: + - sql: SELECT CONNECTION_ID(); + result: +-----------------+ | CONNECTION_ID() | +-----------------+ | 3 + | +-----------------+ + - sql: 'URL: https://mariadb.com/kb/en/connection_id/' + - name: CONTAINS + category_id: geometry_relations + category_label: Geometry Relations + tags: + - geometry_relations + aliases: [] + signature: + display: CONTAINS(g1,g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + summary: Returns 1 or 0 to indicate whether a geometry g1 completely contains + geometry + description: 'Returns 1 or 0 to indicate whether a geometry g1 completely contains + geometry g2. CONTAINS() is based on the original MySQL implementation and uses + object bounding rectangles, while ST_CONTAINS() uses object shapes. This tests + the opposite relationship to Within(). URL: https://mariadb.com/kb/en/contains/' + examples: [] + - name: CONV + category_id: numeric + category_label: Numeric Functions + tags: + - numeric + aliases: [] + signature: + display: CONV(N,from_base,to_base) + args: + - name: N + optional: false + type: any + - name: from_base + optional: false + type: any + - name: to_base + optional: false + type: any + summary: Converts numbers between different number bases. + description: 'Converts numbers between different number bases. Returns a string + representation of the number N, converted from base from_base to base to_base. + Returns NULL if any argument is NULL, or if the second or third argument are + not in the allowed range. The argument N is interpreted as an integer, but may + be specified as an integer or a string. The minimum base is 2 and the maximum + base is 36 (prior to MariaDB 11.4.0) or 62 (from MariaDB 11.4.0). If to_base + is a negative number, N is regarded as a signed number. Otherwise, N is treated + as unsigned. CONV() works with 64-bit precision. Some shortcuts for this function + are also available: BIN(), OCT(), HEX(), UNHEX(). Also, MariaDB allows binary + literal values and hexadecimal literal values.' + examples: + - sql: SELECT CONV('a',16,2); + result: +----------------+ | CONV('a',16,2) | +----------------+ | 1010 | + +----------------+ + - sql: SELECT CONV('6E',18,8); + result: +-----------------+ | CONV('6E',18,8) | +-----------------+ | 172 | + +-----------------+ + - sql: SELECT CONV(-17,10,-18); + result: +------------------+ | CONV(-17,10,-18) | +------------------+ | -H | + +------------------+ + - sql: SELECT CONV(12+'10'+'10'+0xa,10,10); + result: +------------------------------+ | CONV(12+'10'+'10'+0xa,10,10) | +------------------------------+ + | 42 | +------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/conv/' + - name: CONVERT + category_id: string + category_label: String Functions + tags: + - string + aliases: [] + signature: + display: CONVERT(expr,type) + args: + - name: expr + optional: false + type: any + - name: type + optional: false + type: any + summary: "The\tCONVERT() and CAST() functions take a value of one type and produce\ + \ a" + description: "The\tCONVERT() and CAST() functions take a value of one type and\ + \ produce a\nvalue of another type.\n\nThe type can be one of the following\ + \ values:\n\n* BINARY\n* CHAR\n* DATE\n* DATETIME\n* DECIMAL[(M[,D])]\n* DOUBLE\n\ + * FLOAT (from MariaDB 10.4.5)\n* INTEGER\nShort for SIGNED INTEGER\n\n* SIGNED\ + \ [INTEGER]\n* UNSIGNED [INTEGER]\n* TIME\n* VARCHAR (in Oracle mode, from MariaDB\ + \ 10.3)\n\nNote that in MariaDB, INT and INTEGER are the same thing.\n\nBINARY\ + \ produces a string with the BINARY data type. If the optional length is\ngiven,\ + \ BINARY(N) causes the cast to use no more than N bytes of the argument.\nValues\ + \ shorter than the given number in bytes are padded with 0x00 bytes to\nmake\ + \ them equal the length value.\n\nCHAR(N) causes the cast to use no more than\ + \ the number of characters given in\nthe argument.\n\nThe main difference between\ + \ the CAST() and CONVERT() is that\nCONVERT(expr,type) is ODBC syntax while\ + \ CAST(expr as type) and CONVERT(...\nUSING ...) are SQL92 syntax.\n\nCONVERT()\ + \ with USING is used to convert data between different character sets.\nIn MariaDB,\ + \ transcoding names are the same as the corresponding character set\nnames.\ + \ For example, this statement converts the string 'abc' in the default\ncharacter\ + \ set to the corresponding string in the utf8 character set:\n\nSELECT CONVERT('abc'\ + \ USING utf8);" + examples: + - sql: SELECT enum_col FROM tbl_name ORDER BY CAST(enum_col AS CHAR); + result: 'Converting a BINARY to string to permit the LOWER function to work:' + - sql: "SET @x = 'AardVark';\n ..." + - name: CONVERT_TZ + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: CONVERT_TZ(dt,from_tz,to_tz) + args: + - name: dt + optional: false + type: any + - name: from_tz + optional: false + type: any + - name: to_tz + optional: false + type: any + summary: CONVERT_TZ() converts a datetime value dt from the time zone given by + from_tz + description: CONVERT_TZ() converts a datetime value dt from the time zone given + by from_tz to the time zone given by to_tz and returns the resulting value. + In order to use named time zones, such as GMT, MET or Africa/Johannesburg, the + time_zone tables must be loaded (see mysql_tzinfo_to_sql). No conversion will + take place if the value falls outside of the supported TIMESTAMP range ('1970-01-01 + 00:00:01' to '2038-01-19 05:14:07' UTC) when converted from from_tz to UTC. + This function returns NULL if the arguments are invalid (or named time zones + have not been loaded). See time zones for more information. + examples: + - sql: SELECT CONVERT_TZ('2016-01-01 12:00:00','+00:00','+10:00'); + result: +-----------------------------------------------------+ | CONVERT_TZ('2016-01-01 + 12:00:00','+00:00','+10:00') | +-----------------------------------------------------+ + | 2016-01-01 22:00:00 | +-----------------------------------------------------+ + - sql: 'Using named time zones (with the time zone tables loaded):' + result: SELECT CONVERT_TZ('2016-01-01 12:00:00','GMT','Africa/Johannesburg'); + +---------------------------------------------------------------+ | CONVERT_TZ('2016-01-01 + 12:00:00','GMT','Africa/Johannesburg') | +---------------------------------------------------------------+ + | 2016-01-01 14:00:00 | +---------------------------------------------------------------+ + - sql: 'The value is out of the TIMESTAMP range, so no conversion takes place:' + result: SELECT CONVERT_TZ('1969-12-31 22:00:00','+00:00','+10:00'); +-----------------------------------------------------+ + | CONVERT_TZ('1969-12-31 22:00:00','+00:00','+10:00') | +-----------------------------------------------------+ + | 1969-12-31 22:00:00 | +-----------------------------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/convert_tz/' + - name: COS + category_id: numeric + category_label: Numeric Functions + tags: + - numeric + aliases: [] + signature: + display: COS(X) + args: + - name: X + optional: false + type: any + summary: Returns the cosine of X, where X is given in radians. + description: Returns the cosine of X, where X is given in radians. + examples: + - sql: SELECT COS(PI()); + result: +-----------+ | COS(PI()) | +-----------+ | -1 | +-----------+ + - sql: 'URL: https://mariadb.com/kb/en/cos/' + - name: COT + category_id: numeric + category_label: Numeric Functions + tags: + - numeric + aliases: [] + signature: + display: COT(X) + args: + - name: X + optional: false + type: any + summary: Returns the cotangent of X. + description: Returns the cotangent of X. + examples: + - sql: SELECT COT(42); + result: +--------------------+ | COT(42) | +--------------------+ + | 0.4364167060752729 | +--------------------+ + - sql: SELECT COT(12); + result: +---------------------+ | COT(12) | +---------------------+ + | -1.5726734063976893 | +---------------------+ + - sql: 'SELECT COT(0); ERROR 1690 (22003): DOUBLE value is out of range in ''cot(0)''' + result: 'URL: https://mariadb.com/kb/en/cot/' + - name: COUNT + category_id: group_by + category_label: Functions and Modifiers for Use with GROUP BY + tags: + - group_by + aliases: [] + signature: + display: COUNT(expr) + args: + - name: expr + optional: false + type: any + summary: Returns a count of the number of non-NULL values of expr in the rows + retrieved + description: Returns a count of the number of non-NULL values of expr in the rows + retrieved by a SELECT statement. The result is a BIGINT value. It is an aggregate + function, and so can be used with the GROUP BY clause. COUNT(*) counts the total + number of rows in a table. COUNT() returns 0 if there were no matching rows. + COUNT() can be used as a window function. + examples: + - sql: CREATE TABLE student (name CHAR(10), test CHAR(10), score TINYINT); + result: INSERT INTO student VALUES + - sql: "('Esben', 'SQL', 43), ('Esben', 'Tuning', 31),\n ('Kaolin', 'SQL', 56),\ + \ ('Kaolin', 'Tuning', 88),\n ('Tatiana', 'SQL', 87), ('Tatiana', 'Tuning',\ + \ 83);" + result: SELECT COUNT(*) FROM student; +----------+ | COUNT(*) | +----------+ + | 8 | +----------+ + - sql: 'COUNT(DISTINCT) example:' + result: SELECT COUNT(DISTINCT (name)) FROM student; +------------------------+ + | COUNT(DISTINCT (name)) | +------------------------+ | 4 + | +------------------------+ + - sql: As a window function + result: CREATE OR REPLACE TABLE student_test (name CHAR(10), test CHAR(10), + score + - sql: "INSERT INTO student_test VALUES\n ('Chun', 'SQL', 75), ('Chun', 'Tuning',\ + \ 73),\n ('Esben', 'SQL', 43), ('Esben', 'Tuning', 31),\n ('Kaolin', 'SQL',\ + \ 56), ('Kaolin', 'Tuning', 88),\n ('Tatiana', 'SQL', 87);" + result: SELECT name, test, score, COUNT(score) OVER (PARTITION BY name) + - sql: '...' + - name: CRC32 + category_id: numeric + category_label: Numeric Functions + tags: + - numeric + aliases: [] + signature: + display: CRC32(expr) + args: + - name: expr + optional: false + type: any + summary: Computes a cyclic redundancy check (CRC) value and returns a 32-bit unsigned + description: 'Computes a cyclic redundancy check (CRC) value and returns a 32-bit + unsigned value. The result is NULL if the argument is NULL. The argument is + expected to be a string and (if possible) is treated as one if it is not. Uses + the ISO 3309 polynomial that used by zlib and many others. MariaDB 10.8 introduced + the CRC32C() function, which uses the alternate Castagnoli polynomia. MariaDB + starting with 10.8 -------------------------- Often, CRC is computed in pieces. + To facilitate this, MariaDB 10.8.0 introduced an optional parameter: CRC32(''MariaDB'')=CRC32(CRC32(''Maria''),''DB'').' + examples: + - sql: SELECT CRC32('MariaDB'); + result: +------------------+ | CRC32('MariaDB') | +------------------+ | 4227209140 + | +------------------+ + - sql: SELECT CRC32('mariadb'); + result: +------------------+ | CRC32('mariadb') | +------------------+ | 2594253378 + | +------------------+ + - sql: From MariaDB 10.8.0 + result: SELECT CRC32(CRC32('Maria'),'DB'); +----------------------------+ | + CRC32(CRC32('Maria'),'DB') | +----------------------------+ | 4227209140 + | +----------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/crc32/' + - name: CRC32C + category_id: numeric + category_label: Numeric Functions + tags: + - numeric + aliases: [] + signature: + display: CRC32C([par,]expr) + args: + - name: '[par' + optional: false + type: any + - name: ']expr' + optional: false + type: any + summary: MariaDB has always included a native unary function CRC32() that computes + the + description: "MariaDB has always included a native unary function CRC32() that\ + \ computes the\nCRC-32 of a string using the ISO 3309 polynomial that used by\ + \ zlib and many\nothers.\n\nInnoDB and MyRocks use a different polynomial, which\ + \ was implemented in SSE4.2\ninstructions that were introduced in the Intel\ + \ Nehalem microarchitecture. This\nis commonly called CRC-32C (Castagnoli).\n\ + \nThe CRC32C function uses the Castagnoli polynomial.\n\nThis allows SELECT\u2026\ + INTO DUMPFILE to be used for the creation of files with\nvalid checksums, such\ + \ as a logically empty InnoDB redo log file ib_logfile0\ncorresponding to a\ + \ particular log sequence number.\n\nThe optional parameter allows the checksum\ + \ to be computed in pieces:\nCRC32C('MariaDB')=CRC32C(CRC32C('Maria'),'DB')." + examples: + - sql: SELECT CRC32C('MariaDB'); + result: +-------------------+ | CRC32C('MariaDB') | +-------------------+ | 809606978 + | +-------------------+ + - sql: SELECT CRC32C(CRC32C('Maria'),'DB'); + result: +------------------------------+ | CRC32C(CRC32C('Maria'),'DB') | +------------------------------+ + | 809606978 | +------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/crc32c/' + - name: CROSSES + category_id: geometry_relations + category_label: Geometry Relations + tags: + - geometry_relations + aliases: [] + signature: + display: CROSSES(g1,g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + summary: Returns 1 if g1 spatially crosses g2. + description: "Returns 1 if g1 spatially crosses g2. Returns NULL if g1 is a Polygon\ + \ or a\nMultiPolygon, or if g2 is a Point or a MultiPoint. Otherwise, returns\ + \ 0.\n\nThe term spatially crosses denotes a spatial relation between two given\n\ + geometries that has the following properties:\n\n* The two geometries intersect\n\ + * Their intersection results in a geometry that has a dimension that is one\n\ + \ less than the maximum dimension of the two given geometries\n* Their intersection\ + \ is not equal to either of the two given geometries\n\nCROSSES() is based on\ + \ the original MySQL implementation, and uses object\nbounding rectangles, while\ + \ ST_CROSSES() uses object shapes.\n\nURL: https://mariadb.com/kb/en/crosses/" + examples: [] + - name: CUME_DIST + category_id: window + category_label: Window Functions + tags: + - window + aliases: [] + signature: + display: CUME_DIST + args: [] + summary: CUME_DIST() is a window function that returns the cumulative distribution + of a + description: 'CUME_DIST() is a window function that returns the cumulative distribution + of a given row. The following formula is used to calculate the value: (number + of rows <= current row) / (total rows)' + examples: + - sql: "create table t1 (\n pk int primary key,\n a int,\n b int\n);" + result: insert into t1 values + - sql: ( 2 , 0, 10), ( 3 , 1, 10), ( 4 , 1, 10), ( 8 , 2, 10), ( 5 , 2, 20), ( + 6 , 2, 20), ( 7 , 2, 20), ( 9 , 4, 20), (10 , 4, 20); + result: select pk, a, b, + - sql: "percent_rank() over (order by a) as pct_rank,\n cume_dist() over (order\ + \ by a) as cume_dist\nfrom t1;" + result: +----+------+------+------+--------------+--------------+ | pk | a | + b | rank | pct_rank | cume_dist | +----+------+------+------+--------------+--------------+ + | 1 | 0 | 10 | 1 | 0.0000000000 | 0.2000000000 | | 2 | 0 | 10 + | 1 | 0.0000000000 | 0.2000000000 | | 3 | 1 | 10 | 3 | 0.2222222222 + | 0.4000000000 | | 4 | 1 | 10 | 3 | 0.2222222222 | 0.4000000000 | + | 5 | 2 | 20 | 5 | 0.4444444444 | 0.8000000000 | | 6 | 2 | 20 + | 5 | 0.4444444444 | 0.8000000000 | | 7 | 2 | 20 | 5 | 0.4444444444 + | 0.8000000000 | | 8 | 2 | 10 | 5 | 0.4444444444 | 0.8000000000 | + | 9 | 4 | 20 | 9 | 0.8888888889 | 1.0000000000 | | 10 | 4 | 20 + | 9 | 0.8888888889 | 1.0000000000 | +----+------+------+------+--------------+--------------+ + - sql: "select pk, a, b,\n percent_rank() over (order by pk) as pct_rank,\n\ + \ cume_dist() over (order by pk) as cume_dist\nfrom t1 order by pk;\n ..." + - name: CURDATE + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: CURDATE + args: [] + summary: CURDATE returns the current date as a value in 'YYYY-MM-DD' or YYYYMMDD + description: CURDATE returns the current date as a value in 'YYYY-MM-DD' or YYYYMMDD + format, depending on whether the function is used in a string or numeric context. + CURRENT_DATE and CURRENT_DATE() are synonyms. + examples: + - sql: SELECT CURDATE(); + result: +------------+ | CURDATE() | +------------+ | 2019-03-05 | +------------+ + - sql: 'In a numeric context (note this is not performing date calculations):' + result: SELECT CURDATE() +0; +--------------+ | CURDATE() +0 | +--------------+ + | 20190305 | +--------------+ + - sql: 'Data calculation:' + result: SELECT CURDATE() - INTERVAL 5 DAY; +----------------------------+ | + CURDATE() - INTERVAL 5 DAY | +----------------------------+ | 2019-02-28 | + +----------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/curdate/' + - name: CURRENT_DATE + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: CURRENT_DATE + args: [] + summary: CURRENT_DATE and CURRENT_DATE() are synonyms for CURDATE(). + description: 'CURRENT_DATE and CURRENT_DATE() are synonyms for CURDATE(). URL: + https://mariadb.com/kb/en/current_date/' + examples: [] + - name: CURRENT_ROLE + category_id: information + category_label: Information Functions + tags: + - information + aliases: [] + signature: + display: CURRENT_ROLE + args: [] + summary: Returns the current role name. + description: Returns the current role name. This determines your access privileges. + The return value is a string in the utf8 character set. If there is no current + role, NULL is returned. The output of SELECT CURRENT_ROLE is equivalent to the + contents of the ENABLED_ROLES Information Schema table. USER() returns the combination + of user and host used to login. CURRENT_USER() returns the account used to determine + current connection's privileges. Statements using the CURRENT_ROLE function + are not safe for statement-based replication. + examples: + - sql: SELECT CURRENT_ROLE; + result: +--------------+ | CURRENT_ROLE | +--------------+ | NULL | + +--------------+ + - sql: SET ROLE staff; + result: SELECT CURRENT_ROLE; +--------------+ | CURRENT_ROLE | +--------------+ + | staff | +--------------+ + - sql: 'URL: https://mariadb.com/kb/en/current_role/' + - name: CURRENT_TIME + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: CURRENT_TIME([precision]) + args: + - name: precision + optional: true + type: any + summary: CURRENT_TIME and CURRENT_TIME() are synonyms for CURTIME(). + description: 'CURRENT_TIME and CURRENT_TIME() are synonyms for CURTIME(). URL: + https://mariadb.com/kb/en/current_time/' + examples: [] + - name: CURRENT_TIMESTAMP + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: CURRENT_TIMESTAMP([precision]) + args: + - name: precision + optional: true + type: any + summary: CURRENT_TIMESTAMP and CURRENT_TIMESTAMP() are synonyms for NOW(). + description: 'CURRENT_TIMESTAMP and CURRENT_TIMESTAMP() are synonyms for NOW(). + URL: https://mariadb.com/kb/en/current_timestamp/' + examples: [] + - name: CURRENT_USER + category_id: information + category_label: Information Functions + tags: + - information + aliases: [] + signature: + display: CURRENT_USER + args: [] + summary: Returns the user name and host name combination for the MariaDB account + that + description: Returns the user name and host name combination for the MariaDB account + that the server used to authenticate the current client. This account determines + your access privileges. The return value is a string in the utf8 character set. + The value of CURRENT_USER() can differ from the value of USER(). CURRENT_ROLE() + returns the current active role. Statements using the CURRENT_USER function + are not safe for statement-based replication. + examples: + - sql: shell> mysql --user="anonymous" + result: select user(),current_user(); +---------------------+----------------+ + | user() | current_user() | +---------------------+----------------+ + | anonymous@localhost | @localhost | +---------------------+----------------+ + - sql: When calling CURRENT_USER() in a stored procedure, it returns the owner + of the stored procedure, as defined with DEFINER. + result: 'URL: https://mariadb.com/kb/en/current_user/' + - name: CURTIME + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: CURTIME([precision]) + args: + - name: precision + optional: true + type: any + summary: Returns the current time as a value in 'HH:MM:SS' or HHMMSS. + description: Returns the current time as a value in 'HH:MM:SS' or HHMMSS.uuuuuu + format, depending on whether the function is used in a string or numeric context. + The value is expressed in the current time zone. The optional precision determines + the microsecond precision. See Microseconds in MariaDB. + examples: + - sql: SELECT CURTIME(); + result: +-----------+ | CURTIME() | +-----------+ | 12:45:39 | +-----------+ + - sql: SELECT CURTIME() + 0; + result: +---------------+ | CURTIME() + 0 | +---------------+ | 124545.000000 + | +---------------+ + - sql: 'With precision:' + result: SELECT CURTIME(2); +-------------+ | CURTIME(2) | +-------------+ | + 09:49:08.09 | +-------------+ + - sql: 'URL: https://mariadb.com/kb/en/curtime/' + - name: DATABASE + category_id: information + category_label: Information Functions + tags: + - information + aliases: [] + signature: + display: DATABASE + args: [] + summary: Returns the default (current) database name as a string in the utf8 character + description: Returns the default (current) database name as a string in the utf8 + character set. If there is no default database, DATABASE() returns NULL. Within + a stored routine, the default database is the database that the routine is associated + with, which is not necessarily the same as the database that is the default + in the calling context. SCHEMA() is a synonym for DATABASE(). To select a default + database, the USE statement can be run. Another way to set the default database + is specifying its name at mariadb command line client startup. + examples: + - sql: SELECT DATABASE(); + result: +------------+ | DATABASE() | +------------+ | NULL | +------------+ + - sql: USE test; Database changed + result: SELECT DATABASE(); +------------+ | DATABASE() | +------------+ | test | + +------------+ + - sql: 'URL: https://mariadb.com/kb/en/database/' + - name: DATEDIFF + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: DATEDIFF(expr1,expr2) + args: + - name: expr1 + optional: false + type: any + - name: expr2 + optional: false + type: any + summary: "DATEDIFF() returns (expr1 \u2013 expr2) expressed as a value in days\ + \ from one date" + description: "DATEDIFF() returns (expr1 \u2013 expr2) expressed as a value in\ + \ days from one date\nto the other. expr1 and expr2 are date or date-and-time\ + \ expressions. Only the\ndate parts of the values are used in the calculation." + examples: + - sql: SELECT DATEDIFF('2007-12-31 23:59:59','2007-12-30'); + result: +----------------------------------------------+ | DATEDIFF('2007-12-31 + 23:59:59','2007-12-30') | +----------------------------------------------+ + | 1 | +----------------------------------------------+ + - sql: SELECT DATEDIFF('2010-11-30 23:59:59','2010-12-31'); + result: +----------------------------------------------+ | DATEDIFF('2010-11-30 + 23:59:59','2010-12-31') | +----------------------------------------------+ + | -31 | +----------------------------------------------+ + - sql: "CREATE TABLE t1 (d DATETIME);\nINSERT INTO t1 VALUES\n (\"2007-01-30\ + \ 21:31:07\"),\n (\"1983-10-15 06:42:51\"),\n (\"2011-04-21 12:34:56\"),\n\ + \ (\"2011-10-30 06:31:41\"),\n (\"2011-01-30 14:03:25\"),\n (\"2004-10-07\ + \ 11:19:34\");" + result: SELECT NOW(); +---------------------+ | NOW() | +---------------------+ + | 2011-05-23 10:56:05 | +---------------------+ + - sql: SELECT d, DATEDIFF(NOW(),d) FROM t1; + result: +---------------------+-------------------+ | d | + DATEDIFF(NOW(),d) | +---------------------+-------------------+ | 2007-01-30 + 21:31:07 | 1574 | | 1983-10-15 06:42:51 | 10082 | + | 2011-04-21 12:34:56 | 32 | | 2011-10-30 06:31:41 | -160 + | | 2011-01-30 14:03:25 | 113 | | 2004-10-07 11:19:34 | 2419 + | +---------------------+-------------------+ + - sql: 'URL: https://mariadb.com/kb/en/datediff/' + - name: DATETIME + category_id: data_types + category_label: Data Types + tags: + - data_types + aliases: [] + signature: + display: DATETIME(microsecond precision) + args: + - name: microsecond precision + optional: false + type: any + summary: A date and time combination. + description: "A date and time combination.\n\nMariaDB displays DATETIME values\ + \ in 'YYYY-MM-DD HH:MM:SS.ffffff' format, but\nallows assignment of values to\ + \ DATETIME columns using either strings or\nnumbers. For details, see date and\ + \ time literals.\n\nDATETIME columns also accept CURRENT_TIMESTAMP as the default\ + \ value.\n\nMariaDB 10.1.2 introduced the --mysql56-temporal-format option,\ + \ on by default,\nwhich allows MariaDB to store DATETMEs using the same low-level\ + \ format MySQL\n5.6 uses. For more information, see Internal Format, below.\n\ + \nFor storage requirements, see Data Type Storage Requirements.\n\nSupported\ + \ Values\n----------------\n\nMariaDB stores values that use the DATETIME data\ + \ type in a format that\nsupports values between 1000-01-01 00:00:00.000000\ + \ and 9999-12-31\n23:59:59.999999.\n\nMariaDB can also store microseconds with\ + \ a precision between 0 and 6. If no\nmicrosecond precision is specified, then\ + \ 0 is used by default.\n\nMariaDB also supports '0000-00-00' as a special zero-date\ + \ value, unless\nNO_ZERO_DATE is specified in the SQL_MODE. Similarly, individual\ + \ components of\na date can be set to 0 (for example: '2015-00-12'), unless\ + \ NO_ZERO_IN_DATE is\nspecified in the SQL_MODE. In many cases, the result of\ + \ en expression\ninvolving a zero-date, or a date with zero-parts, is NULL.\ + \ If the\nALLOW_INVALID_DATES SQL_MODE is enabled, if the day part is in the\ + \ range\nbetween 1 and 31, the date does not produce any error, even for months\ + \ that\nhave less than 31 days.\n\nOracle Mode\n-----------\n\nMariaDB starting\ + \ with 10.3\n--------------------------\nIn Oracle mode from MariaDB 10.3, DATE\ + \ with a time portion is a synonym for\nDATETIME. See also mariadb_schema.\n\ + \nInternal Format\n---------------\n\nIn MariaDB 10.1.2 a new temporal format\ + \ was introduced from MySQL 5.6 that\nalters how the TIME, DATETIME and TIMESTAMP\ + \ columns operate at lower levels.\nThese changes allow these temporal data\ + \ types to have fractional parts and\nnegative values. You can disable this\ + \ feature using the\nmysql56_temporal_format system variable.\n\n ..." + examples: [] + - name: DATE_ADD + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: DATE_ADD(date,INTERVAL expr unit) + args: + - name: date + optional: false + type: any + - name: INTERVAL expr unit + optional: false + type: any + summary: Performs date arithmetic. + description: Performs date arithmetic. The date argument specifies the starting + date or datetime value. expr is an expression specifying the interval value + to be added or subtracted from the starting date. expr is a string; it may start + with a "-" for negative intervals. unit is a keyword indicating the units in + which the expression should be interpreted. See Date and Time Units for a complete + list of permitted units. + examples: + - sql: SELECT '2008-12-31 23:59:59' + INTERVAL 1 SECOND; + result: +-------------------------------------------+ | '2008-12-31 23:59:59' + + INTERVAL 1 SECOND | +-------------------------------------------+ | 2009-01-01 + 00:00:00 | +-------------------------------------------+ + - sql: SELECT INTERVAL 1 DAY + '2008-12-31'; + result: +-------------------------------+ | INTERVAL 1 DAY + '2008-12-31' | + +-------------------------------+ | 2009-01-01 | +-------------------------------+ + - sql: SELECT '2005-01-01' - INTERVAL 1 SECOND; + result: +----------------------------------+ | '2005-01-01' - INTERVAL 1 SECOND + | +----------------------------------+ | 2004-12-31 23:59:59 | + +----------------------------------+ + - sql: SELECT DATE_ADD('2000-12-31 23:59:59', INTERVAL 1 SECOND); + result: +----------------------------------------------------+ | DATE_ADD('2000-12-31 + 23:59:59', INTERVAL 1 SECOND) | +----------------------------------------------------+ + | 2001-01-01 00:00:00 | +----------------------------------------------------+ + - sql: SELECT DATE_ADD('2010-12-31 23:59:59', INTERVAL 1 DAY); + result: +-------------------------------------------------+ | DATE_ADD('2010-12-31 + 23:59:59', INTERVAL 1 DAY) | +-------------------------------------------------+ + | 2011-01-01 23:59:59 | +-------------------------------------------------+ + - sql: SELECT DATE_ADD('2100-12-31 23:59:59', INTERVAL '1:1' MINUTE_SECOND); + result: +---------------------------------------------------------------+ | + DATE_ADD('2100-12-31 23:59:59', INTERVAL '1:1' MINUTE_SECOND) | +---------------------------------------------------------------+ + | 2101-01-01 00:01:00 | + - name: DATE_FORMAT + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: DATE_FORMAT(date, format[, locale]) + args: + - name: date + optional: false + type: any + - name: format[ + optional: false + type: any + - name: locale] + optional: false + type: any + summary: Formats the date value according to the format string. + description: "Formats the date value according to the format string.\n\nThe language\ + \ used for the names is controlled by the value of the\nlc_time_names system\ + \ variable. See server locale for more on the supported\nlocales.\n\nThe options\ + \ that can be used by DATE_FORMAT(), as well as its inverse\nSTR_TO_DATE() and\ + \ the FROM_UNIXTIME() function, are:\n\n+---------------------------+------------------------------------------------+\n\ + | Option | Description \ + \ |\n+---------------------------+------------------------------------------------+\n\ + | %a | Short weekday name in current locale \ + \ |\n| | (Variable lc_time_names). \ + \ |\n+---------------------------+------------------------------------------------+\n\ + | %b | Short form month name in current locale. For \ + \ |\n| | locale en_US this is one of: \ + \ |\n| | Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov\ + \ |\n| | or Dec. \ + \ |\n+---------------------------+------------------------------------------------+\n\ + | %c | Month with 1 or 2 digits. \ + \ |\n+---------------------------+------------------------------------------------+\n\ + | %D | Day with English suffix 'th', 'nd', 'st' or \ + \ |\n| | 'rd''. (1st, 2nd, 3rd...). \ + \ |\n+---------------------------+------------------------------------------------+\n\ + | %d | Day with 2 digits. \ + \ |\n+---------------------------+------------------------------------------------+\n\ + | %e | Day with 1 or 2 digits. \ + \ |\n+---------------------------+------------------------------------------------+\n\ + | %f | Microseconds 6 digits. \ + \ |\n+---------------------------+------------------------------------------------+\n\ + | %H | Hour with 2 digits between 00-23. \ + \ |\n+---------------------------+------------------------------------------------+\n\ + | %h | Hour with 2 digits between 01-12. \ + \ |\n+---------------------------+------------------------------------------------+\n\ + | %I | Hour with 2 digits between 01-12. \ + \ |\n+---------------------------+------------------------------------------------+\n\ + | %i | Minute with 2 digits. \ + \ |\n+---------------------------+------------------------------------------------+\n\ + | %j | Day of the year (001-366) \ + \ |\n+---------------------------+------------------------------------------------+\n\ + | %k | Hour with 1 digits between 0-23. \ + \ |\n+---------------------------+------------------------------------------------+\n\ + | %l | Hour with 1 digits between 1-12. \ + \ |\n+---------------------------+------------------------------------------------+\n\ + | %M | Full month name in current locale (Variable \ + \ |\n| | lc_time_names). \ + \ |\n+---------------------------+------------------------------------------------+\n\ + | %m | Month with 2 digits. \ + \ |\n+---------------------------+------------------------------------------------+\n\ + \ ..." + examples: [] + - name: DATE_SUB + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: DATE_SUB(date,INTERVAL expr unit) + args: + - name: date + optional: false + type: any + - name: INTERVAL expr unit + optional: false + type: any + summary: Performs date arithmetic. + description: Performs date arithmetic. The date argument specifies the starting + date or datetime value. expr is an expression specifying the interval value + to be added or subtracted from the starting date. expr is a string; it may start + with a "-" for negative intervals. unit is a keyword indicating the units in + which the expression should be interpreted. See Date and Time Units for a complete + list of permitted units. See also DATE_ADD(). + examples: + - sql: SELECT DATE_SUB('1998-01-02', INTERVAL 31 DAY); + result: +-----------------------------------------+ | DATE_SUB('1998-01-02', + INTERVAL 31 DAY) | +-----------------------------------------+ | 1997-12-02 | + +-----------------------------------------+ + - sql: SELECT DATE_SUB('2005-01-01 00:00:00', INTERVAL '1 1:1:1' DAY_SECOND); + result: +----------------------------------------------------------------+ | + DATE_SUB('2005-01-01 00:00:00', INTERVAL '1 1:1:1' DAY_SECOND) | +----------------------------------------------------------------+ + | 2004-12-30 22:58:59 | +----------------------------------------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/date_sub/' + - name: DAY + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: DAY(date) + args: + - name: date + optional: false + type: any + summary: DAY() is a synonym for DAYOFMONTH(). + description: 'DAY() is a synonym for DAYOFMONTH(). URL: https://mariadb.com/kb/en/day/' + examples: [] + - name: DAYNAME + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: DAYNAME(date) + args: + - name: date + optional: false + type: any + summary: Returns the name of the weekday for date. + description: Returns the name of the weekday for date. The language used for the + name is controlled by the value of the lc_time_names system variable. See server + locale for more on the supported locales. + examples: + - sql: SELECT DAYNAME('2007-02-03'); + result: +-----------------------+ | DAYNAME('2007-02-03') | +-----------------------+ + | Saturday | +-----------------------+ + - sql: "CREATE TABLE t1 (d DATETIME);\nINSERT INTO t1 VALUES\n (\"2007-01-30\ + \ 21:31:07\"),\n (\"1983-10-15 06:42:51\"),\n (\"2011-04-21 12:34:56\"),\n\ + \ (\"2011-10-30 06:31:41\"),\n (\"2011-01-30 14:03:25\"),\n (\"2004-10-07\ + \ 11:19:34\");" + result: SELECT d, DAYNAME(d) FROM t1; +---------------------+------------+ | + d | DAYNAME(d) | +---------------------+------------+ | + 2007-01-30 21:31:07 | Tuesday | | 1983-10-15 06:42:51 | Saturday | | + 2011-04-21 12:34:56 | Thursday | | 2011-10-30 06:31:41 | Sunday | | + 2011-01-30 14:03:25 | Sunday | | 2004-10-07 11:19:34 | Thursday | +---------------------+------------+ + - sql: 'Changing the locale:' + result: SET lc_time_names = 'fr_CA'; + - sql: SELECT DAYNAME('2013-04-01'); + result: +-----------------------+ | DAYNAME('2013-04-01') | +-----------------------+ + | lundi | +-----------------------+ + - sql: 'URL: https://mariadb.com/kb/en/dayname/' + - name: DAYOFMONTH + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: DAYOFMONTH(date) + args: + - name: date + optional: false + type: any + summary: Returns the day of the month for date, in the range 1 to 31, or 0 for + dates + description: Returns the day of the month for date, in the range 1 to 31, or 0 + for dates such as '0000-00-00' or '2008-00-00' which have a zero day part. DAY() + is a synonym. + examples: + - sql: SELECT DAYOFMONTH('2007-02-03'); + result: +--------------------------+ | DAYOFMONTH('2007-02-03') | +--------------------------+ + | 3 | +--------------------------+ + - sql: "CREATE TABLE t1 (d DATETIME);\nINSERT INTO t1 VALUES\n (\"2007-01-30\ + \ 21:31:07\"),\n (\"1983-10-15 06:42:51\"),\n (\"2011-04-21 12:34:56\"),\n\ + \ (\"2011-10-30 06:31:41\"),\n (\"2011-01-30 14:03:25\"),\n (\"2004-10-07\ + \ 11:19:34\");" + result: SELECT d FROM t1 where DAYOFMONTH(d) = 30; +---------------------+ | + d | +---------------------+ | 2007-01-30 21:31:07 | | 2011-10-30 + 06:31:41 | | 2011-01-30 14:03:25 | +---------------------+ + - sql: 'URL: https://mariadb.com/kb/en/dayofmonth/' + - name: DAYOFWEEK + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: DAYOFWEEK(date) + args: + - name: date + optional: false + type: any + summary: Returns the day of the week index for the date (1 = Sunday, 2 = Monday, + . + description: Returns the day of the week index for the date (1 = Sunday, 2 = Monday, + ..., 7 = Saturday). These index values correspond to the ODBC standard. This + contrasts with WEEKDAY() which follows a different index numbering (0 = Monday, + 1 = Tuesday, ... 6 = Sunday). + examples: + - sql: SELECT DAYOFWEEK('2007-02-03'); + result: +-------------------------+ | DAYOFWEEK('2007-02-03') | +-------------------------+ + | 7 | +-------------------------+ + - sql: "CREATE TABLE t1 (d DATETIME);\nINSERT INTO t1 VALUES\n (\"2007-01-30\ + \ 21:31:07\"),\n (\"1983-10-15 06:42:51\"),\n (\"2011-04-21 12:34:56\"),\n\ + \ (\"2011-10-30 06:31:41\"),\n (\"2011-01-30 14:03:25\"),\n (\"2004-10-07\ + \ 11:19:34\");" + result: SELECT d, DAYNAME(d), DAYOFWEEK(d), WEEKDAY(d) from t1; +---------------------+------------+--------------+------------+ + | d | DAYNAME(d) | DAYOFWEEK(d) | WEEKDAY(d) | +---------------------+------------+--------------+------------+ + | 2007-01-30 21:31:07 | Tuesday | 3 | 1 | | 1983-10-15 + 06:42:51 | Saturday | 7 | 5 | | 2011-04-21 12:34:56 + | Thursday | 5 | 3 | | 2011-10-30 06:31:41 | Sunday | 1 + | 6 | | 2011-01-30 14:03:25 | Sunday | 1 | 6 + | | 2004-10-07 11:19:34 | Thursday | 5 | 3 | +---------------------+------------+--------------+------------+ + - sql: 'URL: https://mariadb.com/kb/en/dayofweek/' + - name: DAYOFYEAR + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: DAYOFYEAR(date) + args: + - name: date + optional: false + type: any + summary: Returns the day of the year for date, in the range 1 to 366. + description: Returns the day of the year for date, in the range 1 to 366. + examples: + - sql: SELECT DAYOFYEAR('2018-02-16'); + result: +-------------------------+ | DAYOFYEAR('2018-02-16') | +-------------------------+ + | 47 | +-------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/dayofyear/' + - name: DECIMAL + category_id: data_types + category_label: Data Types + tags: + - data_types + aliases: [] + signature: + display: DECIMAL(M[,D]) + args: + - name: M[ + optional: false + type: any + - name: D] + optional: false + type: any + summary: A packed "exact" fixed-point number. + description: "A packed \"exact\" fixed-point number. M is the total number of\ + \ digits (the\nprecision) and D is the number of digits after the decimal point\ + \ (the scale).\n\n* The decimal point and (for negative numbers) the \"-\" sign\ + \ are not\ncounted in M. \n* If D is 0, values have no decimal point or fractional\n\ + part and on INSERT the value will be rounded to the nearest DECIMAL. \n* The\ + \ maximum number of digits (M) for DECIMAL is 65. \n* The maximum number of\ + \ supported decimals (D) is 30 before MariadB 10.2.1 and\n38 afterwards. \n\ + * If D is omitted, the default is 0. If M is omitted, the default is 10.\n\n\ + UNSIGNED, if specified, disallows negative values.\n\nZEROFILL, if specified,\ + \ pads the number with zeros, up to the total number of\ndigits specified by\ + \ M.\n\nAll basic calculations (+, -, *, /) with DECIMAL columns are done with\ + \ a\nprecision of 65 digits.\n\nFor more details on the attributes, see Numeric\ + \ Data Type Overview.\n\nDEC, NUMERIC and FIXED are synonyms, as well as NUMBER\ + \ in Oracle mode from\nMariaDB 10.3." + examples: + - sql: CREATE TABLE t1 (d DECIMAL UNSIGNED ZEROFILL); + result: INSERT INTO t1 VALUES (1),(2),(3),(4.0),(5.2),(5.7); + - sql: 'Records: 6 Duplicates: 0 Warnings: 2' + result: 'Note (Code 1265): Data truncated for column ''d'' at row 5' + - sql: SELECT * FROM t1; + result: +------------+ | d | +------------+ | 0000000001 | | 0000000002 + | | 0000000003 | | 0000000004 | | 0000000005 | | 0000000006 | +------------+ + - sql: "With strict_mode set, the default from MariaDB 10.2.4:\n ..." + - name: DECODE + category_id: encryption + category_label: Encryption Functions + tags: + - encryption + aliases: [] + signature: + display: DECODE(crypt_str,pass_str) + args: + - name: crypt_str + optional: false + type: any + - name: pass_str + optional: false + type: any + summary: In the default mode, DECODE decrypts the encrypted string crypt_str using + description: In the default mode, DECODE decrypts the encrypted string crypt_str + using pass_str as the password. crypt_str should be a string returned from ENCODE(). + The resulting string will be the original string only if pass_str is the same. + In Oracle mode from MariaDB 10.3.2, DECODE compares expr to the search expressions, + in order. If it finds a match, the corresponding result expression is returned. + If no matches are found, the default expression is returned, or NULL if no default + is provided. NULLs are treated as equivalent. DECODE_ORACLE is a synonym for + the Oracle-mode version of the function, and is available in all modes. + examples: + - sql: 'From MariaDB 10.3.2:' + result: SELECT DECODE_ORACLE(2+1,3*1,'found1',3*2,'found2','default'); +--------------------------------------------------------+ + | DECODE_ORACLE(2+1,3*1,'found1',3*2,'found2','default') | +--------------------------------------------------------+ + | found1 | +--------------------------------------------------------+ + - sql: SELECT DECODE_ORACLE(2+4,3*1,'found1',3*2,'found2','default'); + result: +--------------------------------------------------------+ | DECODE_ORACLE(2+4,3*1,'found1',3*2,'found2','default') + | +--------------------------------------------------------+ | found2 | + +--------------------------------------------------------+ + - sql: SELECT DECODE_ORACLE(2+2,3*1,'found1',3*2,'found2','default'); + result: +--------------------------------------------------------+ | DECODE_ORACLE(2+2,3*1,'found1',3*2,'found2','default') + | +--------------------------------------------------------+ | default | + +--------------------------------------------------------+ + - sql: 'Nulls are treated as equivalent:' + result: SELECT DECODE_ORACLE(NULL,NULL,'Nulls are equivalent','Nulls are not + - sql: +----------------------------------------------------------------------------+ + result: '| DECODE_ORACLE(NULL,NULL,''Nulls are equivalent'',''Nulls are not + equivalent'') | +----------------------------------------------------------------------------+ + | Nulls are equivalent | + +----------------------------------------------------------------------------+' + - sql: 'URL: https://mariadb.com/kb/en/decode/' + - name: DECODE_HISTOGRAM + category_id: information + category_label: Information Functions + tags: + - information + aliases: [] + signature: + display: DECODE_HISTOGRAM(hist_type,histogram) + args: + - name: hist_type + optional: false + type: any + - name: histogram + optional: false + type: any + summary: Returns a string of comma separated numeric values corresponding to a + description: Returns a string of comma separated numeric values corresponding + to a probability distribution represented by the histogram of type hist_type + (SINGLE_PREC_HB or DOUBLE_PREC_HB). The hist_type and histogram would be commonly + used from the mysql.column_stats table. See Histogram Based Statistics for details. + examples: + - sql: "CREATE TABLE origin (\n i INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY\ + \ KEY,\n v INT UNSIGNED NOT NULL\n);" + result: INSERT INTO origin(v) VALUES + - sql: "(30),(40),(50),(60),(70),(80),\n (90),(100),(200),(400),(800);" + result: SET histogram_size=10,histogram_type=SINGLE_PREC_HB; + - sql: ANALYZE TABLE origin PERSISTENT FOR ALL; + result: +-------------+---------+----------+-----------------------------------------+ + | Table | Op | Msg_type | Msg_text | + +-------------+---------+----------+-----------------------------------------+ + | test.origin | analyze | status | Engine-independent statistics collected + | | test.origin | analyze | status | OK | + +-------------+---------+----------+-----------------------------------------+ + - sql: "SELECT db_name,table_name,column_name,hist_type,\n hex(histogram),decode_histogram(hist_type,histogram)\n\ + \ FROM mysql.column_stats WHERE db_name='test' and table_name='origin';" + result: +---------+------------+-------------+----------------+----------------------+- + - sql: '| db_name | table_name | column_name | hist_type | hex(histogram) | + decode_histogram(hist_type,histogram) |' + result: +---------+------------+-------------+----------------+----------------------+- + - sql: '| test | origin | i | SINGLE_PREC_HB | 0F2D3C5A7887A5C3D2F0 + | 0.059,0.118,0.059,0.118,0.118,0.059,0.118,0.118,0.059,0.118,0.059 |' + result: '| test | origin | v | SINGLE_PREC_HB | 000001060C0F161C1F7F + |' + - sql: +---------+------------+-------------+----------------+----------------------+- + -----------------------------------------------------------------+ + result: SET histogram_size=20,histogram_type=DOUBLE_PREC_HB; + - sql: ANALYZE TABLE origin PERSISTENT FOR ALL; + result: +-------------+---------+----------+-----------------------------------------+ + - name: DEGREES + category_id: numeric + category_label: Numeric Functions + tags: + - numeric + aliases: [] + signature: + display: DEGREES(X) + args: + - name: X + optional: false + type: any + summary: Returns the argument X, converted from radians to degrees. + description: Returns the argument X, converted from radians to degrees. This is + the converse of the RADIANS() function. + examples: + - sql: SELECT DEGREES(PI()); + result: +---------------+ | DEGREES(PI()) | +---------------+ | 180 + | +---------------+ + - sql: SELECT DEGREES(PI() / 2); + result: +-------------------+ | DEGREES(PI() / 2) | +-------------------+ | 90 + | +-------------------+ + - sql: SELECT DEGREES(45); + result: +-----------------+ | DEGREES(45) | +-----------------+ | 2578.3100780887 + | +-----------------+ + - sql: 'URL: https://mariadb.com/kb/en/degrees/' + - name: DENSE_RANK + category_id: window + category_label: Window Functions + tags: + - window + aliases: [] + signature: + display: DENSE_RANK + args: [] + summary: DENSE_RANK() is a window function that displays the number of a given + row, + description: DENSE_RANK() is a window function that displays the number of a given + row, starting at one and following the ORDER BY sequence of the window function, + with identical values receiving the same result. Unlike the RANK() function, + there are no skipped values if the preceding results are identical. It is also + similar to the ROW_NUMBER() function except that in that function, identical + values will receive a different row number for each result. + examples: + - sql: 'The distinction between DENSE_RANK(), RANK() and ROW_NUMBER():' + result: CREATE TABLE student(course VARCHAR(10), mark int, name varchar(10)); + - sql: "INSERT INTO student VALUES\n ('Maths', 60, 'Thulile'),\n ('Maths', 60,\ + \ 'Pritha'),\n ('Maths', 70, 'Voitto'),\n ('Maths', 55, 'Chun'),\n ('Biology',\ + \ 60, 'Bilal'),\n ('Biology', 70, 'Roger');" + result: SELECT + - sql: "DENSE_RANK() OVER (PARTITION BY course ORDER BY mark DESC) AS dense_rank,\n\ + \ ROW_NUMBER() OVER (PARTITION BY course ORDER BY mark DESC) AS row_num,\n\ + \ course, mark, name\nFROM student ORDER BY course, mark DESC;" + result: +------+------------+---------+---------+------+---------+ | rank | + dense_rank | row_num | course | mark | name | +------+------------+---------+---------+------+---------+ + | 1 | 1 | 1 | Biology | 70 | Roger | | 2 | 2 + | 2 | Biology | 60 | Bilal | | 1 | 1 | 1 | Maths | 70 + | Voitto | | 2 | 2 | 2 | Maths | 60 | Thulile | | 2 + | 2 | 3 | Maths | 60 | Pritha | | 4 | 3 | 4 + | Maths | 55 | Chun | +------+------------+---------+---------+------+---------+ + - sql: 'URL: https://mariadb.com/kb/en/dense_rank/' + - name: DES_DECRYPT + category_id: encryption + category_label: Encryption Functions + tags: + - encryption + aliases: [] + signature: + display: DES_DECRYPT(crypt_str[,key_str]) + args: + - name: crypt_str[ + optional: false + type: any + - name: key_str] + optional: false + type: any + summary: Decrypts a string encrypted with DES_ENCRYPT(). + description: 'Decrypts a string encrypted with DES_ENCRYPT(). If an error occurs, + this function returns NULL. This function works only if MariaDB has been configured + with TLS support. If no key_str argument is given, DES_DECRYPT() examines the + first byte of the encrypted string to determine the DES key number that was + used to encrypt the original string, and then reads the key from the DES key + file to decrypt the message. For this to work, the user must have the SUPER + privilege. The key file can be specified with the --des-key-file server option. + If you pass this function a key_str argument, that string is used as the key + for decrypting the message. If the crypt_str argument does not appear to be + an encrypted string, MariaDB returns the given crypt_str. URL: https://mariadb.com/kb/en/des_decrypt/' + examples: [] + - name: DES_ENCRYPT + category_id: encryption + category_label: Encryption Functions + tags: + - encryption + aliases: [] + signature: + display: DES_ENCRYPT(str[,{key_num|key_str}]) + args: + - name: str[ + optional: false + type: any + - name: '{key_num|key_str}]' + optional: false + type: any + summary: Encrypts the string with the given key using the Triple-DES algorithm. + description: 'Encrypts the string with the given key using the Triple-DES algorithm. + This function works only if MariaDB has been configured with TLS support. The + encryption key to use is chosen based on the second argument to DES_ENCRYPT(), + if one was given. With no argument, the first key from the DES key file is used. + With a key_num argument, the given key number (0-9) from the DES key file is + used. With a key_str argument, the given key string is used to encrypt str. + The key file can be specified with the --des-key-file server option. The return + string is a binary string where the first character is CHAR(128 | key_num). + If an error occurs, DES_ENCRYPT() returns NULL. The 128 is added to make it + easier to recognize an encrypted key. If you use a string key, key_num is 127. + The string length for the result is given by this formula: new_len = orig_len + + (8 - (orig_len % 8)) + 1 Each line in the DES key file has the following format: + key_num des_key_str Each key_num value must be a number in the range from 0 + to 9. Lines in the file may be in any order. des_key_str is the string that + is used to encrypt the message. There should be at least one space between the + number and the key. The first key is the default key that is used if you do + not specify any key argument to DES_ENCRYPT(). You can tell MariaDB to read + new key values from the key file with the FLUSH DES_KEY_FILE statement. This + requires the RELOAD privilege. One benefit of having a set of default keys is + that it gives applications a way to check for the existence of encrypted column + values, without giving the end user the right to decrypt those values.' + examples: + - sql: "SELECT customer_address FROM customer_table\n WHERE crypted_credit_card\ + \ = DES_ENCRYPT('credit_card_number');" + result: 'URL: https://mariadb.com/kb/en/des_encrypt/' + - name: DISJOINT + category_id: geometry_relations + category_label: Geometry Relations + tags: + - geometry_relations + aliases: [] + signature: + display: DISJOINT(g1,g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + summary: Returns 1 or 0 to indicate whether g1 is spatially disjoint from (does + not + description: 'Returns 1 or 0 to indicate whether g1 is spatially disjoint from + (does not intersect) g2. DISJOINT() tests the opposite relationship to INTERSECTS(). + DISJOINT() is based on the original MySQL implementation and uses object bounding + rectangles, while ST_DISJOINT() uses object shapes. URL: https://mariadb.com/kb/en/disjoint/' + examples: [] + - name: DOUBLE + category_id: data_types + category_label: Data Types + tags: + - data_types + aliases: [] + signature: + display: DOUBLE(M,D) + args: + - name: M + optional: false + type: any + - name: D + optional: false + type: any + summary: A normal-size (double-precision) floating-point number (see FLOAT for + a + description: 'A normal-size (double-precision) floating-point number (see FLOAT + for a single-precision floating-point number). Allowable values are: * -1.7976931348623157E+308 + to -2.2250738585072014E-308 * 0 * 2.2250738585072014E-308 to 1.7976931348623157E+308 + These are the theoretical limits, based on the IEEE standard. The actual range + might be slightly smaller depending on your hardware or operating system. M + is the total number of digits and D is the number of digits following the decimal + point. If M and D are omitted, values are stored to the limits allowed by the + hardware. A double-precision floating-point number is accurate to approximately + 15 decimal places. UNSIGNED, if specified, disallows negative values. ZEROFILL, + if specified, pads the number with zeros, up to the total number of digits specified + by M. REAL and DOUBLE PRECISION are synonyms, unless the REAL_AS_FLOAT SQL mode + is enabled, in which case REAL is a synonym for FLOAT rather than DOUBLE. See + Floating Point Accuracy for issues when using floating-point numbers. For more + details on the attributes, see Numeric Data Type Overview.' + examples: + - sql: CREATE TABLE t1 (d DOUBLE(5,0) zerofill); + result: INSERT INTO t1 VALUES (1),(2),(3),(4); + - sql: SELECT * FROM t1; + result: +-------+ | d | +-------+ | 00001 | | 00002 | | 00003 | | 00004 + | +-------+ + - sql: 'URL: https://mariadb.com/kb/en/double/' + - name: ELT + category_id: string + category_label: String Functions + tags: + - string + aliases: [] + signature: + display: ELT(N, str1[, str2, str3,...]) + args: + - name: N + optional: false + type: any + - name: str1[ + optional: false + type: any + - name: str2 + optional: false + type: any + - name: str3 + optional: false + type: any + - name: '...]' + optional: false + type: any + summary: Takes a numeric argument and a series of string arguments. + description: Takes a numeric argument and a series of string arguments. Returns + the string that corresponds to the given numeric position. For instance, it + returns str1 if N is 1, str2 if N is 2, and so on. If the numeric argument is + a FLOAT, MariaDB rounds it to the nearest INTEGER. If the numeric argument is + less than 1, greater than the total number of arguments, or not a number, ELT() + returns NULL. It must have at least two arguments. It is complementary to the + FIELD() function. + examples: + - sql: SELECT ELT(1, 'ej', 'Heja', 'hej', 'foo'); + result: +------------------------------------+ | ELT(1, 'ej', 'Heja', 'hej', + 'foo') | +------------------------------------+ | ej | + +------------------------------------+ + - sql: SELECT ELT(4, 'ej', 'Heja', 'hej', 'foo'); + result: +------------------------------------+ | ELT(4, 'ej', 'Heja', 'hej', + 'foo') | +------------------------------------+ | foo | + +------------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/elt/' + - name: ENCODE + category_id: encryption + category_label: Encryption Functions + tags: + - encryption + aliases: [] + signature: + display: ENCODE(str,pass_str) + args: + - name: str + optional: false + type: any + - name: pass_str + optional: false + type: any + summary: ENCODE is not considered cryptographically secure, and should not be + used for + description: ENCODE is not considered cryptographically secure, and should not + be used for password encryption. Encrypt str using pass_str as the password. + To decrypt the result, use DECODE(). The result is a binary string of the same + length as str. The strength of the encryption is based on how good the random + generator is. It is not recommended to rely on the encryption performed by the + ENCODE function. Using a salt value (changed when a password is updated) will + improve matters somewhat, but for storing passwords, consider a more cryptographically + secure function, such as SHA2(). + examples: + - sql: ENCODE('not so secret text', CONCAT('random_salt','password')) + result: 'URL: https://mariadb.com/kb/en/encode/' + - name: ENCRYPT + category_id: encryption + category_label: Encryption Functions + tags: + - encryption + aliases: [] + signature: + display: ENCRYPT(str[,salt]) + args: + - name: str[ + optional: false + type: any + - name: salt] + optional: false + type: any + summary: Encrypts a string using the Unix crypt() system call, returning an encrypted + description: Encrypts a string using the Unix crypt() system call, returning an + encrypted binary string. The salt argument should be a string with at least + two characters or the returned result will be NULL. If no salt argument is given, + a random value of sufficient length is used. It is not recommended to use ENCRYPT() + with utf16, utf32 or ucs2 multi-byte character sets because the crypt() system + call expects a string terminated with a zero byte. Note that the underlying + crypt() system call may have some limitations, such as ignoring all but the + first eight characters. If the have_crypt system variable is set to NO (because + the crypt() system call is not available), the ENCRYPT function will always + return NULL. + examples: + - sql: SELECT ENCRYPT('encrypt me'); + result: +-----------------------+ | ENCRYPT('encrypt me') | +-----------------------+ + | 4I5BsEx0lqTDk | +-----------------------+ + - sql: 'URL: https://mariadb.com/kb/en/encrypt/' + - name: ENUM + category_id: data_types + category_label: Data Types + tags: + - data_types + aliases: [] + signature: + display: ENUM('value1','value2',...) + args: + - name: '''value1''' + optional: false + type: any + - name: '''value2''' + optional: false + type: any + - name: '...' + optional: false + type: any + summary: An enumeration. + description: "An enumeration. A string object that can have only one value, chosen\ + \ from the\nlist of values 'value1', 'value2', ..., NULL or the special '' error\ + \ value. In\ntheory, an ENUM column can have a maximum of 65,535 distinct values;\ + \ in\npractice, the real maximum depends on many factors. ENUM values are\n\ + represented internally as integers.\n\nTrailing spaces are automatically stripped\ + \ from ENUM values on table creation.\n\nENUMs require relatively little storage\ + \ space compared to strings, either one\nor two bytes depending on the number\ + \ of enumeration values.\n\nNULL and empty values\n---------------------\n\n\ + An ENUM can also contain NULL and empty values. If the ENUM column is declared\n\ + to permit NULL values, NULL becomes a valid value, as well as the default\n\ + value (see below). If strict SQL Mode is not enabled, and an invalid value is\n\ + inserted into an ENUM, a special empty string, with an index value of zero\n\ + (see Numeric index, below), is inserted, with a warning. This may be\nconfusing,\ + \ because the empty string is also a possible value, and the only\ndifference\ + \ if that is this case its index is not 0. Inserting will fail with\nan error\ + \ if strict mode is active.\n\nIf a DEFAULT clause is missing, the default value\ + \ will be:\n\n* NULL if the column is nullable;\n* otherwise, the first value\ + \ in the enumeration.\n\nNumeric index\n-------------\n\nENUM values are indexed\ + \ numerically in the order they are defined, and sorting\nwill be performed\ + \ in this numeric order. We suggest not using ENUM to store\nnumerals, as there\ + \ is little to no storage space benefit, and it is easy to\nconfuse the enum\ + \ integer with the enum numeral value by leaving out the quotes.\n\nAn ENUM\ + \ defined as ENUM('apple','orange','pear') would have the following\nindex values:\n\ + \n+--------------------------------------+--------------------------------------+\n\ + | Index | Value \ + \ |\n+--------------------------------------+--------------------------------------+\n\ + | NULL | NULL \ + \ |\n+--------------------------------------+--------------------------------------+\n\ + | 0 | '' \ + \ |\n+--------------------------------------+--------------------------------------+\n\ + | 1 | 'apple' \ + \ |\n+--------------------------------------+--------------------------------------+\n\ + | 2 | 'orange' \ + \ |\n+--------------------------------------+--------------------------------------+\n\ + \ ..." + examples: [] + - name: EQUALS + category_id: geometry_relations + category_label: Geometry Relations + tags: + - geometry_relations + aliases: [] + signature: + display: EQUALS(g1,g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + summary: Returns 1 or 0 to indicate whether g1 is spatially equal to g2. + description: 'Returns 1 or 0 to indicate whether g1 is spatially equal to g2. + EQUALS() is based on the original MySQL implementation and uses object bounding + rectangles, while ST_EQUALS() uses object shapes. From MariaDB 10.2.3, MBREQUALS + is a synonym for Equals. URL: https://mariadb.com/kb/en/equals/' + examples: [] + - name: EXCEPT + category_id: data_manipulation + category_label: Data Manipulation + tags: + - data_manipulation + aliases: [] + signature: + display: EXCEPT(SELECT c_name AS name, email FROM employees) + args: + - name: SELECT c_name AS name + optional: false + type: any + - name: email FROM employees + optional: false + type: any + summary: Difference between UNION, EXCEPT and INTERSECT. + description: "Difference between UNION, EXCEPT and INTERSECT. INTERSECT ALL and\ + \ EXCEPT ALL\nare available from MariaDB 10.5.0.\n\nCREATE TABLE seqs (i INT);\n\ + INSERT INTO seqs VALUES (1),(2),(2),(3),(3),(4),(5),(6);\n\nSELECT i FROM seqs\ + \ WHERE i <= 3 UNION SELECT i FROM seqs WHERE i>=3;\n+------+\n| i |\n+------+\n\ + | 1 |\n| 2 |\n| 3 |\n| 4 |\n| 5 |\n| 6 |\n+------+\n\nSELECT\ + \ i FROM seqs WHERE i <= 3 UNION ALL SELECT i FROM seqs WHERE i>=3;\n+------+\n\ + | i |\n+------+\n| 1 |\n| 2 |\n| 2 |\n| 3 |\n| 3 |\n| 3\ + \ |\n| 3 |\n| 4 |\n| 5 |\n| 6 |\n+------+\n\nSELECT i FROM seqs\ + \ WHERE i <= 3 EXCEPT SELECT i FROM seqs WHERE i>=3;\n+------+\n| i |\n+------+\n\ + | 1 |\n| 2 |\n+------+\n\nSELECT i FROM seqs WHERE i <= 3 EXCEPT ALL SELECT\ + \ i FROM seqs WHERE i>=3;\n+------+\n| i |\n+------+\n| 1 |\n| 2 |\n\ + | 2 |\n+------+\n ..." + examples: [] + - name: EXP + category_id: numeric + category_label: Numeric Functions + tags: + - numeric + aliases: [] + signature: + display: EXP(X) + args: + - name: X + optional: false + type: any + summary: Returns the value of e (the base of natural logarithms) raised to the + power of + description: Returns the value of e (the base of natural logarithms) raised to + the power of X. The inverse of this function is LOG() (using a single argument + only) or LN(). If X is NULL, this function returns NULL. + examples: + - sql: SELECT EXP(2); + result: +------------------+ | EXP(2) | +------------------+ | 7.38905609893065 + | +------------------+ + - sql: SELECT EXP(-2); + result: +--------------------+ | EXP(-2) | +--------------------+ + | 0.1353352832366127 | +--------------------+ + - sql: SELECT EXP(0); + result: +--------+ | EXP(0) | +--------+ | 1 | +--------+ + - sql: SELECT EXP(NULL); + result: +-----------+ | EXP(NULL) | +-----------+ | NULL | +-----------+ + - sql: 'URL: https://mariadb.com/kb/en/exp/' + - name: EXPORT_SET + category_id: string + category_label: String Functions + tags: + - string + aliases: [] + signature: + display: EXPORT_SET(bits, on, off[, separator[, number_of_bits]]) + args: + - name: bits + optional: false + type: any + - name: 'on' + optional: false + type: any + - name: off[ + optional: false + type: any + - name: separator[ + optional: false + type: any + - name: number_of_bits]] + optional: false + type: any + summary: Takes a minimum of three arguments. + description: Takes a minimum of three arguments. Returns a string where each bit + in the given bits argument is returned, with the string values given for on + and off. Bits are examined from right to left, (from low-order to high-order + bits). Strings are added to the result from left to right, separated by a separator + string (defaults as ','). You can optionally limit the number of bits the EXPORT_SET() + function examines using the number_of_bits option. If any of the arguments are + set as NULL, the function returns NULL. + examples: + - sql: SELECT EXPORT_SET(5,'Y','N',',',4); + result: +-----------------------------+ | EXPORT_SET(5,'Y','N',',',4) | +-----------------------------+ + | Y,N,Y,N | +-----------------------------+ + - sql: SELECT EXPORT_SET(6,'1','0',',',10); + result: +------------------------------+ | EXPORT_SET(6,'1','0',',',10) | +------------------------------+ + | 0,1,1,0,0,0,0,0,0,0 | +------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/export_set/' + - name: EXTRACT + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: EXTRACT(unit FROM date) + args: + - name: unit FROM date + optional: false + type: any + summary: The EXTRACT() function extracts the required unit from the date. + description: The EXTRACT() function extracts the required unit from the date. + See Date and Time Units for a complete list of permitted units. In MariaDB 10.0.7 + and MariaDB 5.5.35, EXTRACT (HOUR FROM ...) was changed to return a value from + 0 to 23, adhering to the SQL standard. Until MariaDB 10.0.6 and MariaDB 5.5.34, + and in all versions of MySQL at least as of MySQL 5.7, it could return a value + > 23. HOUR() is not a standard function, so continues to adhere to the old behaviour + inherited from MySQL. + examples: + - sql: SELECT EXTRACT(YEAR FROM '2009-07-02'); + result: +---------------------------------+ | EXTRACT(YEAR FROM '2009-07-02') + | +---------------------------------+ | 2009 | + +---------------------------------+ + - sql: SELECT EXTRACT(YEAR_MONTH FROM '2009-07-02 01:02:03'); + result: +------------------------------------------------+ | EXTRACT(YEAR_MONTH + FROM '2009-07-02 01:02:03') | +------------------------------------------------+ + | 200907 | +------------------------------------------------+ + - sql: SELECT EXTRACT(DAY_MINUTE FROM '2009-07-02 01:02:03'); + result: +------------------------------------------------+ | EXTRACT(DAY_MINUTE + FROM '2009-07-02 01:02:03') | +------------------------------------------------+ + | 20102 | +------------------------------------------------+ + - sql: SELECT EXTRACT(MICROSECOND FROM '2003-01-02 10:30:00.000123'); + result: +--------------------------------------------------------+ | EXTRACT(MICROSECOND + FROM '2003-01-02 10:30:00.000123') | +--------------------------------------------------------+ + | 123 | +--------------------------------------------------------+ + - sql: From MariaDB 10.0.7 and MariaDB 5.5.35, EXTRACT (HOUR FROM...) returns + a value from 0 to 23, as per the SQL standard. HOUR is not a standard function, + so continues to adhere to the old behaviour inherited from MySQL. + result: SELECT EXTRACT(HOUR FROM '26:30:00'), HOUR('26:30:00'); +-------------------------------+------------------+ + | EXTRACT(HOUR FROM '26:30:00') | HOUR('26:30:00') | +-------------------------------+------------------+ + | 2 | 26 | +-------------------------------+------------------+ + - sql: 'URL: https://mariadb.com/kb/en/extract/' + - name: EXTRACTVALUE + category_id: string + category_label: String Functions + tags: + - string + aliases: [] + signature: + display: EXTRACTVALUE(xml_frag, xpath_expr) + args: + - name: xml_frag + optional: false + type: any + - name: xpath_expr + optional: false + type: any + summary: 'The EXTRACTVALUE() function takes two string arguments: a fragment of + XML' + description: "The EXTRACTVALUE() function takes two string arguments: a fragment\ + \ of XML\nmarkup and an XPath expression, (also known as a locator). It returns\ + \ the text\n(That is, CDDATA), of the first text node which is a child of the\ + \ element or\nelements matching the XPath expression.\n\nIn cases where a valid\ + \ XPath expression does not match any text nodes in a\nvalid XML fragment, (including\ + \ the implicit /text() expression), the\nEXTRACTVALUE() function returns an\ + \ empty string.\n\nInvalid Arguments\n-----------------\n\nWhen either the XML\ + \ fragment or the XPath expression is NULL, the\nEXTRACTVALUE() function returns\ + \ NULL. When the XML fragment is invalid, it\nraises a warning Code 1525:\n\n\ + Warning (Code 1525): Incorrect XML value: 'parse error at line 1 pos 11:\nunexpected\ + \ END-OF-INPUT'\n\nWhen the XPath value is invalid, it generates an Error 1105:\n\ + \nERROR 1105 (HY000): XPATH syntax error: ')'\n\nExplicit text() Expressions\n\ + ---------------------------\n\nThis function is the equivalent of performing\ + \ a match using the XPath\nexpression after appending /text(). In other words:\n\ + \nSELECT\n EXTRACTVALUE('example', '/cases/case')\n\ + \ AS 'Base Example',\n EXTRACTVALUE('example',\ + \ '/cases/case/text()')\n AS 'text() Example';\n+--------------+----------------+\n\ + | Base Example | text() Example |\n+--------------+----------------+\n| example\ + \ | example |\n+--------------+----------------+\n\nCount Matches\n\ + -------------\n\nWhen EXTRACTVALUE() returns multiple matches, it returns the\ + \ content of the\nfirst child text node of each matching element, in the matched\ + \ order, as a\nsingle, space-delimited string.\n\nBy design, the EXTRACTVALUE()\ + \ function makes no distinction between a match on\nan empty element and no\ + \ match at all. If you need to determine whether no\nmatching element was found\ + \ in the XML fragment or if an element was found that\n ..." + examples: [] + - name: FIELD + category_id: string + category_label: String Functions + tags: + - string + aliases: [] + signature: + display: FIELD(pattern, str1[,str2,...]) + args: + - name: pattern + optional: false + type: any + - name: str1[ + optional: false + type: any + - name: str2 + optional: false + type: any + - name: '...]' + optional: false + type: any + summary: Returns the index position of the string or number matching the given + pattern. + description: "Returns the index position of the string or number matching the\ + \ given pattern.\nReturns 0 in the event that none of the arguments match the\ + \ pattern. Raises an\nError 1582 if not given at least two arguments.\n\nWhen\ + \ all arguments given to the FIELD() function are strings, they are treated\n\ + as case-insensitive. When all the arguments are numbers, they are treated as\n\ + numbers. Otherwise, they are treated as doubles.\n\nIf the given pattern occurs\ + \ more than once, the\tFIELD() function only returns\nthe index of the first\ + \ instance. If the given pattern is NULL, the function\nreturns 0, as a NULL\ + \ pattern always fails to match.\n\nThis function is complementary to the ELT()\ + \ function." + examples: + - sql: "SELECT FIELD('ej', 'Hej', 'ej', 'Heja', 'hej', 'foo')\n AS 'Field Results';" + result: +---------------+ | Field Results | +---------------+ | 2 + | +---------------+ + - sql: "SELECT FIELD('fo', 'Hej', 'ej', 'Heja', 'hej', 'foo')\n AS 'Field Results';" + result: +---------------+ | Field Results | +---------------+ | 0 + | +---------------+ + - sql: SELECT FIELD(1, 2, 3, 4, 5, 1) AS 'Field Results'; + result: +---------------+ | Field Results | +---------------+ | 5 + | +---------------+ + - sql: SELECT FIELD(NULL, 2, 3) AS 'Field Results'; + result: +---------------+ | Field Results | +---------------+ | 0 + | +---------------+ + - sql: 'SELECT FIELD(''fail'') AS ''Field Results''; Error 1582 (42000): Incorrect + parameter count in call to native function ''field''' + result: 'URL: https://mariadb.com/kb/en/field/' + - name: FIND_IN_SET + category_id: string + category_label: String Functions + tags: + - string + aliases: [] + signature: + display: FIND_IN_SET(pattern, strlist) + args: + - name: pattern + optional: false + type: any + - name: strlist + optional: false + type: any + summary: Returns the index position where the given pattern occurs in a string + list. + description: Returns the index position where the given pattern occurs in a string + list. The first argument is the pattern you want to search for. The second argument + is a string containing comma-separated variables. If the second argument is + of the SET data-type, the function is optimized to use bit arithmetic. If the + pattern does not occur in the string list or if the string list is an empty + string, the function returns 0. If either argument is NULL, the function returns + NULL. The function does not return the correct result if the pattern contains + a comma (",") character. + examples: + - sql: SELECT FIND_IN_SET('b','a,b,c,d') AS "Found Results"; + result: +---------------+ | Found Results | +---------------+ | 2 + | +---------------+ + - sql: 'URL: https://mariadb.com/kb/en/find_in_set/' + - name: FIRST_VALUE + category_id: window + category_label: Window Functions + tags: + - window + aliases: [] + signature: + display: FIRST_VALUE(expr) + args: + - name: expr + optional: false + type: any + summary: FIRST_VALUE returns the first result from an ordered set, or NULL if + no such + description: FIRST_VALUE returns the first result from an ordered set, or NULL + if no such result exists. + examples: + - sql: "CREATE TABLE t1 (\n pk int primary key,\n a int,\n b int,\n c char(10),\n\ + \ d decimal(10, 3),\n e real\n);" + result: INSERT INTO t1 VALUES + - sql: ( 2, 0, 2, 'two', 0.2, 0.002), ( 3, 0, 3, 'three', 0.3, 0.003), + ( 4, 1, 2, 'three', 0.4, 0.004), ( 5, 1, 1, 'two', 0.5, 0.005), + ( 6, 1, 1, 'one', 0.6, 0.006), ( 7, 2, NULL, 'n_one', 0.5, 0.007), + ( 8, 2, 1, 'n_two', NULL, 0.008), ( 9, 2, 2, NULL, 0.7, 0.009), + (10, 2, 0, 'n_four', 0.8, 0.010), (11, 2, 10, NULL, 0.9, NULL); + result: SELECT pk, FIRST_VALUE(pk) OVER (ORDER BY pk) AS first_asc, + - sql: "FIRST_VALUE(pk) OVER (ORDER BY pk DESC) AS first_desc,\n LAST_VALUE(pk)\ + \ OVER (ORDER BY pk DESC) AS last_desc\nFROM t1\nORDER BY pk DESC;" + result: +----+-----------+----------+------------+-----------+ | pk | first_asc + | last_asc | first_desc | last_desc | +----+-----------+----------+------------+-----------+ + | 11 | 1 | 11 | 11 | 11 | | 10 | 1 | 10 + | 11 | 10 | | 9 | 1 | 9 | 11 | 9 + | | 8 | 1 | 8 | 11 | 8 | | 7 | 1 + | 7 | 11 | 7 | | 6 | 1 | 6 | 11 + | 6 | | 5 | 1 | 5 | 11 | 5 | | 4 + | 1 | 4 | 11 | 4 | | 3 | 1 | 3 + | 11 | 3 | | 2 | 1 | 2 | 11 | 2 + | | 1 | 1 | 1 | 11 | 1 | +----+-----------+----------+------------+-----------+ + - name: FLOAT + category_id: data_types + category_label: Data Types + tags: + - data_types + aliases: [] + signature: + display: FLOAT(M,D) + args: + - name: M + optional: false + type: any + - name: D + optional: false + type: any + summary: A small (single-precision) floating-point number (see DOUBLE for a + description: 'A small (single-precision) floating-point number (see DOUBLE for + a regular-size floating point number). Allowable values are: * -3.402823466E+38 + to -1.175494351E-38 * 0 * 1.175494351E-38 to 3.402823466E+38. These are the + theoretical limits, based on the IEEE standard. The actual range might be slightly + smaller depending on your hardware or operating system. M is the total number + of digits and D is the number of digits following the decimal point. If M and + D are omitted, values are stored to the limits allowed by the hardware. A single-precision + floating-point number is accurate to approximately 7 decimal places. UNSIGNED, + if specified, disallows negative values. Using FLOAT might give you some unexpected + problems because all calculations in MariaDB are done with double precision. + See Floating Point Accuracy. For more details on the attributes, see Numeric + Data Type Overview. URL: https://mariadb.com/kb/en/float/' + examples: [] + - name: FLOOR + category_id: numeric + category_label: Numeric Functions + tags: + - numeric + aliases: [] + signature: + display: FLOOR(X) + args: + - name: X + optional: false + type: any + summary: Returns the largest integer value not greater than X. + description: Returns the largest integer value not greater than X. + examples: + - sql: SELECT FLOOR(1.23); + result: +-------------+ | FLOOR(1.23) | +-------------+ | 1 | +-------------+ + - sql: SELECT FLOOR(-1.23); + result: +--------------+ | FLOOR(-1.23) | +--------------+ | -2 | + +--------------+ + - sql: 'URL: https://mariadb.com/kb/en/floor/' + - name: FORMAT + category_id: string + category_label: String Functions + tags: + - string + aliases: [] + signature: + display: FORMAT(num, decimal_position[, locale]) + args: + - name: num + optional: false + type: any + - name: decimal_position[ + optional: false + type: any + - name: locale] + optional: false + type: any + summary: Formats the given number for display as a string, adding separators to + description: Formats the given number for display as a string, adding separators + to appropriate position and rounding the results to the given decimal position. + For instance, it would format 15233.345 to 15,233.35. If the given decimal position + is 0, it rounds to return no decimal point or fractional part. You can optionally + specify a locale value to format numbers to the pattern appropriate for the + given region. + examples: + - sql: SELECT FORMAT(1234567890.09876543210, 4) AS 'Format'; + result: +--------------------+ | Format | +--------------------+ + | 1,234,567,890.0988 | +--------------------+ + - sql: SELECT FORMAT(1234567.89, 4) AS 'Format'; + result: +----------------+ | Format | +----------------+ | 1,234,567.8900 + | +----------------+ + - sql: SELECT FORMAT(1234567.89, 0) AS 'Format'; + result: +-----------+ | Format | +-----------+ | 1,234,568 | +-----------+ + - sql: SELECT FORMAT(123456789,2,'rm_CH') AS 'Format'; + result: +----------------+ | Format | +----------------+ | 123'456'789,00 + | +----------------+ + - sql: 'URL: https://mariadb.com/kb/en/format/' + - name: FORMAT_PICO_TIME + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: FORMAT_PICO_TIME(time_val) + args: + - name: time_val + optional: false + type: any + summary: Given a time in picoseconds, returns a human-readable time value and + unit + description: 'Given a time in picoseconds, returns a human-readable time value + and unit indicator. Resulting unit is dependent on the length of the argument, + and can be: * ps - picoseconds * ns - nanoseconds * us - microseconds * ms - + milliseconds * s - seconds * min - minutes * h - hours * d - days With the exception + of results under one nanosecond, which are not rounded and are represented as + whole numbers, the result is rounded to 2 decimal places, with a minimum of + 3 significant digits. Returns NULL if the argument is NULL. This function is + very similar to the Sys Schema FORMAT_TIME function, but with the following + differences: * Represents minutes as min rather than m. * Does not represent + weeks.' + examples: + - sql: "SELECT\n FORMAT_PICO_TIME(43) AS ps,\n FORMAT_PICO_TIME(4321) AS ns,\n\ + \ FORMAT_PICO_TIME(43211234) AS us,\n FORMAT_PICO_TIME(432112344321) AS\ + \ ms,\n FORMAT_PICO_TIME(43211234432123) AS s,\n FORMAT_PICO_TIME(432112344321234)\ + \ AS m,\n FORMAT_PICO_TIME(4321123443212345) AS h,\n FORMAT_PICO_TIME(432112344321234545)\ + \ AS d;" + result: +--------+---------+----------+-----------+---------+----------+--------+------ + - sql: '| ps | ns | us | ms | s | m | h | + d' + result: '| +--------+---------+----------+-----------+---------+----------+--------+------' + - sql: '| 43 ps | 4.32 ns | 43.21 us | 432.11 ms | 43.21 s | 7.20 min | 1.20 + h | 5.00 d |' + result: +--------+---------+----------+-----------+---------+----------+--------+------ + - sql: 'URL: https://mariadb.com/kb/en/format_pico_time/' + - name: FOUND_ROWS + category_id: information + category_label: Information Functions + tags: + - information + aliases: [] + signature: + display: FOUND_ROWS + args: [] + summary: A SELECT statement may include a LIMIT clause to restrict the number + of rows + description: 'A SELECT statement may include a LIMIT clause to restrict the number + of rows the server returns to the client. In some cases, it is desirable to + know how many rows the statement would have returned without the LIMIT, but + without running the statement again. To obtain this row count, include a SQL_CALC_FOUND_ROWS + option in the SELECT statement, and then invoke FOUND_ROWS() afterwards. You + can also use FOUND_ROWS() to obtain the number of rows returned by a SELECT + which does not contain a LIMIT clause. In this case you don''t need to use the + SQL_CALC_FOUND_ROWS option. This can be useful for example in a stored procedure. + Also, this function works with some other statements which return a resultset, + including SHOW, DESC and HELP. For DELETE ... RETURNING you should use ROW_COUNT(). + It also works as a prepared statement, or after executing a prepared statement. + Statements which don''t return any results don''t affect FOUND_ROWS() - the + previous value will still be returned. Warning: When used after a CALL statement, + this function returns the number of rows selected by the last query in the procedure, + not by the whole procedure. Statements using the FOUND_ROWS() function are not + safe for statement-based replication.' + examples: + - sql: "SHOW ENGINES\\G\n*************************** 1. row ***************************\n\ + \ Engine: CSV\n Support: YES\n Comment: Stores tables as CSV files\nTransactions:\ + \ NO\n XA: NO\n Savepoints: NO\n*************************** 2. row ***************************\n\ + \ Engine: MRG_MyISAM\n Support: YES\n Comment: Collection of identical\ + \ MyISAM tables\nTransactions: NO\n XA: NO\n Savepoints: NO" + result: '...' + - sql: "*************************** 8. row ***************************\n Engine:\ + \ PERFORMANCE_SCHEMA\n Support: YES\n ..." + - name: FROM_BASE64 + category_id: string + category_label: String Functions + tags: + - string + aliases: [] + signature: + display: FROM_BASE64(str) + args: + - name: str + optional: false + type: any + summary: Decodes the given base-64 encode string, returning the result as a binary + description: 'Decodes the given base-64 encode string, returning the result as + a binary string. Returns NULL if the given string is NULL or if it''s invalid. + It is the reverse of the TO_BASE64 function. There are numerous methods to base-64 + encode a string. MariaDB uses the following: * It encodes alphabet value 64 + as ''+''. * It encodes alphabet value 63 as ''/''. * It codes output in groups + of four printable characters. Each three byte of data encoded uses four characters. If + the final group is incomplete, it pads the difference with the ''='' character. + * It divides long output, adding a new line very 76 characters. * In decoding, + it recognizes and ignores newlines, carriage returns, tabs and space whitespace + characters. SELECT TO_BASE64(''Maria'') AS ''Input''; +-----------+ | Input | + +-----------+ | TWFyaWE= | +-----------+ SELECT FROM_BASE64(''TWFyaWE='') AS + ''Output''; +--------+ | Output | +--------+ | Maria | +--------+ URL: https://mariadb.com/kb/en/from_base64/' + examples: [] + - name: FROM_DAYS + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: FROM_DAYS(N) + args: + - name: N + optional: false + type: any + summary: Given a day number N, returns a DATE value. + description: Given a day number N, returns a DATE value. The day count is based + on the number of days from the start of the standard calendar (0000-00-00). + The function is not designed for use with dates before the advent of the Gregorian + calendar in October 1582. Results will not be reliable since it doesn't account + for the lost days when the calendar changed from the Julian calendar. This is + the converse of the TO_DAYS() function. + examples: + - sql: SELECT FROM_DAYS(730669); + result: +-------------------+ | FROM_DAYS(730669) | +-------------------+ | + 2000-07-03 | +-------------------+ + - sql: 'URL: https://mariadb.com/kb/en/from_days/' + - name: FROM_UNIXTIME + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: FROM_UNIXTIME(unix_timestamp) + args: + - name: unix_timestamp + optional: false + type: any + summary: Returns a representation of the unix_timestamp argument as a value in + description: "Returns a representation of the unix_timestamp argument as a value\ + \ in\n'YYYY-MM-DD HH:MM:SS' or YYYYMMDDHHMMSS.uuuuuu format, depending on whether\n\ + the function is used in a string or numeric context. The value is expressed\ + \ in\nthe current time zone. unix_timestamp is an internal timestamp value such\ + \ as\nis produced by the UNIX_TIMESTAMP() function.\n\nIf format is given, the\ + \ result is formatted according to the format string,\nwhich is used the same\ + \ way as listed in the entry for the DATE_FORMAT()\nfunction.\n\nTimestamps\ + \ in MariaDB have a maximum value of 2147483647, equivalent to\n2038-01-19 05:14:07.\ + \ This is due to the underlying 32-bit limitation. Using\nthe function on a\ + \ timestamp beyond this will result in NULL being returned.\nUse DATETIME as\ + \ a storage type if you require dates beyond this.\n\nThe options that can be\ + \ used by FROM_UNIXTIME(), as well as DATE_FORMAT() and\nSTR_TO_DATE(), are:\n\ + \n+---------------------------+------------------------------------------------+\n\ + | Option | Description \ + \ |\n+---------------------------+------------------------------------------------+\n\ + | %a | Short weekday name in current locale \ + \ |\n| | (Variable lc_time_names). \ + \ |\n+---------------------------+------------------------------------------------+\n\ + | %b | Short form month name in current locale. For \ + \ |\n| | locale en_US this is one of: \ + \ |\n| | Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov\ + \ |\n| | or Dec. \ + \ |\n+---------------------------+------------------------------------------------+\n\ + | %c | Month with 1 or 2 digits. \ + \ |\n+---------------------------+------------------------------------------------+\n\ + | %D | Day with English suffix 'th', 'nd', 'st' or \ + \ |\n| | 'rd''. (1st, 2nd, 3rd...). \ + \ |\n+---------------------------+------------------------------------------------+\n\ + | %d | Day with 2 digits. \ + \ |\n+---------------------------+------------------------------------------------+\n\ + | %e | Day with 1 or 2 digits. \ + \ |\n+---------------------------+------------------------------------------------+\n\ + | %f | Microseconds 6 digits. \ + \ |\n+---------------------------+------------------------------------------------+\n\ + | %H | Hour with 2 digits between 00-23. \ + \ |\n+---------------------------+------------------------------------------------+\n\ + | %h | Hour with 2 digits between 01-12. \ + \ |\n+---------------------------+------------------------------------------------+\n\ + | %I | Hour with 2 digits between 01-12. \ + \ |\n+---------------------------+------------------------------------------------+\n\ + | %i | Minute with 2 digits. \ + \ |\n+---------------------------+------------------------------------------------+\n\ + | %j | Day of the year (001-366) \ + \ |\n+---------------------------+------------------------------------------------+\n\ + \ ..." + examples: [] + - name: GEOMETRYCOLLECTION + category_id: geometry_constructors + category_label: Geometry Constructors + tags: + - geometry_constructors + aliases: [] + signature: + display: GEOMETRYCOLLECTION(g1,g2,...) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + - name: '...' + optional: false + type: any + summary: Constructs a WKB GeometryCollection. + description: Constructs a WKB GeometryCollection. If any argument is not a well-formed + WKB representation of a geometry, the return value is NULL. + examples: + - sql: "CREATE TABLE gis_geometrycollection (g GEOMETRYCOLLECTION);\nSHOW FIELDS\ + \ FROM gis_geometrycollection;\nINSERT INTO gis_geometrycollection VALUES\n\ + \ (GeomCollFromText('GEOMETRYCOLLECTION(POINT(0 0), LINESTRING(0 0,10\n10))')),\n\ + \ (GeometryFromWKB(AsWKB(GeometryCollection(Point(44, 6),\nLineString(Point(3,\ + \ 6), Point(7, 9)))))),\n (GeomFromText('GeometryCollection()')),\n (GeomFromText('GeometryCollection\ + \ EMPTY'));" + result: 'URL: https://mariadb.com/kb/en/geometrycollection/' + - name: GET_FORMAT + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: GET_FORMAT({DATE|DATETIME|TIME}, {'EUR'|'USA'|'JIS'|'ISO'|'INTERNAL'}) + args: + - name: '{DATE|DATETIME|TIME}' + optional: false + type: any + - name: '{''EUR''|''USA''|''JIS''|''ISO''|''INTERNAL''}' + optional: false + type: any + summary: Returns a format string. + description: 'Returns a format string. This function is useful in combination + with the DATE_FORMAT() and the STR_TO_DATE() functions. Possible result formats + are: +--------------------------------------+--------------------------------------+ + | Function Call | Result Format | + +--------------------------------------+--------------------------------------+ + | GET_FORMAT(DATE,''EUR'') | ''%d.%m.%Y'' | + +--------------------------------------+--------------------------------------+ + | GET_FORMAT(DATE,''USA'') | ''%m.%d.%Y'' | + +--------------------------------------+--------------------------------------+ + | GET_FORMAT(DATE,''JIS'') | ''%Y-%m-%d'' | + +--------------------------------------+--------------------------------------+ + | GET_FORMAT(DATE,''ISO'') | ''%Y-%m-%d'' | + +--------------------------------------+--------------------------------------+ + | GET_FORMAT(DATE,''INTERNAL'') | ''%Y%m%d'' | + +--------------------------------------+--------------------------------------+ + | GET_FORMAT(DATETIME,''EUR'') | ''%Y-%m-%d %H.%i.%s'' | + +--------------------------------------+--------------------------------------+ + | GET_FORMAT(DATETIME,''USA'') | ''%Y-%m-%d %H.%i.%s'' | + +--------------------------------------+--------------------------------------+ + | GET_FORMAT(DATETIME,''JIS'') | ''%Y-%m-%d %H:%i:%s'' | + +--------------------------------------+--------------------------------------+ + | GET_FORMAT(DATETIME,''ISO'') | ''%Y-%m-%d %H:%i:%s'' | + +--------------------------------------+--------------------------------------+ + | GET_FORMAT(DATETIME,''INTERNAL'') | ''%Y%m%d%H%i%s'' | + +--------------------------------------+--------------------------------------+ + | GET_FORMAT(TIME,''EUR'') | ''%H.%i.%s'' | + +--------------------------------------+--------------------------------------+ + | GET_FORMAT(TIME,''USA'') | ''%h:%i:%s %p'' | + +--------------------------------------+--------------------------------------+ + | GET_FORMAT(TIME,''JIS'') | ''%H:%i:%s'' | + +--------------------------------------+--------------------------------------+ + | GET_FORMAT(TIME,''ISO'') | ''%H:%i:%s'' | + +--------------------------------------+--------------------------------------+ + | GET_FORMAT(TIME,''INTERNAL'') | ''%H%i%s'' | + +--------------------------------------+--------------------------------------+' + examples: + - sql: 'Obtaining the string matching to the standard European date format:' + result: SELECT GET_FORMAT(DATE, 'EUR'); +-------------------------+ | GET_FORMAT(DATE, + 'EUR') | +-------------------------+ | %d.%m.%Y | +-------------------------+ + - name: GET_LOCK + category_id: miscellaneous + category_label: Miscellaneous Functions + tags: + - miscellaneous + aliases: [] + signature: + display: GET_LOCK(str,timeout) + args: + - name: str + optional: false + type: any + - name: timeout + optional: false + type: any + summary: Tries to obtain a lock with a name given by the string str, using a timeout + of + description: Tries to obtain a lock with a name given by the string str, using + a timeout of timeout seconds. Returns 1 if the lock was obtained successfully, + 0 if the attempt timed out (for example, because another client has previously + locked the name), or NULL if an error occurred (such as running out of memory + or the thread was killed with mariadb-admin kill). A lock is released with RELEASE_LOCK(), + when the connection terminates (either normally or abnormally). A connection + can hold multiple locks at the same time, so a lock that is no longer needed + needs to be explicitly released. The IS_FREE_LOCK function returns whether a + specified lock a free or not, and the IS_USED_LOCK whether the function is in + use or not. Locks obtained with GET_LOCK() do not interact with transactions. + That is, committing a transaction does not release any such locks obtained during + the transaction. It is also possible to recursively set the same lock. If a + lock with the same name is set n times, it needs to be released n times as well. + str is case insensitive for GET_LOCK() and related functions. If str is an empty + string or NULL, GET_LOCK() returns NULL and does nothing. timeout supports microseconds. + If the metadata_lock_info plugin is installed, locks acquired with this function + are visible in the Information Schema METADATA_LOCK_INFO table. This function + can be used to implement application locks or to simulate record locks. Names + are locked on a server-wide basis. If a name has been locked by one client, + GET_LOCK() blocks any request by another client for a lock with the same name. + This allows clients that agree on a given lock name to use the name to perform + cooperative advisory locking. But be aware that it also allows a client that + is not among the set of cooperating clients to lock a name, either inadvertently + or deliberately, and thus prevent any of the cooperating clients from locking + that name. One way to reduce the likelihood of this is to use lock names that + are database-specific or application-specific. For example, use lock names of + the form db_name.str or app_name.str. Statements using the GET_LOCK function + are not safe for statement-based replication. The patch to permit multiple locks + was contributed by Konstantin "Kostja" Osipov (MDEV-3917). + examples: + - sql: SELECT GET_LOCK('lock1',10); + result: +----------------------+ | GET_LOCK('lock1',10) | + - name: GLENGTH + category_id: linestring_properties + category_label: LineString Properties + tags: + - linestring_properties + aliases: [] + signature: + display: GLENGTH(ls) + args: + - name: ls + optional: false + type: any + summary: Returns as a double-precision number the length of the LineString value + ls in + description: Returns as a double-precision number the length of the LineString + value ls in its associated spatial reference. + examples: + - sql: SET @ls = 'LineString(1 1,2 2,3 3)'; + result: SELECT GLength(GeomFromText(@ls)); +----------------------------+ | + GLength(GeomFromText(@ls)) | +----------------------------+ | 2.82842712474619 + | +----------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/glength/' + - name: GREATEST + category_id: comparison_operators + category_label: Comparison Operators + tags: + - comparison_operators + aliases: [] + signature: + display: GREATEST(value1,value2,...) + args: + - name: value1 + optional: false + type: any + - name: value2 + optional: false + type: any + - name: '...' + optional: false + type: any + summary: With two or more arguments, returns the largest (maximum-valued) argument. + description: With two or more arguments, returns the largest (maximum-valued) + argument. The arguments are compared using the same rules as for LEAST(). + examples: + - sql: SELECT GREATEST(2,0); + result: +---------------+ | GREATEST(2,0) | +---------------+ | 2 + | +---------------+ + - sql: SELECT GREATEST(34.0,3.0,5.0,767.0); + result: +------------------------------+ | GREATEST(34.0,3.0,5.0,767.0) | +------------------------------+ + | 767.0 | +------------------------------+ + - sql: SELECT GREATEST('B','A','C'); + result: +-----------------------+ | GREATEST('B','A','C') | +-----------------------+ + | C | +-----------------------+ + - sql: 'URL: https://mariadb.com/kb/en/greatest/' + - name: GROUP_CONCAT + category_id: group_by + category_label: Functions and Modifiers for Use with GROUP BY + tags: + - group_by + aliases: [] + signature: + display: GROUP_CONCAT(expr) + args: + - name: expr + optional: false + type: any + summary: This function returns a string result with the concatenated non-NULL + values + description: "This function returns a string result with the concatenated non-NULL\ + \ values\nfrom a group. If any expr in GROUP_CONCAT evaluates to NULL, that\ + \ tuple is not\npresent in the list returned by GROUP_CONCAT.\n\nIt returns\ + \ NULL if all arguments are NULL, or there are no matching rows.\n\nThe maximum\ + \ returned length in bytes is determined by the group_concat_max_len\nserver\ + \ system variable, which defaults to 1M.\n\nIf group_concat_max_len <= 512,\ + \ the return type is VARBINARY or VARCHAR;\notherwise, the return type is BLOB\ + \ or TEXT. The choice between binary or\nnon-binary types depends from the input.\n\ + \nThe full syntax is as follows:\n\nGROUP_CONCAT([DISTINCT] expr [,expr ...]\n\ + \ [ORDER BY {unsigned_integer | col_name | expr}\n [ASC | DESC]\ + \ [,col_name ...]]\n [SEPARATOR str_val]\n [LIMIT {[offset,] row_count\ + \ | row_count OFFSET offset}])\n\nDISTINCT eliminates duplicate values from\ + \ the output string.\n\nORDER BY determines the order of returned values.\n\n\ + SEPARATOR specifies a separator between the values. The default separator is\ + \ a\ncomma (,). It is possible to avoid using a separator by specifying an empty\n\ + string.\n\nLIMIT\n-----\n\nThe LIMIT clause can be used with GROUP_CONCAT. This\ + \ was not possible prior to\nMariaDB 10.3.3." + examples: + - sql: "SELECT student_name,\n GROUP_CONCAT(test_score)\n FROM student\n \ + \ GROUP BY student_name;" + result: 'Get a readable list of MariaDB users from the mysql.user table:' + - sql: "SELECT GROUP_CONCAT(DISTINCT User ORDER BY User SEPARATOR '\n')\n FROM\ + \ mysql.user;" + result: In the former example, DISTINCT is used because the same user may occur + more + - sql: ") used as a SEPARATOR makes the results easier to\n ..." + - name: HEX + category_id: string + category_label: String Functions + tags: + - string + aliases: [] + signature: + display: HEX(N_or_S) + args: + - name: N_or_S + optional: false + type: any + summary: If N_or_S is a number, returns a string representation of the hexadecimal + description: If N_or_S is a number, returns a string representation of the hexadecimal + value of N, where N is a longlong (BIGINT) number. This is equivalent to CONV(N,10,16). + If N_or_S is a string, returns a hexadecimal string representation of N_or_S + where each byte of each character in N_or_S is converted to two hexadecimal + digits. If N_or_S is NULL, returns NULL. The inverse of this operation is performed + by the UNHEX() function. MariaDB starting with 10.5.0 ---------------------------- + HEX() with an INET6 argument returns a hexadecimal representation of the underlying + 16-byte binary string. + examples: + - sql: SELECT HEX(255); + result: +----------+ | HEX(255) | +----------+ | FF | +----------+ + - sql: SELECT 0x4D617269614442; + result: +------------------+ | 0x4D617269614442 | +------------------+ | MariaDB | + +------------------+ + - sql: SELECT HEX('MariaDB'); + result: +----------------+ | HEX('MariaDB') | +----------------+ | 4D617269614442 + | +----------------+ + - sql: 'From MariaDB 10.5.0:' + result: SELECT HEX(CAST('2001:db8::ff00:42:8329' AS INET6)); +----------------------------------------------+ + | HEX(CAST('2001:db8::ff00:42:8329' AS INET6)) | +----------------------------------------------+ + | 20010DB8000000000000FF0000428329 | +----------------------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/hex/' + - name: HOUR + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: HOUR(time) + args: + - name: time + optional: false + type: any + summary: Returns the hour for time. + description: Returns the hour for time. The range of the return value is 0 to + 23 for time-of-day values. However, the range of TIME values actually is much + larger, so HOUR can return values greater than 23. The return value is always + positive, even if a negative TIME value is provided. + examples: + - sql: SELECT HOUR('10:05:03'); + result: +------------------+ | HOUR('10:05:03') | +------------------+ | 10 + | +------------------+ + - sql: SELECT HOUR('272:59:59'); + result: +-------------------+ | HOUR('272:59:59') | +-------------------+ | 272 + | +-------------------+ + - sql: 'Difference between EXTRACT (HOUR FROM ...) (>= MariaDB 10.0.7 and MariaDB + 5.5.35) and HOUR:' + result: SELECT EXTRACT(HOUR FROM '26:30:00'), HOUR('26:30:00'); +-------------------------------+------------------+ + | EXTRACT(HOUR FROM '26:30:00') | HOUR('26:30:00') | +-------------------------------+------------------+ + | 2 | 26 | +-------------------------------+------------------+ + - sql: 'URL: https://mariadb.com/kb/en/hour/' + - name: IFNULL + category_id: control_flow + category_label: Control Flow Functions + tags: + - control_flow + aliases: + - NVL + signature: + display: IFNULL(expr1,expr2) + args: + - name: expr1 + optional: false + type: any + - name: expr2 + optional: false + type: any + summary: If expr1 is not NULL, IFNULL() returns expr1; otherwise it returns expr2. + description: If expr1 is not NULL, IFNULL() returns expr1; otherwise it returns + expr2. IFNULL() returns a numeric or string value, depending on the context + in which it is used. From MariaDB 10.3, NVL() is an alias for IFNULL(). + examples: + - sql: SELECT IFNULL(1,0); + result: +-------------+ | IFNULL(1,0) | +-------------+ | 1 | +-------------+ + - sql: SELECT IFNULL(NULL,10); + result: +-----------------+ | IFNULL(NULL,10) | +-----------------+ | 10 + | +-----------------+ + - sql: SELECT IFNULL(1/0,10); + result: +----------------+ | IFNULL(1/0,10) | +----------------+ | 10.0000 + | +----------------+ + - sql: SELECT IFNULL(1/0,'yes'); + result: +-------------------+ | IFNULL(1/0,'yes') | +-------------------+ | + yes | +-------------------+ + - sql: 'URL: https://mariadb.com/kb/en/ifnull/' + - name: IN + category_id: comparison_operators + category_label: Comparison Operators + tags: + - comparison_operators + aliases: [] + signature: + display: IN(value,...) + args: + - name: value + optional: false + type: any + - name: '...' + optional: false + type: any + summary: Returns 1 if expr is equal to any of the values in the IN list, else + returns + description: Returns 1 if expr is equal to any of the values in the IN list, else + returns 0. If all values are constants, they are evaluated according to the + type of expr and sorted. The search for the item then is done using a binary + search. This means IN is very quick if the IN value list consists entirely of + constants. Otherwise, type conversion takes place according to the rules described + at Type Conversion, but applied to all the arguments. If expr is NULL, IN always + returns NULL. If at least one of the values in the list is NULL, and one of + the comparisons is true, the result is 1. If at least one of the values in the + list is NULL and none of the comparisons is true, the result is NULL. + examples: + - sql: SELECT 2 IN (0,3,5,7); + result: +----------------+ | 2 IN (0,3,5,7) | +----------------+ | 0 + | +----------------+ + - sql: SELECT 'wefwf' IN ('wee','wefwf','weg'); + result: +----------------------------------+ | 'wefwf' IN ('wee','wefwf','weg') + | +----------------------------------+ | 1 + | +----------------------------------+ + - sql: 'Type conversion:' + result: SELECT 1 IN ('1', '2', '3'); +----------------------+ | 1 IN ('1', '2', + '3') | +----------------------+ | 1 | +----------------------+ + - sql: SELECT NULL IN (1, 2, 3); + result: +-------------------+ | NULL IN (1, 2, 3) | +-------------------+ | NULL + | +-------------------+ + - sql: SELECT 1 IN (1, 2, NULL); + result: +-------------------+ | 1 IN (1, 2, NULL) | +-------------------+ | 1 + | + - name: INET6_ATON + category_id: miscellaneous + category_label: Miscellaneous Functions + tags: + - miscellaneous + aliases: [] + signature: + display: INET6_ATON(expr) + args: + - name: expr + optional: false + type: any + summary: Given an IPv6 or IPv4 network address as a string, returns a binary string + description: Given an IPv6 or IPv4 network address as a string, returns a binary + string that represents the numeric value of the address. No trailing zone ID's + or traling network masks are permitted. For IPv4 addresses, or IPv6 addresses + with IPv4 address parts, no classful addresses or trailing port numbers are + permitted and octal numbers are not supported. The returned binary string will + be VARBINARY(16) or VARBINARY(4) for IPv6 and IPv4 addresses respectively. Returns + NULL if the argument is not understood. MariaDB starting with 10.5.0 ---------------------------- + From MariaDB 10.5.0, INET6_ATON can take INET6 as an argument. + examples: + - sql: SELECT HEX(INET6_ATON('10.0.1.1')); + result: +-----------------------------+ | HEX(INET6_ATON('10.0.1.1')) | +-----------------------------+ + | 0A000101 | +-----------------------------+ + - sql: SELECT HEX(INET6_ATON('48f3::d432:1431:ba23:846f')); + result: +----------------------------------------------+ | HEX(INET6_ATON('48f3::d432:1431:ba23:846f')) + | +----------------------------------------------+ | 48F3000000000000D4321431BA23846F | + +----------------------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/inet6_aton/' + - name: INET6_NTOA + category_id: miscellaneous + category_label: Miscellaneous Functions + tags: + - miscellaneous + aliases: [] + signature: + display: INET6_NTOA(expr) + args: + - name: expr + optional: false + type: any + summary: Given an IPv6 or IPv4 network address as a numeric binary string, returns + the + description: Given an IPv6 or IPv4 network address as a numeric binary string, + returns the address as a nonbinary string in the connection character set. The + return string is lowercase, and is platform independent, since it does not use + functions specific to the operating system. It has a maximum length of 39 characters. + Returns NULL if the argument is not understood. + examples: + - sql: SELECT INET6_NTOA(UNHEX('0A000101')); + result: +-------------------------------+ | INET6_NTOA(UNHEX('0A000101')) | + +-------------------------------+ | 10.0.1.1 | +-------------------------------+ + - sql: SELECT INET6_NTOA(UNHEX('48F3000000000000D4321431BA23846F')); + result: +-------------------------------------------------------+ | INET6_NTOA(UNHEX('48F3000000000000D4321431BA23846F')) + | +-------------------------------------------------------+ | 48f3::d432:1431:ba23:846f | + +-------------------------------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/inet6_ntoa/' + - name: INET_ATON + category_id: miscellaneous + category_label: Miscellaneous Functions + tags: + - miscellaneous + aliases: [] + signature: + display: INET_ATON(expr) + args: + - name: expr + optional: false + type: any + summary: Given the dotted-quad representation of an IPv4 network address as a + string, + description: Given the dotted-quad representation of an IPv4 network address as + a string, returns an integer that represents the numeric value of the address. + Addresses may be 4- or 8-byte addresses. Returns NULL if the argument is not + understood. + examples: + - sql: SELECT INET_ATON('192.168.1.1'); + result: +--------------------------+ | INET_ATON('192.168.1.1') | +--------------------------+ + | 3232235777 | +--------------------------+ + - sql: 'This is calculated as follows: 192 x 2563 + 168 x 256 2 + 1 x 256 + 1' + result: 'URL: https://mariadb.com/kb/en/inet_aton/' + - name: INET_NTOA + category_id: miscellaneous + category_label: Miscellaneous Functions + tags: + - miscellaneous + aliases: [] + signature: + display: INET_NTOA(expr) + args: + - name: expr + optional: false + type: any + summary: Given a numeric IPv4 network address in network byte order (4 or 8 byte), + description: Given a numeric IPv4 network address in network byte order (4 or + 8 byte), returns the dotted-quad representation of the address as a string. + examples: + - sql: SELECT INET_NTOA(3232235777); + result: +-----------------------+ | INET_NTOA(3232235777) | +-----------------------+ + | 192.168.1.1 | +-----------------------+ + - sql: 192.168.1.1 corresponds to 3232235777 since 192 x 2563 + 168 x 256 2 + + 1 x 256 + result: + 1 = 3232235777 + - sql: 'URL: https://mariadb.com/kb/en/inet_ntoa/' + - name: INSTR + category_id: string + category_label: String Functions + tags: + - string + aliases: [] + signature: + display: INSTR(str,substr) + args: + - name: str + optional: false + type: any + - name: substr + optional: false + type: any + summary: Returns the position of the first occurrence of substring substr in string + description: Returns the position of the first occurrence of substring substr + in string str. This is the same as the two-argument form of LOCATE(), except + that the order of the arguments is reversed. INSTR() performs a case-insensitive + search. If any argument is NULL, returns NULL. + examples: + - sql: SELECT INSTR('foobarbar', 'bar'); + result: +---------------------------+ | INSTR('foobarbar', 'bar') | +---------------------------+ + | 4 | +---------------------------+ + - sql: SELECT INSTR('My', 'Maria'); + result: +----------------------+ | INSTR('My', 'Maria') | +----------------------+ + | 0 | +----------------------+ + - sql: 'URL: https://mariadb.com/kb/en/instr/' + - name: INT + category_id: data_types + category_label: Data Types + tags: + - data_types + aliases: [] + signature: + display: INT(M) + args: + - name: M + optional: false + type: any + summary: A normal-size integer. + description: 'A normal-size integer. When marked UNSIGNED, it ranges from 0 to + 4294967295, otherwise its range is -2147483648 to 2147483647 (SIGNED is the + default). If a column has been set to ZEROFILL, all values will be prepended + by zeros so that the INT value contains a number of M digits. INTEGER is a synonym + for INT. Note: If the ZEROFILL attribute has been specified, the column will + automatically become UNSIGNED. INT4 is a synonym for INT. For details on the + attributes, see Numeric Data Type Overview.' + examples: + - sql: CREATE TABLE ints (a INT,b INT UNSIGNED,c INT ZEROFILL); + result: 'With strict_mode set, the default from MariaDB 10.2.4:' + - sql: 'INSERT INTO ints VALUES (-10,-10,-10); ERROR 1264 (22003): Out of range + value for column ''b'' at row 1' + result: INSERT INTO ints VALUES (-10,10,-10); + - sql: INSERT INTO ints VALUES (-10,10,10); + result: INSERT INTO ints VALUES (2147483648,2147483648,2147483648); + - sql: INSERT INTO ints VALUES (2147483647,2147483648,2147483648); + result: SELECT * FROM ints; +------------+------------+------------+ | a | + b | c | +------------+------------+------------+ | -10 + | 10 | 0000000010 | | 2147483647 | 2147483648 | 2147483648 | +------------+------------+------------+ + - sql: 'With strict_mode unset, the default until MariaDB 10.2.3:' + result: INSERT INTO ints VALUES (-10,-10,-10); + - sql: 'Warning (Code 1264): Out of range value for column ''b'' at row 1 Warning + (Code 1264): Out of range value for column ''c'' at row 1' + result: INSERT INTO ints VALUES (-10,10,-10); + - sql: "Warning (Code 1264): Out of range value for column 'c' at row 1\n ..." + - name: INTERSECT + category_id: data_manipulation + category_label: Data Manipulation + tags: + - data_manipulation + aliases: [] + signature: + display: INTERSECT(as well as EXCEPT) + args: + - name: as well as EXCEPT + optional: false + type: any + summary: MariaDB 10. + description: "MariaDB 10.3.\n\nAll behavior for naming columns, ORDER BY and LIMIT\ + \ is the same as for UNION.\n\nINTERSECT implicitly supposes a DISTINCT operation.\n\ + \nThe result of an intersect is the intersection of right and left SELECT\n\ + results, i.e. only records that are present in both result sets will be\nincluded\ + \ in the result of the operation.\n\nINTERSECT has higher precedence than UNION\ + \ and EXCEPT (unless running running\nin Oracle mode, in which case all three\ + \ have the same precedence). If possible\nit will be executed linearly but if\ + \ not it will be translated to a subquery in\nthe FROM clause:\n\n(select a,b\ + \ from t1)\nunion\n(select c,d from t2)\nintersect\n(select e,f from t3)\nunion\n\ + (select 4,4);\n\nwill be translated to:\n\n(select a,b from t1)\nunion\nselect\ + \ c,d from\n ((select c,d from t2)\n intersect\n (select e,f from t3)) dummy_subselect\n\ + union\n(select 4,4)\n\nMariaDB starting with 10.4.0\n----------------------------\n\ + \nParentheses\n-----------\n\nFrom MariaDB 10.4.0, parentheses can be used to\ + \ specify precedence. Before\nthis, a syntax error would be returned.\n\nMariaDB\ + \ starting with 10.5.0\n----------------------------\n\nALL/DISTINCT\n------------\n\ + \nINTERSECT ALL and INTERSECT DISTINCT were introduced in MariaDB 10.5.0. The\n\ + \ ..." + examples: [] + - name: INTERSECTS + category_id: geometry_relations + category_label: Geometry Relations + tags: + - geometry_relations + aliases: [] + signature: + display: INTERSECTS(g1,g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + summary: Returns 1 or 0 to indicate whether geometry g1 spatially intersects geometry + description: 'Returns 1 or 0 to indicate whether geometry g1 spatially intersects + geometry g2. INTERSECTS() is based on the original MySQL implementation and + uses object bounding rectangles, while ST_INTERSECTS() uses object shapes. INTERSECTS() + tests the opposite relationship to DISJOINT(). URL: https://mariadb.com/kb/en/intersects/' + examples: [] + - name: INTERVAL + category_id: comparison_operators + category_label: Comparison Operators + tags: + - comparison_operators + aliases: [] + signature: + display: INTERVAL(N,N1,N2,N3,...) + args: + - name: N + optional: false + type: any + - name: N1 + optional: false + type: any + - name: N2 + optional: false + type: any + - name: N3 + optional: false + type: any + - name: '...' + optional: false + type: any + summary: Returns the index of the last argument that is less than the first argument + or + description: Returns the index of the last argument that is less than the first + argument or is NULL. Returns 0 if N < N1, 1 if N < N2, 2 if N < N3 and so on + or -1 if N is NULL. All arguments are treated as integers. It is required that + N1 < N2 < N3 < ... < Nn for this function to work correctly. This is because + a fast binary search is used. + examples: + - sql: SELECT INTERVAL(23, 1, 15, 17, 30, 44, 200); + result: +--------------------------------------+ | INTERVAL(23, 1, 15, 17, 30, + 44, 200) | +--------------------------------------+ | 3 + | +--------------------------------------+ + - sql: SELECT INTERVAL(10, 1, 10, 100, 1000); + result: +--------------------------------+ | INTERVAL(10, 1, 10, 100, 1000) + | +--------------------------------+ | 2 | +--------------------------------+ + - sql: SELECT INTERVAL(22, 23, 30, 44, 200); + result: +-------------------------------+ | INTERVAL(22, 23, 30, 44, 200) | + +-------------------------------+ | 0 | +-------------------------------+ + - sql: SELECT INTERVAL(10, 2, NULL); + result: +-----------------------+ | INTERVAL(10, 2, NULL) | +-----------------------+ + | 2 | +-----------------------+ + - sql: 'URL: https://mariadb.com/kb/en/interval/' + - name: ISNULL + category_id: comparison_operators + category_label: Comparison Operators + tags: + - comparison_operators + aliases: [] + signature: + display: ISNULL(expr) + args: + - name: expr + optional: false + type: any + summary: If expr is NULL, ISNULL() returns 1, otherwise it returns 0. + description: If expr is NULL, ISNULL() returns 1, otherwise it returns 0. See + also NULL Values in MariaDB. + examples: + - sql: SELECT ISNULL(1+1); + result: +-------------+ | ISNULL(1+1) | +-------------+ | 0 | +-------------+ + - sql: SELECT ISNULL(1/0); + result: +-------------+ | ISNULL(1/0) | +-------------+ | 1 | +-------------+ + - sql: 'URL: https://mariadb.com/kb/en/isnull/' + - name: IS_FREE_LOCK + category_id: miscellaneous + category_label: Miscellaneous Functions + tags: + - miscellaneous + aliases: [] + signature: + display: IS_FREE_LOCK(str) + args: + - name: str + optional: false + type: any + summary: Checks whether the lock named str is free to use (that is, not locked). + description: 'Checks whether the lock named str is free to use (that is, not locked). + Returns 1 if the lock is free (no one is using the lock), 0 if the lock is in + use, and NULL if an error occurs (such as an incorrect argument, like an empty + string or NULL). str is case insensitive. If the metadata_lock_info plugin is + installed, the Information Schema metadata_lock_info table contains information + about locks of this kind (as well as metadata locks). Statements using the IS_FREE_LOCK + function are not safe for statement-based replication. URL: https://mariadb.com/kb/en/is_free_lock/' + examples: [] + - name: IS_IPV4 + category_id: miscellaneous + category_label: Miscellaneous Functions + tags: + - miscellaneous + aliases: [] + signature: + display: IS_IPV4(expr) + args: + - name: expr + optional: false + type: any + summary: If the expression is a valid IPv4 address, returns 1, otherwise returns + 0. + description: If the expression is a valid IPv4 address, returns 1, otherwise returns + 0. IS_IPV4() is stricter than INET_ATON(), but as strict as INET6_ATON(), in + determining the validity of an IPv4 address. This implies that if IS_IPV4 returns + 1, the same expression will always return a non-NULL result when passed to INET_ATON(), + but that the reverse may not apply. + examples: + - sql: SELECT IS_IPV4('1110.0.1.1'); + result: +-----------------------+ | IS_IPV4('1110.0.1.1') | +-----------------------+ + | 0 | +-----------------------+ + - sql: SELECT IS_IPV4('48f3::d432:1431:ba23:846f'); + result: +--------------------------------------+ | IS_IPV4('48f3::d432:1431:ba23:846f') + | +--------------------------------------+ | 0 + | +--------------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/is_ipv4/' + - name: IS_IPV4_COMPAT + category_id: miscellaneous + category_label: Miscellaneous Functions + tags: + - miscellaneous + aliases: [] + signature: + display: IS_IPV4_COMPAT(expr) + args: + - name: expr + optional: false + type: any + summary: Returns 1 if a given numeric binary string IPv6 address, such as returned + by + description: Returns 1 if a given numeric binary string IPv6 address, such as + returned by INET6_ATON(), is IPv4-compatible, otherwise returns 0. MariaDB starting + with 10.5.0 ---------------------------- From MariaDB 10.5.0, when the argument + is not INET6, automatic implicit CAST to INET6 is applied. As a consequence, + IS_IPV4_COMPAT now understands arguments in both text representation and binary(16) + representation. Before MariaDB 10.5.0, the function understood only binary(16) + representation. + examples: + - sql: SELECT IS_IPV4_COMPAT(INET6_ATON('::10.0.1.1')); + result: +------------------------------------------+ | IS_IPV4_COMPAT(INET6_ATON('::10.0.1.1')) + | +------------------------------------------+ | 1 + | +------------------------------------------+ + - sql: SELECT IS_IPV4_COMPAT(INET6_ATON('::48f3::d432:1431:ba23:846f')); + result: +-----------------------------------------------------------+ | IS_IPV4_COMPAT(INET6_ATON('::48f3::d432:1431:ba23:846f')) + | +-----------------------------------------------------------+ | 0 + | +-----------------------------------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/is_ipv4_compat/' + - name: IS_IPV4_MAPPED + category_id: miscellaneous + category_label: Miscellaneous Functions + tags: + - miscellaneous + aliases: [] + signature: + display: IS_IPV4_MAPPED(expr) + args: + - name: expr + optional: false + type: any + summary: Returns 1 if a given a numeric binary string IPv6 address, such as returned + by + description: Returns 1 if a given a numeric binary string IPv6 address, such as + returned by INET6_ATON(), is a valid IPv4-mapped address, otherwise returns + 0. MariaDB starting with 10.5.0 ---------------------------- From MariaDB 10.5.0, + when the argument is not INET6, automatic implicit CAST to INET6 is applied. + As a consequence, IS_IPV4_MAPPED now understands arguments in both text representation + and binary(16) representation. Before MariaDB 10.5.0, the function understood + only binary(16) representation. + examples: + - sql: SELECT IS_IPV4_MAPPED(INET6_ATON('::10.0.1.1')); + result: +------------------------------------------+ | IS_IPV4_MAPPED(INET6_ATON('::10.0.1.1')) + | +------------------------------------------+ | 0 + | +------------------------------------------+ + - sql: SELECT IS_IPV4_MAPPED(INET6_ATON('::ffff:10.0.1.1')); + result: +-----------------------------------------------+ | IS_IPV4_MAPPED(INET6_ATON('::ffff:10.0.1.1')) + | +-----------------------------------------------+ | 1 + | +-----------------------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/is_ipv4_mapped/' + - name: IS_IPV6 + category_id: miscellaneous + category_label: Miscellaneous Functions + tags: + - miscellaneous + aliases: [] + signature: + display: IS_IPV6(expr) + args: + - name: expr + optional: false + type: any + summary: Returns 1 if the expression is a valid IPv6 address specified as a string, + description: Returns 1 if the expression is a valid IPv6 address specified as + a string, otherwise returns 0. Does not consider IPv4 addresses to be valid + IPv6 addresses. + examples: + - sql: SELECT IS_IPV6('48f3::d432:1431:ba23:846f'); + result: +--------------------------------------+ | IS_IPV6('48f3::d432:1431:ba23:846f') + | +--------------------------------------+ | 1 + | +--------------------------------------+ + - sql: SELECT IS_IPV6('10.0.1.1'); + result: +---------------------+ | IS_IPV6('10.0.1.1') | +---------------------+ + | 0 | +---------------------+ + - sql: 'URL: https://mariadb.com/kb/en/is_ipv6/' + - name: IS_USED_LOCK + category_id: miscellaneous + category_label: Miscellaneous Functions + tags: + - miscellaneous + aliases: [] + signature: + display: IS_USED_LOCK(str) + args: + - name: str + optional: false + type: any + summary: Checks whether the lock named str is in use (that is, locked). + description: 'Checks whether the lock named str is in use (that is, locked). If + so, it returns the connection identifier of the client that holds the lock. + Otherwise, it returns NULL. str is case insensitive. If the metadata_lock_info + plugin is installed, the Information Schema metadata_lock_info table contains + information about locks of this kind (as well as metadata locks). Statements + using the IS_USED_LOCK function are not safe for statement-based replication. + URL: https://mariadb.com/kb/en/is_used_lock/' + examples: [] + - name: JSON_ARRAY + category_id: json + category_label: JSON Functions + tags: + - json + aliases: [] + signature: + display: JSON_ARRAY([value[, value2] ...]) + args: + - name: '[value[' + optional: false + type: any + - name: value2] ...] + optional: false + type: any + summary: Returns a JSON array containing the listed values. + description: 'Returns a JSON array containing the listed values. The list can + be empty. Example ------- SELECT Json_Array(56, 3.1416, ''My name is "Foo"'', + NULL); +--------------------------------------------------+ | Json_Array(56, + 3.1416, ''My name is "Foo"'', NULL) | +--------------------------------------------------+ + | [56, 3.1416, "My name is \"Foo\"", null] | +--------------------------------------------------+ + URL: https://mariadb.com/kb/en/json_array/' + examples: [] + - name: JSON_ARRAYAGG + category_id: json + category_label: JSON Functions + tags: + - json + aliases: [] + signature: + display: JSON_ARRAYAGG(column_or_expression) + args: + - name: column_or_expression + optional: false + type: any + summary: JSON_ARRAYAGG returns a JSON array containing an element for each value + in a + description: "JSON_ARRAYAGG returns a JSON array containing an element for each\ + \ value in a\ngiven set of JSON or SQL values. It acts on a column or an expression\ + \ that\nevaluates to a single value.\n\nThe maximum returned length in bytes\ + \ is determined by the group_concat_max_len\nserver system variable.\n\nReturns\ + \ NULL in the case of an error, or if the result contains no rows.\n\nJSON_ARRAYAGG\ + \ cannot currently be used as a window function.\n\nThe full syntax is as follows:\n\ + \nJSON_ARRAYAGG([DISTINCT] expr\n [ORDER BY {unsigned_integer | col_name\ + \ | expr}\n [ASC | DESC] [,col_name ...]]\n [LIMIT {[offset,] row_count\ + \ | row_count OFFSET offset}])" + examples: + - sql: CREATE TABLE t1 (a INT, b INT); + result: INSERT INTO t1 VALUES (1, 1),(2, 1), (1, 1),(2, 1), (3, 2),(2, 2),(2, + 2),(2, + - sql: SELECT JSON_ARRAYAGG(a), JSON_ARRAYAGG(b) FROM t1; + result: +-------------------+-------------------+ | JSON_ARRAYAGG(a) | JSON_ARRAYAGG(b) | + +-------------------+-------------------+ | [1,2,1,2,3,2,2,2] | [1,1,1,1,2,2,2,2] + | +-------------------+-------------------+ + - sql: SELECT JSON_ARRAYAGG(a), JSON_ARRAYAGG(b) FROM t1 GROUP BY b; + result: +------------------+------------------+ | JSON_ARRAYAGG(a) | JSON_ARRAYAGG(b) + | +------------------+------------------+ | [1,2,1,2] | [1,1,1,1] | + | [3,2,2,2] | [2,2,2,2] | +------------------+------------------+ + - sql: 'URL: https://mariadb.com/kb/en/json_arrayagg/' + - name: JSON_ARRAY_APPEND + category_id: json + category_label: JSON Functions + tags: + - json + aliases: [] + signature: + display: JSON_ARRAY_APPEND(json_doc, path, value[, path, value] ...) + args: + - name: json_doc + optional: false + type: any + - name: path + optional: false + type: any + - name: value[ + optional: false + type: any + - name: path + optional: false + type: any + - name: value] ... + optional: false + type: any + summary: Appends values to the end of the specified arrays within a JSON document, + description: Appends values to the end of the specified arrays within a JSON document, + returning the result, or NULL if any of the arguments are NULL. Evaluation is + performed from left to right, with the resulting document from the previous + pair becoming the new value against which the next pair is evaluated. If the + json_doc is not a valid JSON document, or if any of the paths are not valid, + or contain a * or ** wildcard, an error is returned. + examples: + - sql: SET @json = '[1, 2, [3, 4]]'; + result: SELECT JSON_ARRAY_APPEND(@json, '$[0]', 5) +-------------------------------------+ + | JSON_ARRAY_APPEND(@json, '$[0]', 5) | +-------------------------------------+ + | [[1, 5], 2, [3, 4]] | +-------------------------------------+ + - sql: SELECT JSON_ARRAY_APPEND(@json, '$[1]', 6); + result: +-------------------------------------+ | JSON_ARRAY_APPEND(@json, '$[1]', + 6) | +-------------------------------------+ | [1, [2, 6], [3, 4]] | + +-------------------------------------+ + - sql: SELECT JSON_ARRAY_APPEND(@json, '$[1]', 6, '$[2]', 7); + result: +------------------------------------------------+ | JSON_ARRAY_APPEND(@json, + '$[1]', 6, '$[2]', 7) | +------------------------------------------------+ + | [1, [2, 6], [3, 4, 7]] | +------------------------------------------------+ + - sql: SELECT JSON_ARRAY_APPEND(@json, '$', 5); + result: +----------------------------------+ | JSON_ARRAY_APPEND(@json, '$', + 5) | +----------------------------------+ | [1, 2, [3, 4], 5] | + +----------------------------------+ + - sql: 'SET @json = ''{"A": 1, "B": [2], "C": [3, 4]}'';' + result: 'SELECT JSON_ARRAY_APPEND(@json, ''$.B'', 5); +------------------------------------+ + | JSON_ARRAY_APPEND(@json, ''$.B'', 5) | +------------------------------------+ + | {"A": 1, "B": [2, 5], "C": [3, 4]} | +------------------------------------+' + - sql: 'URL: https://mariadb.com/kb/en/json_array_append/' + - name: JSON_ARRAY_INSERT + category_id: json + category_label: JSON Functions + tags: + - json + aliases: [] + signature: + display: JSON_ARRAY_INSERT(json_doc, path, value[, path, value] ...) + args: + - name: json_doc + optional: false + type: any + - name: path + optional: false + type: any + - name: value[ + optional: false + type: any + - name: path + optional: false + type: any + - name: value] ... + optional: false + type: any + summary: Inserts a value into a JSON document, returning the modified document, + or NULL + description: Inserts a value into a JSON document, returning the modified document, + or NULL if any of the arguments are NULL. Evaluation is performed from left + to right, with the resulting document from the previous pair becoming the new + value against which the next pair is evaluated. If the json_doc is not a valid + JSON document, or if any of the paths are not valid, or contain a * or ** wildcard, + an error is returned. + examples: + - sql: SET @json = '[1, 2, [3, 4]]'; + result: SELECT JSON_ARRAY_INSERT(@json, '$[0]', 5); +-------------------------------------+ + | JSON_ARRAY_INSERT(@json, '$[0]', 5) | +-------------------------------------+ + | [5, 1, 2, [3, 4]] | +-------------------------------------+ + - sql: SELECT JSON_ARRAY_INSERT(@json, '$[1]', 6); + result: +-------------------------------------+ | JSON_ARRAY_INSERT(@json, '$[1]', + 6) | +-------------------------------------+ | [1, 6, 2, [3, 4]] | + +-------------------------------------+ + - sql: SELECT JSON_ARRAY_INSERT(@json, '$[1]', 6, '$[2]', 7); + result: +------------------------------------------------+ | JSON_ARRAY_INSERT(@json, + '$[1]', 6, '$[2]', 7) | +------------------------------------------------+ + | [1, 6, 7, 2, [3, 4]] | +------------------------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/json_array_insert/' + - name: JSON_ARRAY_INTERSECT + category_id: json + category_label: JSON Functions + tags: + - json + aliases: [] + signature: + display: JSON_ARRAY_INTERSECT(arr1, arr2) + args: + - name: arr1 + optional: false + type: any + - name: arr2 + optional: false + type: any + summary: Finds intersection between two json arrays and returns an array of items + found + description: Finds intersection between two json arrays and returns an array of + items found in both array. + examples: + - sql: SET @json1= '[1,2,3]'; SET @json2= '[1,2,4]'; + result: SELECT json_array_intersect(@json1, @json2); +--------------------------------------+ + | json_array_intersect(@json1, @json2) | +--------------------------------------+ + | [1, 2] | +--------------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/json_array_intersect/' + - name: JSON_COMPACT + category_id: json + category_label: JSON Functions + tags: + - json + aliases: [] + signature: + display: JSON_COMPACT(json_doc) + args: + - name: json_doc + optional: false + type: any + summary: Removes all unnecessary spaces so the json document is as short as possible. + description: 'Removes all unnecessary spaces so the json document is as short + as possible. Example ------- SET @j = ''{ "A": 1, "B": [2, 3]}''; SELECT JSON_COMPACT(@j), + @j; +-------------------+------------------------+ | JSON_COMPACT(@j) | @j | + +-------------------+------------------------+ | {"A":1,"B":[2,3]} | { "A": + 1, "B": [2, 3]} | +-------------------+------------------------+ URL: https://mariadb.com/kb/en/json_compact/' + examples: [] + - name: JSON_CONTAINS + category_id: json + category_label: JSON Functions + tags: + - json + aliases: [] + signature: + display: JSON_CONTAINS(json_doc, val[, path]) + args: + - name: json_doc + optional: false + type: any + - name: val[ + optional: false + type: any + - name: path] + optional: false + type: any + summary: Returns whether or not the specified value is found in the given JSON + document + description: Returns whether or not the specified value is found in the given + JSON document or, optionally, at the specified path within the document. Returns + 1 if it does, 0 if not and NULL if any of the arguments are null. An error occurs + if the document or path is not valid, or contains the * or ** wildcards. + examples: + - sql: 'SET @json = ''{"A": 0, "B": {"C": 1}, "D": 2}'';' + result: SELECT JSON_CONTAINS(@json, '2', '$.A'); +----------------------------------+ + | JSON_CONTAINS(@json, '2', '$.A') | +----------------------------------+ + | 0 | +----------------------------------+ + - sql: SELECT JSON_CONTAINS(@json, '2', '$.D'); + result: +----------------------------------+ | JSON_CONTAINS(@json, '2', '$.D') + | +----------------------------------+ | 1 + | +----------------------------------+ + - sql: 'SELECT JSON_CONTAINS(@json, ''{"C": 1}'', ''$.A'');' + result: '+-----------------------------------------+ | JSON_CONTAINS(@json, + ''{"C": 1}'', ''$.A'') | +-----------------------------------------+ | 0 + | +-----------------------------------------+' + - sql: 'SELECT JSON_CONTAINS(@json, ''{"C": 1}'', ''$.B'');' + result: '+-----------------------------------------+ | JSON_CONTAINS(@json, + ''{"C": 1}'', ''$.B'') | +-----------------------------------------+ | 1 + | +-----------------------------------------+' + - sql: 'URL: https://mariadb.com/kb/en/json_contains/' + - name: JSON_CONTAINS_PATH + category_id: json + category_label: JSON Functions + tags: + - json + aliases: [] + signature: + display: JSON_CONTAINS_PATH(json_doc, return_arg, path[, path] ...) + args: + - name: json_doc + optional: false + type: any + - name: return_arg + optional: false + type: any + - name: path[ + optional: false + type: any + - name: path] ... + optional: false + type: any + summary: Indicates whether the given JSON document contains data at the specified + path + description: "Indicates whether the given JSON document contains data at the specified\ + \ path\nor paths. Returns 1 if it does, 0 if not and NULL if any of the arguments\ + \ are\nnull.\n\nThe return_arg can be one or all:\n\n* one - Returns 1 if at\ + \ least one path exists within the JSON document. \n* all - Returns 1 only if\ + \ all paths exist within the JSON document." + examples: + - sql: 'SET @json = ''{"A": 1, "B": [2], "C": [3, 4]}'';' + result: SELECT JSON_CONTAINS_PATH(@json, 'one', '$.A', '$.D'); +------------------------------------------------+ + | JSON_CONTAINS_PATH(@json, 'one', '$.A', '$.D') | +------------------------------------------------+ + | 1 | +------------------------------------------------+ + - sql: SELECT JSON_CONTAINS_PATH(@json, 'all', '$.A', '$.D'); + result: +------------------------------------------------+ | JSON_CONTAINS_PATH(@json, + 'all', '$.A', '$.D') | +------------------------------------------------+ + | 0 | +------------------------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/json_contains_path/' + - name: JSON_DEPTH + category_id: json + category_label: JSON Functions + tags: + - json + aliases: [] + signature: + display: JSON_DEPTH(json_doc) + args: + - name: json_doc + optional: false + type: any + summary: Returns the maximum depth of the given JSON document, or NULL if the + argument + description: Returns the maximum depth of the given JSON document, or NULL if + the argument is null. An error will occur if the argument is an invalid JSON + document. * Scalar values or empty arrays or objects have a depth of 1. * Arrays + or objects that are not empty but contain only elements or member values of + depth 1 will have a depth of 2. * In other cases, the depth will be greater + than 2. + examples: + - sql: SELECT JSON_DEPTH('[]'), JSON_DEPTH('true'), JSON_DEPTH('{}'); + result: +------------------+--------------------+------------------+ | JSON_DEPTH('[]') + | JSON_DEPTH('true') | JSON_DEPTH('{}') | +------------------+--------------------+------------------+ + | 1 | 1 | 1 | +------------------+--------------------+------------------+ + - sql: SELECT JSON_DEPTH('[1, 2, 3]'), JSON_DEPTH('[[], {}, []]'); + result: +-------------------------+----------------------------+ | JSON_DEPTH('[1, + 2, 3]') | JSON_DEPTH('[[], {}, []]') | +-------------------------+----------------------------+ + | 2 | 2 | +-------------------------+----------------------------+ + - sql: SELECT JSON_DEPTH('[1, 2, [3, 4, 5, 6], 7]'); + result: +---------------------------------------+ | JSON_DEPTH('[1, 2, [3, 4, + 5, 6], 7]') | +---------------------------------------+ | 3 + | +---------------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/json_depth/' + - name: JSON_DETAILED + category_id: json + category_label: JSON Functions + tags: + - json + aliases: [] + signature: + display: JSON_DETAILED(json_doc[, tab_size]) + args: + - name: json_doc[ + optional: false + type: any + - name: tab_size] + optional: false + type: any + summary: Represents JSON in the most understandable way emphasizing nested structures. + description: "Represents JSON in the most understandable way emphasizing nested\ + \ structures.\n\nJSON_PRETTY was added as an alias for JSON_DETAILED in MariaDB\ + \ 10.10.3,\nMariaDB 10.9.5, MariaDB 10.8.7, MariaDB 10.7.8, MariaDB 10.6.12,\ + \ MariaDB\n10.5.19 and MariaDB 10.4.28.\n\nExample\n-------\n\nSET @j = '{ \"\ + A\":1,\"B\":[2,3]}';\n\nSELECT @j;\n+--------------------+\n| @j \ + \ |\n+--------------------+\n| { \"A\":1,\"B\":[2,3]} |\n+--------------------+\n\ + \nSELECT JSON_DETAILED(@j);\n+------------------------------------------------------------+\n\ + | JSON_DETAILED(@j) |\n+------------------------------------------------------------+\n\ + | {\n \"A\": 1,\n \"B\":\n [\n 2,\n 3\n ]\n} |\n+------------------------------------------------------------+\n\ + \nURL: https://mariadb.com/kb/en/json_detailed/" + examples: [] + - name: JSON_EQUALS + category_id: json + category_label: JSON Functions + tags: + - json + aliases: [] + signature: + display: JSON_EQUALS(json1, json2) + args: + - name: json1 + optional: false + type: any + - name: json2 + optional: false + type: any + summary: Checks if there is equality between two json objects. + description: Checks if there is equality between two json objects. Returns 1 if + it there is, 0 if not, or NULL if any of the arguments are null. + examples: + - sql: SELECT JSON_EQUALS('{"a" :[1, 2, 3],"b":[4]}', '{"b":[4],"a":[1, 2, 3.0]}'); + result: +------------------------------------------------------------------------+ + | JSON_EQUALS('{"a" :[1, 2, 3],"b":[4]}', '{"b":[4],"a":[1, 2, 3.0]}') | + +------------------------------------------------------------------------+ + | 1 | + +------------------------------------------------------------------------+ + - sql: SELECT JSON_EQUALS('{"a":[1, 2, 3]}', '{"a":[1, 2, 3.01]}'); + result: +------------------------------------------------------+ | JSON_EQUALS('{"a":[1, + 2, 3]}', '{"a":[1, 2, 3.01]}') | +------------------------------------------------------+ + | 0 | +------------------------------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/json_equals/' + - name: JSON_EXISTS + category_id: json + category_label: JSON Functions + tags: + - json + aliases: [] + signature: + display: JSON_EXISTS('{"key1":"xxxx", "key2":[1, 2, 3]}', "$.key2") + args: + - name: '''{"key1":"xxxx"' + optional: false + type: any + - name: '"key2":[1' + optional: false + type: any + - name: '2' + optional: false + type: any + - name: 3]}' + optional: false + type: any + - name: '"$.key2"' + optional: false + type: any + summary: +------------------------------------------------------------+ + description: '+------------------------------------------------------------+ | + JSON_EXISTS(''{"key1":"xxxx", "key2":[1, 2, 3]}'', "$.key2") | +------------------------------------------------------------+ + | 1 | +------------------------------------------------------------+ + SELECT JSON_EXISTS(''{"key1":"xxxx", "key2":[1, 2, 3]}'', "$.key3"); +------------------------------------------------------------+ + | JSON_EXISTS(''{"key1":"xxxx", "key2":[1, 2, 3]}'', "$.key3") | +------------------------------------------------------------+ + | 0 | +------------------------------------------------------------+ + SELECT JSON_EXISTS(''{"key1":"xxxx", "key2":[1, 2, 3]}'', "$.key2[1]"); +---------------------------------------------------------------+ + | JSON_EXISTS(''{"key1":"xxxx", "key2":[1, 2, 3]}'', "$.key2[1]") | +---------------------------------------------------------------+ + | 1 | +---------------------------------------------------------------+ + SELECT JSON_EXISTS(''{"key1":"xxxx", "key2":[1, 2, 3]}'', "$.key2[10]"); +----------------------------------------------------------------+ + | JSON_EXISTS(''{"key1":"xxxx", "key2":[1, 2, 3]}'', "$.key2[10]") | +----------------------------------------------------------------+ + | 0 | +----------------------------------------------------------------+ + URL: https://mariadb.com/kb/en/json_exists/' + examples: [] + - name: JSON_EXTRACT + category_id: json + category_label: JSON Functions + tags: + - json + aliases: [] + signature: + display: JSON_EXTRACT(json_doc, path[, path] ...) + args: + - name: json_doc + optional: false + type: any + - name: path[ + optional: false + type: any + - name: path] ... + optional: false + type: any + summary: Extracts data from a JSON document. + description: Extracts data from a JSON document. The extracted data is selected + from the parts matching the path arguments. Returns all matched values; either + as a single matched value, or, if the arguments could return multiple values, + a result autowrapped as an array in the matching order. Returns NULL if no paths + match or if any of the arguments are NULL. An error will occur if any path argument + is not a valid path, or if the json_doc argument is not a valid JSON document. + The path expression be a JSONPath expression as supported by MariaDB + examples: + - sql: SET @json = '[1, 2, [3, 4]]'; + result: SELECT JSON_EXTRACT(@json, '$[1]'); +-----------------------------+ + | JSON_EXTRACT(@json, '$[1]') | +-----------------------------+ | 2 | + +-----------------------------+ + - sql: SELECT JSON_EXTRACT(@json, '$[2]'); + result: +-----------------------------+ | JSON_EXTRACT(@json, '$[2]') | +-----------------------------+ + | [3, 4] | +-----------------------------+ + - sql: SELECT JSON_EXTRACT(@json, '$[2][1]'); + result: +--------------------------------+ | JSON_EXTRACT(@json, '$[2][1]') + | +--------------------------------+ | 4 | +--------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/json_extract/' + - name: JSON_INSERT + category_id: json + category_label: JSON Functions + tags: + - json + aliases: [] + signature: + display: JSON_INSERT(json_doc, path, val[, path, val] ...) + args: + - name: json_doc + optional: false + type: any + - name: path + optional: false + type: any + - name: val[ + optional: false + type: any + - name: path + optional: false + type: any + - name: val] ... + optional: false + type: any + summary: Inserts data into a JSON document, returning the resulting document or + NULL if + description: Inserts data into a JSON document, returning the resulting document + or NULL if either of the json_doc or path arguments are null. An error will + occur if the JSON document is invalid, or if any of the paths are invalid or + contain a * or ** wildcard. JSON_INSERT can only insert data while JSON_REPLACE + can only update. JSON_SET can update or insert data. + examples: + - sql: 'SET @json = ''{ "A": 0, "B": [1, 2]}'';' + result: 'SELECT JSON_INSERT(@json, ''$.C'', ''[3, 4]''); +--------------------------------------+ + | JSON_INSERT(@json, ''$.C'', ''[3, 4]'') | +--------------------------------------+ + | { "A": 0, "B": [1, 2], "C":"[3, 4]"} | +--------------------------------------+' + - sql: 'URL: https://mariadb.com/kb/en/json_insert/' + - name: JSON_KEYS + category_id: json + category_label: JSON Functions + tags: + - json + aliases: [] + signature: + display: JSON_KEYS(json_doc[, path]) + args: + - name: json_doc[ + optional: false + type: any + - name: path] + optional: false + type: any + summary: Returns the keys as a JSON array from the top-level value of a JSON object + or, + description: Returns the keys as a JSON array from the top-level value of a JSON + object or, if the optional path argument is provided, the top-level keys from + the path. Excludes keys from nested sub-objects in the top level value. The + resulting array will be empty if the selected object is empty. Returns NULL + if any of the arguments are null, a given path does not locate an object, or + if the json_doc argument is not an object. An error will occur if JSON document + is invalid, the path is invalid or if the path contains a * or ** wildcard. + examples: + - sql: 'SELECT JSON_KEYS(''{"A": 1, "B": {"C": 2}}'');' + result: '+--------------------------------------+ | JSON_KEYS(''{"A": 1, "B": + {"C": 2}}'') | +--------------------------------------+ | ["A", "B"] | + +--------------------------------------+' + - sql: 'SELECT JSON_KEYS(''{"A": 1, "B": 2, "C": {"D": 3}}'', ''$.C'');' + result: '+-----------------------------------------------------+ | JSON_KEYS(''{"A": + 1, "B": 2, "C": {"D": 3}}'', ''$.C'') | +-----------------------------------------------------+ + | ["D"] | +-----------------------------------------------------+' + - sql: 'URL: https://mariadb.com/kb/en/json_keys/' + - name: JSON_LENGTH + category_id: json + category_label: JSON Functions + tags: + - json + aliases: [] + signature: + display: JSON_LENGTH(json_doc[, path]) + args: + - name: json_doc[ + optional: false + type: any + - name: path] + optional: false + type: any + summary: Returns the length of a JSON document, or, if the optional path argument + is + description: 'Returns the length of a JSON document, or, if the optional path + argument is given, the length of the value within the document specified by + the path. Returns NULL if any of the arguments argument are null or the path + argument does not identify a value in the document. An error will occur if the + JSON document is invalid, the path is invalid or if the path contains a * or + ** wildcard. Length will be determined as follow: * A scalar''s length is always + 1. * If an array, the number of elements in the array. * If an object, the number + of members in the object. The length of nested arrays or objects are not counted.' + examples: + - sql: 'URL: https://mariadb.com/kb/en/json_length/' + - name: JSON_LOOSE + category_id: json + category_label: JSON Functions + tags: + - json + aliases: [] + signature: + display: JSON_LOOSE(json_doc) + args: + - name: json_doc + optional: false + type: any + summary: Adds spaces to a JSON document to make it look more readable. + description: 'Adds spaces to a JSON document to make it look more readable. Example + ------- SET @j = ''{ "A":1,"B":[2,3]}''; SELECT JSON_LOOSE(@j), @j; +-----------------------+--------------------+ + | JSON_LOOSE(@j) | @j | +-----------------------+--------------------+ + | {"A": 1, "B": [2, 3]} | { "A":1,"B":[2,3]} | +-----------------------+--------------------+ + URL: https://mariadb.com/kb/en/json_loose/' + examples: [] + - name: JSON_MERGE + category_id: json + category_label: JSON Functions + tags: + - json + aliases: [] + signature: + display: JSON_MERGE(json_doc, json_doc[, json_doc] ...) + args: + - name: json_doc + optional: false + type: any + - name: json_doc[ + optional: false + type: any + - name: json_doc] ... + optional: false + type: any + summary: Merges the given JSON documents. + description: 'Merges the given JSON documents. Returns the merged result,or NULL + if any argument is NULL. An error occurs if any of the arguments are not valid + JSON documents. JSON_MERGE has been deprecated since MariaDB 10.2.25, MariaDB + 10.3.16 and MariaDB 10.4.5. JSON_MERGE_PATCH is an RFC 7396-compliant replacement, + and JSON_MERGE_PRESERVE is a synonym. Example ------- SET @json1 = ''[1, 2]''; + SET @json2 = ''[3, 4]''; SELECT JSON_MERGE(@json1,@json2); +---------------------------+ + | JSON_MERGE(@json1,@json2) | +---------------------------+ | [1, 2, 3, 4] | + +---------------------------+ URL: https://mariadb.com/kb/en/json_merge/' + examples: [] + - name: JSON_MERGE_PATCH + category_id: json + category_label: JSON Functions + tags: + - json + aliases: [] + signature: + display: JSON_MERGE_PATCH(json_doc, json_doc[, json_doc] ...) + args: + - name: json_doc + optional: false + type: any + - name: json_doc[ + optional: false + type: any + - name: json_doc] ... + optional: false + type: any + summary: Merges the given JSON documents, returning the merged result, or NULL + if any + description: 'Merges the given JSON documents, returning the merged result, or + NULL if any argument is NULL. JSON_MERGE_PATCH is an RFC 7396-compliant replacement + for JSON_MERGE, which has been deprecated. Unlike JSON_MERGE_PRESERVE, members + with duplicate keys are not preserved. Example ------- SET @json1 = ''[1, 2]''; + SET @json2 = ''[2, 3]''; SELECT JSON_MERGE_PATCH(@json1,@json2),JSON_MERGE_PRESERVE(@json1,@json2); + +---------------------------------+------------------------------------+ | JSON_MERGE_PATCH(@json1,@json2) + | JSON_MERGE_PRESERVE(@json1,@json2) | +---------------------------------+------------------------------------+ + | [2, 3] | [1, 2, 2, 3] | +---------------------------------+------------------------------------+ + URL: https://mariadb.com/kb/en/json_merge_patch/' + examples: [] + - name: JSON_MERGE_PRESERVE + category_id: json + category_label: JSON Functions + tags: + - json + aliases: [] + signature: + display: JSON_MERGE_PRESERVE(json_doc, json_doc[, json_doc] ...) + args: + - name: json_doc + optional: false + type: any + - name: json_doc[ + optional: false + type: any + - name: json_doc] ... + optional: false + type: any + summary: Merges the given JSON documents, returning the merged result, or NULL + if any + description: 'Merges the given JSON documents, returning the merged result, or + NULL if any argument is NULL. JSON_MERGE_PRESERVE was introduced as a synonym + for JSON_MERGE, which has been deprecated. Unlike JSON_MERGE_PATCH, members + with duplicate keys are preserved. Example ------- SET @json1 = ''[1, 2]''; + SET @json2 = ''[2, 3]''; SELECT JSON_MERGE_PATCH(@json1,@json2),JSON_MERGE_PRESERVE(@json1,@json2); + +---------------------------------+------------------------------------+ | JSON_MERGE_PATCH(@json1,@json2) + | JSON_MERGE_PRESERVE(@json1,@json2) | +---------------------------------+------------------------------------+ + | [2, 3] | [1, 2, 2, 3] | +---------------------------------+------------------------------------+ + URL: https://mariadb.com/kb/en/json_merge_preserve/' + examples: [] + - name: JSON_NORMALIZE + category_id: json + category_label: JSON Functions + tags: + - json + aliases: [] + signature: + display: JSON_NORMALIZE(json) + args: + - name: json + optional: false + type: any + summary: Recursively sorts keys and removes spaces, allowing comparison of json + description: Recursively sorts keys and removes spaces, allowing comparison of + json documents for equality. + examples: + - sql: We may wish our application to use the database to enforce a unique constraint + on the JSON contents, and we can do so using the JSON_NORMALIZE function in + combination with a unique key. + result: 'For example, if we have a table with a JSON column:' + - sql: "CREATE TABLE t1 (\n id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,\n val\ + \ JSON,\n /* other columns here */\n PRIMARY KEY (id)\n);" + result: 'Add a unique constraint using JSON_NORMALIZE like this:' + - sql: "ALTER TABLE t1\n ADD COLUMN jnorm JSON AS (JSON_NORMALIZE(val)) VIRTUAL,\n\ + \ ADD UNIQUE KEY (jnorm);" + result: 'We can test this by first inserting a row as normal:' + - sql: INSERT INTO t1 (val) VALUES ('{"name":"alice","color":"blue"}'); + result: And then seeing what happens with a different string which would produce + the + - sql: 'INSERT INTO t1 (val) VALUES (''{ "color": "blue", "name": "alice" }''); + ERROR 1062 (23000): Duplicate entry ''{"color":"blue","name":"alice"}'' for + key ''jnorm''' + result: 'URL: https://mariadb.com/kb/en/json_normalize/' + - name: JSON_OBJECT + category_id: json + category_label: JSON Functions + tags: + - json + aliases: [] + signature: + display: JSON_OBJECT([key, value[, key, value] ...]) + args: + - name: '[key' + optional: false + type: any + - name: value[ + optional: false + type: any + - name: key + optional: false + type: any + - name: value] ...] + optional: false + type: any + summary: Returns a JSON object containing the given key/value pairs. + description: 'Returns a JSON object containing the given key/value pairs. The + key/value list can be empty. An error will occur if there are an odd number + of arguments, or any key name is NULL. Example ------- SELECT JSON_OBJECT("id", + 1, "name", "Monty"); +---------------------------------------+ | JSON_OBJECT("id", + 1, "name", "Monty") | +---------------------------------------+ | {"id": 1, + "name": "Monty"} | +---------------------------------------+ URL: + https://mariadb.com/kb/en/json_object/' + examples: [] + - name: JSON_OBJECTAGG + category_id: json + category_label: JSON Functions + tags: + - json + aliases: [] + signature: + display: JSON_OBJECTAGG(key, value) + args: + - name: key + optional: false + type: any + - name: value + optional: false + type: any + summary: JSON_OBJECTAGG returns a JSON object containing key-value pairs. + description: JSON_OBJECTAGG returns a JSON object containing key-value pairs. + It takes two expressions that evaluate to a single value, or two column names, + as arguments, the first used as a key, and the second as a value. The maximum + returned length in bytes is determined by the group_concat_max_len server system + variable. Returns NULL in the case of an error, or if the result contains no + rows. JSON_OBJECTAGG cannot currently be used as a window function. + examples: + - sql: select * from t1; + result: +------+-------+ | a | b | +------+-------+ | 1 | Hello | + | 1 | World | | 2 | This | +------+-------+ + - sql: SELECT JSON_OBJECTAGG(a, b) FROM t1; + result: +----------------------------------------+ | JSON_OBJECTAGG(a, b) | + +----------------------------------------+ | {"1":"Hello", "1":"World", "2":"This"} + | +----------------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/json_objectagg/' + - name: JSON_OBJECT_FILTER_KEYS + category_id: json + category_label: JSON Functions + tags: + - json + aliases: [] + signature: + display: JSON_OBJECT_FILTER_KEYS(obj, array_keys) + args: + - name: obj + optional: false + type: any + - name: array_keys + optional: false + type: any + summary: JSON_OBJECT_FILTER_KEYS returns a JSON object with keys from the object + that + description: "JSON_OBJECT_FILTER_KEYS returns a JSON object with keys from the\ + \ object that\nare also present in the array as string. It is used when one\ + \ wants to get\nkey-value pair such that the keys are common but the values\ + \ may not be common.\n\nExample\n-------\n\nSET @obj1= '{ \"a\": 1, \"b\": 2,\ + \ \"c\": 3}';\nSET @obj2= '{\"b\" : 10, \"c\": 20, \"d\": 30}';\nSELECT JSON_OBJECT_FILTER_KEYS\ + \ (@obj1, JSON_ARRAY_INTERSECT(JSON_KEYS(@obj1),\nJSON_KEYS(@obj2)));\n+------------------------------------------------------------------------------\n\ + ------------+\n| JSON_OBJECT_FILTER_KEYS (@obj1, JSON_ARRAY_INTERSECT(JSON_KEYS(@obj1),\n\ + JSON_KEYS(@obj2))) |\n+------------------------------------------------------------------------------\n\ + ------------+\n| {\"b\": 2, \"c\": 3} \ + \ \n |\n+------------------------------------------------------------------------------\n\ + ------------+\n\nURL: https://mariadb.com/kb/en/json_object_filter_keys/" + examples: [] + - name: JSON_OBJECT_TO_ARRAY + category_id: json + category_label: JSON Functions + tags: + - json + aliases: [] + signature: + display: JSON_OBJECT_TO_ARRAY(Obj) + args: + - name: Obj + optional: false + type: any + summary: It is used to convert all JSON objects found in a JSON document to JSON + arrays + description: It is used to convert all JSON objects found in a JSON document to + JSON arrays where each item in the outer array represents a single key-value + pair from the object. It is used when we want not just common keys, but also + common values. It can be used in conjunction with JSON_ARRAY_INTERSECT(). + examples: + - sql: 'SET @obj1= ''{ "a": [1, 2, 3], "b": { "key1":"val1", "key2": {"key3":"val3"} + }}'';' + result: 'SELECT JSON_OBJECT_TO_ARRAY(@obj1); +-----------------------------------------------------------------------+ + | JSON_OBJECT_TO_ARRAY(@obj1) | + +-----------------------------------------------------------------------+ + | [["a", [1, 2, 3]], ["b", {"key1": "val1", "key2": {"key3": "val3"}}]] | + +-----------------------------------------------------------------------+' + - sql: 'URL: https://mariadb.com/kb/en/json_object_to_array/' + - name: JSON_OVERLAPS + category_id: json + category_label: JSON Functions + tags: + - json + aliases: [] + signature: + display: JSON_OVERLAPS(json_doc1, json_doc2) + args: + - name: json_doc1 + optional: false + type: any + - name: json_doc2 + optional: false + type: any + summary: JSON_OVERLAPS() compares two json documents and returns true if they + have at + description: JSON_OVERLAPS() compares two json documents and returns true if they + have at least one common key-value pair between two objects, array element common + between two arrays, or array element common with scalar if one of the arguments + is a scalar and other is an array. If two json documents are scalars, it returns + true if they have same type and value. If none of the above conditions are satisfied + then it returns false. + examples: + - sql: SELECT JSON_OVERLAPS('false', 'false'); + result: +---------------------------------+ | JSON_OVERLAPS('false', 'false') + | +---------------------------------+ | 1 | + +---------------------------------+ + - sql: SELECT JSON_OVERLAPS('true', '["abc", 1, 2, true, false]'); + result: +----------------------------------------------------+ | JSON_OVERLAPS('true','["abc", + 1, 2, true, false]') | +----------------------------------------------------+ + | 1 | +----------------------------------------------------+ + - sql: 'SELECT JSON_OVERLAPS(''{"A": 1, "B": {"C":2}}'', ''{"A": 2, "B": {"C":2}}'') + AS is_overlap;' + result: +---------------------+ | is_overlap | +---------------------+ + | 1 | +---------------------+ + - sql: Partial match is considered as no-match. + result: Examples + - sql: SELECT JSON_OVERLAPS('[1, 2, true, false, null]', '[3, 4, [1]]') AS is_overlap; + result: +--------------------- + | is_overlap | +----------------------+ + | 0 | +----------------------+ + - sql: 'URL: https://mariadb.com/kb/en/json_overlaps/' + - name: JSON_QUERY + category_id: json + category_label: JSON Functions + tags: + - json + aliases: [] + signature: + display: JSON_QUERY(json_doc, path) + args: + - name: json_doc + optional: false + type: any + - name: path + optional: false + type: any + summary: Given a JSON document, returns an object or array specified by the path. + description: Given a JSON document, returns an object or array specified by the + path. Returns NULL if not given a valid JSON document, or if there is no match. + examples: + - sql: select json_query('{"key1":{"a":1, "b":[1,2]}}', '$.key1'); + result: +-----------------------------------------------------+ | json_query('{"key1":{"a":1, + "b":[1,2]}}', '$.key1') | +-----------------------------------------------------+ + | {"a":1, "b":[1,2]} | +-----------------------------------------------------+ + - sql: 'select json_query(''{"key1":123, "key1": [1,2,3]}'', ''$.key1'');' + result: '+-------------------------------------------------------+ | json_query(''{"key1":123, + "key1": [1,2,3]}'', ''$.key1'') | +-------------------------------------------------------+ + | [1,2,3] | +-------------------------------------------------------+' + - sql: 'URL: https://mariadb.com/kb/en/json_query/' + - name: JSON_QUOTE + category_id: json + category_label: JSON Functions + tags: + - json + aliases: [] + signature: + display: JSON_QUOTE(json_value) + args: + - name: json_value + optional: false + type: any + summary: Quotes a string as a JSON value, usually for producing valid JSON string + description: Quotes a string as a JSON value, usually for producing valid JSON + string literals for inclusion in JSON documents. Wraps the string with double + quote characters and escapes interior quotes and other special characters, returning + a utf8mb4 string. Returns NULL if the argument is NULL. + examples: + - sql: SELECT JSON_QUOTE('A'), JSON_QUOTE("B"), JSON_QUOTE('"C"'); + result: +-----------------+-----------------+-------------------+ | JSON_QUOTE('A') + | JSON_QUOTE("B") | JSON_QUOTE('"C"') | +-----------------+-----------------+-------------------+ + | "A" | "B" | "\"C\"" | +-----------------+-----------------+-------------------+ + - sql: 'URL: https://mariadb.com/kb/en/json_quote/' + - name: JSON_REMOVE + category_id: json + category_label: JSON Functions + tags: + - json + aliases: [] + signature: + display: JSON_REMOVE(json_doc, path[, path] ...) + args: + - name: json_doc + optional: false + type: any + - name: path[ + optional: false + type: any + - name: path] ... + optional: false + type: any + summary: Removes data from a JSON document returning the result, or NULL if any + of the + description: Removes data from a JSON document returning the result, or NULL if + any of the arguments are null. If the element does not exist in the document, + no changes are made. The function returns NULL and throws a warning if the JSON + document is invalid, the path is invalid, contains a range, or contains a * + or ** wildcard. Path arguments are evaluated from left to right, with the result + from the earlier evaluation being used as the value for the next. + examples: + - sql: 'SELECT JSON_REMOVE(''{"A": 1, "B": 2, "C": {"D": 3}}'', ''$.C'');' + result: '+-------------------------------------------------------+ | JSON_REMOVE(''{"A": + 1, "B": 2, "C": {"D": 3}}'', ''$.C'') | +-------------------------------------------------------+ + | {"A": 1, "B": 2} | +-------------------------------------------------------+' + - sql: SELECT JSON_REMOVE('["A", "B", ["C", "D"], "E"]', '$[1]'); + result: +----------------------------------------------------+ | JSON_REMOVE('["A", + "B", ["C", "D"], "E"]', '$[1]') | +----------------------------------------------------+ + | ["A", ["C", "D"], "E"] | +----------------------------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/json_remove/' + - name: JSON_REPLACE + category_id: json + category_label: JSON Functions + tags: + - json + aliases: [] + signature: + display: JSON_REPLACE(json_doc, path, val[, path, val] ...) + args: + - name: json_doc + optional: false + type: any + - name: path + optional: false + type: any + - name: val[ + optional: false + type: any + - name: path + optional: false + type: any + - name: val] ... + optional: false + type: any + summary: Replaces existing values in a JSON document, returning the result, or + NULL if + description: Replaces existing values in a JSON document, returning the result, + or NULL if any of the arguments are NULL. An error will occur if the JSON document + is invalid, the path is invalid or if the path contains a * or ** wildcard. + Paths and values are evaluated from left to right, with the result from the + earlier evaluation being used as the value for the next. JSON_REPLACE can only + update data, while JSON_INSERT can only insert. JSON_SET can update or insert + data. + examples: + - sql: 'SELECT JSON_REPLACE(''{ "A": 1, "B": [2, 3]}'', ''$.B[1]'', 4);' + result: '+-----------------------------------------------------+ | JSON_REPLACE(''{ + "A": 1, "B": [2, 3]}'', ''$.B[1]'', 4) | +-----------------------------------------------------+ + | { "A": 1, "B": [2, 4]} | +-----------------------------------------------------+' + - sql: 'URL: https://mariadb.com/kb/en/json_replace/' + - name: JSON_SCHEMA_VALID + category_id: json + category_label: JSON Functions + tags: + - json + aliases: [] + signature: + display: JSON_SCHEMA_VALID(schema, json) + args: + - name: schema + optional: false + type: any + - name: json + optional: false + type: any + summary: JSON_SCHEMA_VALID allows MariaDB to support JSON schema validation. + description: 'JSON_SCHEMA_VALID allows MariaDB to support JSON schema validation. + If a given json is valid against a schema it returns true. When JSON does not + validate against the schema, it does not return a message about which keyword + it failed against and only returns false. The function supports JSON Schema + Draft 2020 with a few exceptions: * External resources are not supported * Hyper + schema keywords are not supported * Formats like date, email etc are treated + as annotations.' + examples: + - sql: To create validation rules for json field + result: CREATE TABLE obj_table(val_obj JSON CHECK(JSON_SCHEMA_VALID('{ + - sql: "\"properties\": {\n \"number1\":{\n \"type\":\"number\",\n \"\ + maximum\":5,\n \"const\":4\n },\n \"string1\":{\n \"type\":\"string\"\ + ,\n \"maxLength\":5,\n \"minLength\":3\n },\n \"object1\":{\n \"\ + type\":\"object\",\n \"properties\":{\n \"key1\": {\"type\":\"string\"\ + },\n \"key2\":{\"type\":\"array\"},\n \"key3\":{\"type\":\"number\"\ + , \"minimum\":3}\n },\n \"dependentRequired\": { \"key1\":[\"key3\"] }\n\ + \ }\n },\n \"required\":[\"number1\",\"object1\"]\n }', val_obj)));" + result: INSERT INTO obj_table VALUES( + - sql: '"object1":{"key1":"val1", "key2":[1,2,3, "string1"], "key3":4}}'' );' + result: INSERT INTO obj_table VALUES( + - sql: "\"object1\":{\"key1\":\"val1\", \"key2\":[1,2,3, \"string1\"], \"key3\"\ + :4}}'\n ..." + - name: JSON_SEARCH + category_id: json + category_label: JSON Functions + tags: + - json + aliases: [] + signature: + display: JSON_SEARCH(json_doc, return_arg, search_str[, escape_char[, path] + ...]) + args: + - name: json_doc + optional: false + type: any + - name: return_arg + optional: false + type: any + - name: search_str[ + optional: false + type: any + - name: escape_char[ + optional: false + type: any + - name: path] ...] + optional: false + type: any + summary: Returns the path to the given string within a JSON document, or NULL + if any of + description: 'Returns the path to the given string within a JSON document, or + NULL if any of json_doc, search_str or a path argument is NULL; if the search + string is not found, or if no path exists within the document. A warning will + occur if the JSON document is not valid, any of the path arguments are not valid, + if return_arg is neither one nor all, or if the escape character is not a constant. + NULL will be returned. return_arg can be one of two values: * ''one: Terminates + after finding the first match, so will return one path string. If there is more + than one match, it is undefined which is considered first. * all: Returns all + matching path strings, without duplicates. Multiple strings are autowrapped + as an array. The order is undefined.' + examples: + - sql: 'SET @json = ''["A", [{"B": "1"}], {"C":"AB"}, {"D":"BC"}]'';' + result: SELECT JSON_SEARCH(@json, 'one', 'AB'); +---------------------------------+ + | JSON_SEARCH(@json, 'one', 'AB') | +---------------------------------+ | + "$[2].C" | +---------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/json_search/' + - name: JSON_SET + category_id: json + category_label: JSON Functions + tags: + - json + aliases: [] + signature: + display: JSON_SET(json_doc, path, val[, path, val] ...) + args: + - name: json_doc + optional: false + type: any + - name: path + optional: false + type: any + - name: val[ + optional: false + type: any + - name: path + optional: false + type: any + - name: val] ... + optional: false + type: any + summary: Updates or inserts data into a JSON document, returning the result, or + NULL if + description: Updates or inserts data into a JSON document, returning the result, + or NULL if any of the arguments are NULL or the optional path fails to find + an object. An error will occur if the JSON document is invalid, the path is + invalid or if the path contains a * or wildcard. JSON_SET can update or insert + data, while JSON_REPLACE can only update, and JSON_INSERT only insert. + examples: + - sql: SELECT JSON_SET(Priv, '$.locked', 'true') FROM mysql.global_priv + result: 'URL: https://mariadb.com/kb/en/json_set/' + - name: JSON_TABLE + category_id: json + category_label: JSON Functions + tags: + - json + aliases: [] + signature: + display: JSON_TABLE(json_doc, context_path COLUMNS (column_list) + args: + - name: json_doc + optional: false + type: any + - name: context_path COLUMNS (column_list + optional: false + type: any + summary: JSON_TABLE can be used in contexts where a table reference can be used; + in the + description: "JSON_TABLE can be used in contexts where a table reference can be\ + \ used; in the\nFROM clause of a SELECT statement, and in multi-table UPDATE/DELETE\ + \ statements.\n\njson_doc is the JSON document to extract data from. In the\ + \ simplest case, it\nis a string literal containing JSON. In more complex cases\ + \ it can be an\narbitrary expression returning JSON. The expression may have\ + \ references to\ncolumns of other tables. However, one can only refer to tables\ + \ that precede\nthis JSON_TABLE invocation. For RIGHT JOIN, it is assumed that\ + \ its outer side\nprecedes the inner. All tables in outer selects are also considered\ + \ preceding.\n\ncontext_path is a JSON Path expression pointing to a collection\ + \ of nodes in\njson_doc that will be used as the source of rows.\n\nThe COLUMNS\ + \ clause declares the names and types of the columns that JSON_TABLE\nreturns,\ + \ as well as how the values of the columns are produced.\n\nColumn Definitions\n\ + ------------------\n\nThe following types of columns are supported:\n\nPath\ + \ Columns\n------------\n\nname type PATH path_str [on_empty] [on_error]\n\n\ + Locates the JSON node pointed to by path_str and returns its value. The\npath_str\ + \ is evaluated using the current row source node as the context node.\n\nset\ + \ @json='\n[\n {\"name\":\"Laptop\", \"color\":\"black\", \"price\":\"1000\"\ + },\n {\"name\":\"Jeans\", \"color\":\"blue\"}\n]';\n\nselect * from json_table(@json,\ + \ '$[*]' \n columns(\n name varchar(10) path '$.name',\n color varchar(10)\ + \ path '$.color',\n price decimal(8,2) path '$.price' )\n) as jt;\n+--------+-------+---------+\n\ + | name | color | price |\n+--------+-------+---------+\n| Laptop | black\ + \ | 1000.00 |\n| Jeans | blue | NULL |\n+--------+-------+---------+\n\n\ + The on_empty and on_error clauses specify the actions to be performed when the\n\ + value was not found or there was an error condition. See the ON EMPTY and ON\n\ + \ ..." + examples: [] + - name: JSON_TYPE + category_id: json + category_label: JSON Functions + tags: + - json + aliases: [] + signature: + display: JSON_TYPE(json_val) + args: + - name: json_val + optional: false + type: any + summary: Returns the type of a JSON value (as a string), or NULL if the argument + is + description: 'Returns the type of a JSON value (as a string), or NULL if the argument + is null. An error will occur if the argument is an invalid JSON value. The following + is a complete list of the possible return types: +-----------------------------------+-----------------+-----------------------+ + | Return type | Value | Example | + +-----------------------------------+-----------------+-----------------------+ + | ARRAY | JSON array | [1, 2, {"key": | + | | | "value"}] | + +-----------------------------------+-----------------+-----------------------+ + | OBJECT | JSON object | {"key":"value"} | + +-----------------------------------+-----------------+-----------------------+ + | BOOLEAN | JSON | true, false | + | | true/false | | + | | literals | | + +-----------------------------------+-----------------+-----------------------+ + | DOUBLE | A number with | 1.2 | + | | at least one | | + | | floating point | | + | | decimal. | | + +-----------------------------------+-----------------+-----------------------+ + | INTEGER | A number | 1 | + | | without a | | + | | floating point | | + | | decimal. | | + +-----------------------------------+-----------------+-----------------------+ + | NULL | JSON null | null | + | | literal (this | | + | | is returned as | | + | | a string, not | | + | | to be confused | | + | | with the SQL | | + | | NULL value!) | | + +-----------------------------------+-----------------+-----------------------+ + | STRING | JSON String | "a sample string" | + +-----------------------------------+-----------------+-----------------------+' + examples: + - sql: 'SELECT JSON_TYPE(''{"A": 1, "B": 2, "C": 3}'');' + result: '+---------------------------------------+ | JSON_TYPE(''{"A": 1, "B": + 2, "C": 3}'') | +---------------------------------------+ | OBJECT | + +---------------------------------------+' + - sql: 'URL: https://mariadb.com/kb/en/json_type/' + - name: JSON_UNQUOTE + category_id: json + category_label: JSON Functions + tags: + - json + aliases: [] + signature: + display: JSON_UNQUOTE(val) + args: + - name: val + optional: false + type: any + summary: Unquotes a JSON value, returning a string, or NULL if the argument is + null. + description: "Unquotes a JSON value, returning a string, or NULL if the argument\ + \ is null.\n\nAn error will occur if the given value begins and ends with double\ + \ quotes and\nis an invalid JSON string literal.\n\nIf the given value is not\ + \ a JSON string, value is passed through unmodified.\n\nCertain character sequences\ + \ have special meanings within a string. Usually, a\nbackslash is ignored, but\ + \ the escape sequences in the table below are\nrecognised by MariaDB, unless\ + \ the SQL Mode is set to NO_BACKSLASH_ESCAPES SQL.\n\n+-----------------------------------------------+-----------------------------+\n\ + | Escape sequence | Character \ + \ |\n+-----------------------------------------------+-----------------------------+\n\ + | \\\" | Double quote (\") \ + \ |\n+-----------------------------------------------+-----------------------------+\n\ + | \\b | Backslash \ + \ |\n+-----------------------------------------------+-----------------------------+\n\ + | \\f | Formfeed \ + \ |\n+-----------------------------------------------+-----------------------------+\n\ + | \n | Newline (linefeed) \ + \ |\n+-----------------------------------------------+-----------------------------+\n\ + | \n | Carriage return \ + \ |\n+-----------------------------------------------+-----------------------------+\n\ + | \t | Tab \ + \ |\n+-----------------------------------------------+-----------------------------+\n\ + | \\\\ | Backslash (\\) \ + \ |\n+-----------------------------------------------+-----------------------------+\n\ + | \\uXXXX | UTF-8 bytes for Unicode \ + \ |\n| | value XXXX \ + \ |\n+-----------------------------------------------+-----------------------------+" + examples: + - sql: SELECT JSON_UNQUOTE('"Monty"'); + result: +-------------------------+ | JSON_UNQUOTE('"Monty"') | +-------------------------+ + | Monty | +-------------------------+ + - sql: 'With the default SQL Mode:' + result: "SELECT JSON_UNQUOTE('Si\\bng\ting');\n+-----------------------------+\n\ + | JSON_UNQUOTE('Si\\bng\ting') |\n+-----------------------------+\n| Sng\t\ + ing |\n+-----------------------------+" + - name: JSON_VALID + category_id: json + category_label: JSON Functions + tags: + - json + aliases: [] + signature: + display: JSON_VALID(value) + args: + - name: value + optional: false + type: any + summary: Indicates whether the given value is a valid JSON document or not. + description: Indicates whether the given value is a valid JSON document or not. + Returns 1 if valid, 0 if not, and NULL if the argument is NULL. From MariaDB + 10.4.3, the JSON_VALID function is automatically used as a CHECK constraint + for the JSON data type alias in order to ensure that a valid json document is + inserted. + examples: + - sql: 'SELECT JSON_VALID(''{"id": 1, "name": "Monty"}'');' + result: '+------------------------------------------+ | JSON_VALID(''{"id": + 1, "name": "Monty"}'') | +------------------------------------------+ | 1 + | +------------------------------------------+' + - sql: 'SELECT JSON_VALID(''{"id": 1, "name": "Monty", "oddfield"}'');' + result: '+------------------------------------------------------+ | JSON_VALID(''{"id": + 1, "name": "Monty", "oddfield"}'') | +------------------------------------------------------+ + | 0 | +------------------------------------------------------+' + - sql: 'URL: https://mariadb.com/kb/en/json_valid/' + - name: JSON_VALUE + category_id: json + category_label: JSON Functions + tags: + - json + aliases: [] + signature: + display: JSON_VALUE(json_doc, path) + args: + - name: json_doc + optional: false + type: any + - name: path + optional: false + type: any + summary: Given a JSON document, returns the scalar specified by the path. + description: Given a JSON document, returns the scalar specified by the path. + Returns NULL if not given a valid JSON document, or if there is no match. + examples: + - sql: select json_value('{"key1":123}', '$.key1'); + result: +--------------------------------------+ | json_value('{"key1":123}', + '$.key1') | +--------------------------------------+ | 123 | + +--------------------------------------+ + - sql: 'select json_value(''{"key1": [1,2,3], "key1":123}'', ''$.key1'');' + result: '+-------------------------------------------------------+ | json_value(''{"key1": + [1,2,3], "key1":123}'', ''$.key1'') | +-------------------------------------------------------+ + | 123 | +-------------------------------------------------------+' + - sql: In the SET statement below, two escape characters are needed, as a single + escape character would be applied by the SQL parser in the SET statement, + and the escaped character would not form part of the saved value. + result: SET @json = '{"key1":"60\\" Table", "key2":"1"}'; + - sql: SELECT JSON_VALUE(@json,'$.key1') AS Name , json_value(@json,'$.key2') + as ID; + result: +-----------+------+ | Name | ID | +-----------+------+ | 60" + Table | 1 | +-----------+------+ + - sql: 'URL: https://mariadb.com/kb/en/json_value/' + - name: KDF + category_id: encryption + category_label: Encryption Functions + tags: + - encryption + aliases: [] + signature: + display: KDF + args: [] + summary: KDF is a key derivation function, similar to OpenSSL's EVP_KDF_derive(). + description: KDF is a key derivation function, similar to OpenSSL's EVP_KDF_derive(). + The purpose of a KDF is to be slow, so if the calculated value is lost/stolen, + the original key_str is not achievable easily with modern GPU. KDFs are therefore + an ideal replacement for password hashes. KDFs can also pad out a password secret + to the number of bits used in encryption algorithms. For generating good encryption + keys for AES_ENCRYPT a less expensive function, but cryptographically secure + function like RANDOM_BYTES is recommended.. * kdf_name is "hkdf" or "pbkdf2_hmac" + (default) * width (in bits) can be any number divisible by 8, by default it's + taken from @@block_encryption_mode * iterations must be positive, and is 1000 + by default Note that OpenSSL 1.0 doesn't support HKDF, so in this case NULL + is returned. This OpenSSL version is still used in SLES 12 and CentOS 7. + examples: + - sql: select hex(kdf('foo', 'bar', 'infa', 'hkdf')); + result: +----------------------------------------+ | hex(kdf('foo', 'bar', 'infa', + 'hkdf')) | +----------------------------------------+ | 612875F859CFB4EE0DFEFF9F2A18E836 | + +----------------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/kdf/' + - name: LAG + category_id: window + category_label: Window Functions + tags: + - window + aliases: [] + signature: + display: LAG(expr[, offset]) + args: + - name: expr[ + optional: false + type: any + - name: offset] + optional: false + type: any + summary: The LAG function accesses data from a previous row according to the ORDER + BY + description: The LAG function accesses data from a previous row according to the + ORDER BY clause without the need for a self-join. The specific row is determined + by the offset (default 1), which specifies the number of rows behind the current + row to use. An offset of 0 is the current row. + examples: + - sql: CREATE TABLE t1 (pk int primary key, a int, b int, c char(10), d decimal(10, + 3), e real); + result: INSERT INTO t1 VALUES + - sql: "( 2, 0, 2, 'two', 0.2, 0.002),\n ( 3, 0, 3, 'three', 0.3, \ + \ 0.003),\n ( 4, 1, 2, 'three', 0.4, 0.004),\n ( 5, 1, 1, 'two', \ + \ 0.5, 0.005),\n ( 6, 1, 1, 'one', 0.6, 0.006),\n ( 7, 2, NULL,\ + \ 'n_one', 0.5, 0.007),\n ( 8, 2, 1, 'n_two', NULL, 0.008),\n ( 9, 2,\ + \ 2, NULL, 0.7, 0.009),\n (10, 2, 0, 'n_four', 0.8, 0.010),\n\ + \ (11, 2, 10, NULL, 0.9, NULL);" + result: SELECT pk, LAG(pk) OVER (ORDER BY pk) AS l, + - sql: "LAG(pk,2) OVER (ORDER BY pk) AS l2,\n LAG(pk,0) OVER (ORDER BY pk) AS\ + \ l0,\n LAG(pk,-1) OVER (ORDER BY pk) AS lm1,\n LAG(pk,-2) OVER (ORDER BY\ + \ pk) AS lm2\nFROM t1;" + result: +----+------+------+------+------+------+------+ | pk | l | l1 | + l2 | l0 | lm1 | lm2 | +----+------+------+------+------+------+------+ + | 1 | NULL | NULL | NULL | 1 | 2 | 3 | | 2 | 1 | 1 | NULL + | 2 | 3 | 4 | | 3 | 2 | 2 | 1 | 3 | 4 | 5 | | 4 + | 3 | 3 | 2 | 4 | 5 | 6 | | 5 | 4 | 4 | 3 | 5 + | 6 | 7 | | 6 | 5 | 5 | 4 | 6 | 7 | 8 | | 7 | 6 + | 6 | 5 | 7 | 8 | 9 | | 8 | 7 | 7 | 6 | 8 | 9 + | 10 | | 9 | 8 | 8 | 7 | 9 | 10 | 11 | | 10 | 9 | 9 + | 8 | 10 | 11 | NULL | | 11 | 10 | 10 | 9 | 11 | NULL | NULL + | +----+------+------+------+------+------+------+ + - sql: 'URL: https://mariadb.com/kb/en/lag/' + - name: LAST_DAY + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: LAST_DAY(date) + args: + - name: date + optional: false + type: any + summary: Takes a date or datetime value and returns the corresponding value for + the + description: Takes a date or datetime value and returns the corresponding value + for the last day of the month. Returns NULL if the argument is invalid. + examples: + - sql: SELECT LAST_DAY('2003-02-05'); + result: +------------------------+ | LAST_DAY('2003-02-05') | +------------------------+ + | 2003-02-28 | +------------------------+ + - sql: SELECT LAST_DAY('2004-02-05'); + result: +------------------------+ | LAST_DAY('2004-02-05') | +------------------------+ + | 2004-02-29 | +------------------------+ + - sql: SELECT LAST_DAY('2004-01-01 01:01:01'); + result: +---------------------------------+ | LAST_DAY('2004-01-01 01:01:01') + | +---------------------------------+ | 2004-01-31 | + +---------------------------------+ + - sql: SELECT LAST_DAY('2003-03-32'); + result: +------------------------+ | LAST_DAY('2003-03-32') | +------------------------+ + | NULL | +------------------------+ + - sql: 'Warning (Code 1292): Incorrect datetime value: ''2003-03-32''' + result: 'URL: https://mariadb.com/kb/en/last_day/' + - name: LAST_INSERT_ID + category_id: information + category_label: Information Functions + tags: + - information + aliases: [] + signature: + display: LAST_INSERT_ID + args: [] + summary: LAST_INSERT_ID() (no arguments) returns the first automatically generated + description: "LAST_INSERT_ID() (no arguments) returns the first automatically\ + \ generated\nvalue successfully inserted for an AUTO_INCREMENT column as a result\ + \ of the\nmost recently executed INSERT statement. The value of LAST_INSERT_ID()\ + \ remains\nunchanged if no rows are successfully inserted.\n\nIf one gives an\ + \ argument to LAST_INSERT_ID(), then it will return the value of\nthe expression\ + \ and the next call to LAST_INSERT_ID() will return the same\nvalue. The value\ + \ will also be sent to the client and can be accessed by the\nmysql_insert_id\ + \ function.\n\nFor example, after inserting a row that generates an AUTO_INCREMENT\ + \ value, you\ncan get the value like this:\n\nSELECT LAST_INSERT_ID();\n+------------------+\n\ + | LAST_INSERT_ID() |\n+------------------+\n| 9 |\n+------------------+\n\ + \nYou can also use LAST_INSERT_ID() to delete the last inserted row:\n\nDELETE\ + \ FROM product WHERE id = LAST_INSERT_ID();\n\nIf no rows were successfully\ + \ inserted, LAST_INSERT_ID() returns 0.\n\nThe value of LAST_INSERT_ID() will\ + \ be consistent across all versions if all\nrows in the INSERT or UPDATE statement\ + \ were successful.\n\nThe currently executing statement does not affect the\ + \ value of\nLAST_INSERT_ID(). Suppose that you generate an AUTO_INCREMENT value\ + \ with one\nstatement, and then refer to LAST_INSERT_ID() in a multiple-row\ + \ INSERT\nstatement that inserts rows into a table with its own AUTO_INCREMENT\ + \ column.\nThe value of LAST_INSERT_ID() will remain stable in the second statement;\ + \ its\nvalue for the second and later rows is not affected by the earlier row\n\ + insertions. (However, if you mix references to LAST_INSERT_ID() and\nLAST_INSERT_ID(expr),\ + \ the effect is undefined.)\n\nIf the previous statement returned an error,\ + \ the value of LAST_INSERT_ID() is\nundefined. For transactional tables, if\ + \ the statement is rolled back due to an\nerror, the value of LAST_INSERT_ID()\ + \ is left undefined. For manual ROLLBACK,\nthe value of LAST_INSERT_ID() is\ + \ not restored to that before the transaction;\nit remains as it was at the\ + \ point of the ROLLBACK.\n\nWithin the body of a stored routine (procedure or\ + \ function) or a trigger, the\nvalue of LAST_INSERT_ID() changes the same way\ + \ as for statements executed\noutside the body of these kinds of objects. The\ + \ effect of a stored routine or\ntrigger upon the value of LAST_INSERT_ID()\ + \ that is seen by following\nstatements depends on the kind of routine:\n\n\ + \ ..." + examples: [] + - name: LAST_VALUE + category_id: information + category_label: Information Functions + tags: + - information + aliases: [] + signature: + display: LAST_VALUE(expr,[expr,...]) + args: + - name: expr + optional: false + type: any + - name: '[expr' + optional: false + type: any + - name: '...]' + optional: false + type: any + summary: LAST_VALUE() evaluates all expressions and returns the last. + description: LAST_VALUE() evaluates all expressions and returns the last. This + is useful together with setting user variables to a value with @var:=expr, for + example when you want to get data of rows updated/deleted without having to + do two queries against the table. LAST_VALUE can be used as a window function. + Returns NULL if no last value exists. + examples: + - sql: CREATE TABLE t1 (a int, b int); INSERT INTO t1 VALUES(1,10),(2,20); DELETE + FROM t1 WHERE a=1 AND last_value(@a:=a,@b:=b,1); SELECT @a,@b; + result: +------+------+ | @a | @b | +------+------+ | 1 | 10 | +------+------+ + - sql: 'As a window function:' + result: CREATE TABLE t1 ( + - sql: "a int,\n b int,\n c char(10),\n d decimal(10, 3),\n e real\n);" + result: INSERT INTO t1 VALUES + - sql: ( 2, 0, 2, 'two', 0.2, 0.002), ( 3, 0, 3, 'three', 0.3, 0.003), + ( 4, 1, 2, 'three', 0.4, 0.004), ( 5, 1, 1, 'two', 0.5, 0.005), + ( 6, 1, 1, 'one', 0.6, 0.006), ( 7, 2, NULL, 'n_one', 0.5, 0.007), + ( 8, 2, 1, 'n_two', NULL, 0.008), ( 9, 2, 2, NULL, 0.7, 0.009), + (10, 2, 0, 'n_four', 0.8, 0.010), (11, 2, 10, NULL, 0.9, NULL); + result: SELECT pk, FIRST_VALUE(pk) OVER (ORDER BY pk) AS first_asc, + - sql: "FIRST_VALUE(pk) OVER (ORDER BY pk DESC) AS first_desc,\n ..." + - name: LCASE + category_id: string + category_label: String Functions + tags: + - string + aliases: + - LOWER + signature: + display: LCASE(str) + args: + - name: str + optional: false + type: any + summary: LCASE() is a synonym for LOWER(). + description: 'LCASE() is a synonym for LOWER(). URL: https://mariadb.com/kb/en/lcase/' + examples: [] + - name: LEAD + category_id: window + category_label: Window Functions + tags: + - window + aliases: [] + signature: + display: LEAD(expr[, offset]) + args: + - name: expr[ + optional: false + type: any + - name: offset] + optional: false + type: any + summary: The LEAD function accesses data from a following row in the same result + set + description: "The LEAD function accesses data from a following row in the same\ + \ result set\nwithout the need for a self-join. The specific row is determined\ + \ by the offset\n(default 1), which specifies the number of rows ahead the current\ + \ row to use.\nAn offset of 0 is the current row.\n\nExample\n-------\n\nCREATE\ + \ TABLE t1 (pk int primary key, a int, b int, c char(10), d decimal(10,\n3),\ + \ e real);\n\nINSERT INTO t1 VALUES\n ( 1, 0, 1, 'one', 0.1, 0.001),\n\ + \ ( 2, 0, 2, 'two', 0.2, 0.002),\n ( 3, 0, 3, 'three', 0.3, 0.003),\n\ + \ ( 4, 1, 2, 'three', 0.4, 0.004),\n ( 5, 1, 1, 'two', 0.5, 0.005),\n\ + \ ( 6, 1, 1, 'one', 0.6, 0.006),\n ( 7, 2, NULL, 'n_one', 0.5, 0.007),\n\ + \ ( 8, 2, 1, 'n_two', NULL, 0.008),\n ( 9, 2, 2, NULL, 0.7, 0.009),\n\ + \ (10, 2, 0, 'n_four', 0.8, 0.010),\n (11, 2, 10, NULL, 0.9, NULL);\n\ + \nSELECT pk, LEAD(pk) OVER (ORDER BY pk) AS l,\n LEAD(pk,1) OVER (ORDER BY pk)\ + \ AS l1,\n LEAD(pk,2) OVER (ORDER BY pk) AS l2,\n LEAD(pk,0) OVER (ORDER BY\ + \ pk) AS l0,\n LEAD(pk,-1) OVER (ORDER BY pk) AS lm1,\n LEAD(pk,-2) OVER (ORDER\ + \ BY pk) AS lm2\nFROM t1;\n+----+------+------+------+------+------+------+\n\ + | pk | l | l1 | l2 | l0 | lm1 | lm2 |\n+----+------+------+------+------+------+------+\n\ + | 1 | 2 | 2 | 3 | 1 | NULL | NULL |\n| 2 | 3 | 3 | 4\ + \ | 2 | 1 | NULL |\n| 3 | 4 | 4 | 5 | 3 | 2 | 1 |\n\ + | 4 | 5 | 5 | 6 | 4 | 3 | 2 |\n| 5 | 6 | 6 | 7\ + \ | 5 | 4 | 3 |\n| 6 | 7 | 7 | 8 | 6 | 5 | 4 |\n\ + | 7 | 8 | 8 | 9 | 7 | 6 | 5 |\n| 8 | 9 | 9 | 10\ + \ | 8 | 7 | 6 |\n| 9 | 10 | 10 | 11 | 9 | 8 | 7 |\n\ + | 10 | 11 | 11 | NULL | 10 | 9 | 8 |\n| 11 | NULL | NULL | NULL\ + \ | 11 | 10 | 9 |\n+----+------+------+------+------+------+------+\n\ + \nURL: https://mariadb.com/kb/en/lead/" + examples: [] + - name: LEAST + category_id: comparison_operators + category_label: Comparison Operators + tags: + - comparison_operators + aliases: [] + signature: + display: LEAST(value1,value2,...) + args: + - name: value1 + optional: false + type: any + - name: value2 + optional: false + type: any + - name: '...' + optional: false + type: any + summary: With two or more arguments, returns the smallest (minimum-valued) argument. + description: 'With two or more arguments, returns the smallest (minimum-valued) + argument. The arguments are compared using the following rules: * If the return + value is used in an INTEGER context or all arguments are integer-valued, they + are compared as integers. * If the return value is used in a REAL context or + all arguments are real-valued, they are compared as reals. * If any argument + is a case-sensitive string, the arguments are compared as case-sensitive strings. + * In all other cases, the arguments are compared as case-insensitive strings. + LEAST() returns NULL if any argument is NULL.' + examples: + - sql: SELECT LEAST(2,0); + result: +------------+ | LEAST(2,0) | +------------+ | 0 | +------------+ + - sql: SELECT LEAST(34.0,3.0,5.0,767.0); + result: +---------------------------+ | LEAST(34.0,3.0,5.0,767.0) | +---------------------------+ + | 3.0 | +---------------------------+ + - sql: SELECT LEAST('B','A','C'); + result: +--------------------+ | LEAST('B','A','C') | +--------------------+ + | A | +--------------------+ + - sql: 'URL: https://mariadb.com/kb/en/least/' + - name: LEFT + category_id: string + category_label: String Functions + tags: + - string + aliases: [] + signature: + display: LEFT(str,len) + args: + - name: str + optional: false + type: any + - name: len + optional: false + type: any + summary: Returns the leftmost len characters from the string str, or NULL if any + description: Returns the leftmost len characters from the string str, or NULL + if any argument is NULL. + examples: + - sql: SELECT LEFT('MariaDB', 5); + result: +--------------------+ | LEFT('MariaDB', 5) | +--------------------+ + | Maria | +--------------------+ + - sql: 'URL: https://mariadb.com/kb/en/left/' + - name: LENGTH + category_id: string + category_label: String Functions + tags: + - string + aliases: + - CHAR_LENGTH + signature: + display: LENGTH(str) + args: + - name: str + optional: false + type: any + summary: Returns the length of the string str. + description: Returns the length of the string str. In the default mode, when Oracle + mode from MariaDB 10.3 is not set, the length is measured in bytes. In this + case, a multi-byte character counts as multiple bytes. This means that for a + string containing five two-byte characters, LENGTH() returns 10, whereas CHAR_LENGTH() + returns 5. When running Oracle mode from MariaDB 10.3, the length is measured + in characters, and LENGTH is a synonym for CHAR_LENGTH(). If str is not a string + value, it is converted into a string. If str is NULL, the function returns NULL. + examples: + - sql: SELECT LENGTH('MariaDB'); + result: +-------------------+ | LENGTH('MariaDB') | +-------------------+ | 7 + | +-------------------+ + - sql: 'When Oracle mode from MariaDB 10.3 is not set:' + result: "SELECT CHAR_LENGTH('\u03C0'), LENGTH('\u03C0'), LENGTHB('\u03C0'),\ + \ OCTET_LENGTH('\u03C0');\n+-------------------+--------------+---------------+--------------------+\n\ + | CHAR_LENGTH('\u03C0') | LENGTH('\u03C0') | LENGTHB('\u03C0') | OCTET_LENGTH('\u03C0\ + ') |\n+-------------------+--------------+---------------+--------------------+\n\ + | 1 | 2 | 2 | 2 |\n\ + +-------------------+--------------+---------------+--------------------+" + - sql: 'In Oracle mode from MariaDB 10.3:' + result: "SELECT CHAR_LENGTH('\u03C0'), LENGTH('\u03C0'), LENGTHB('\u03C0'),\ + \ OCTET_LENGTH('\u03C0');\n+-------------------+--------------+---------------+--------------------+\n\ + | CHAR_LENGTH('\u03C0') | LENGTH('\u03C0') | LENGTHB('\u03C0') | OCTET_LENGTH('\u03C0\ + ') |\n+-------------------+--------------+---------------+--------------------+\n\ + | 1 | 1 | 2 | 2 |\n\ + +-------------------+--------------+---------------+--------------------+" + - sql: 'URL: https://mariadb.com/kb/en/length/' + - name: LENGTHB + category_id: string + category_label: String Functions + tags: + - string + aliases: [] + signature: + display: LENGTHB(str) + args: + - name: str + optional: false + type: any + summary: LENGTHB() returns the length of the given string, in bytes. + description: LENGTHB() returns the length of the given string, in bytes. When + Oracle mode is not set, this is a synonym for LENGTH. A multi-byte character + counts as multiple bytes. This means that for a string containing five two-byte + characters, LENGTHB() returns 10, whereas CHAR_LENGTH() returns 5. If str is + not a string value, it is converted into a string. If str is NULL, the function + returns NULL. + examples: + - sql: 'When Oracle mode from MariaDB 10.3 is not set:' + result: "SELECT CHAR_LENGTH('\u03C0'), LENGTH('\u03C0'), LENGTHB('\u03C0'),\ + \ OCTET_LENGTH('\u03C0');\n+-------------------+--------------+---------------+--------------------+\n\ + | CHAR_LENGTH('\u03C0') | LENGTH('\u03C0') | LENGTHB('\u03C0') | OCTET_LENGTH('\u03C0\ + ') |\n+-------------------+--------------+---------------+--------------------+\n\ + | 1 | 2 | 2 | 2 |\n\ + +-------------------+--------------+---------------+--------------------+" + - sql: 'In Oracle mode from MariaDB 10.3:' + result: "SELECT CHAR_LENGTH('\u03C0'), LENGTH('\u03C0'), LENGTHB('\u03C0'),\ + \ OCTET_LENGTH('\u03C0');\n+-------------------+--------------+---------------+--------------------+\n\ + | CHAR_LENGTH('\u03C0') | LENGTH('\u03C0') | LENGTHB('\u03C0') | OCTET_LENGTH('\u03C0\ + ') |\n+-------------------+--------------+---------------+--------------------+\n\ + | 1 | 1 | 2 | 2 |\n\ + +-------------------+--------------+---------------+--------------------+" + - sql: 'URL: https://mariadb.com/kb/en/lengthb/' + - name: LIMIT + category_id: data_manipulation + category_label: Data Manipulation + tags: + - data_manipulation + aliases: [] + signature: + display: LIMIT(or ORDER BY) + args: + - name: or ORDER BY + optional: false + type: any + summary: multi-table UPDATE statement. + description: multi-table UPDATE statement. This restriction was lifted in MariaDB + 10.3.2. GROUP_CONCAT ------------ Starting from MariaDB 10.3.3, it is possible + to use LIMIT with GROUP_CONCAT(). + examples: + - sql: CREATE TABLE members (name VARCHAR(20)); INSERT INTO members VALUES('Jagdish'),('Kenny'),('Rokurou'),('Immaculada'); + result: SELECT * FROM members; +------------+ | name | +------------+ + | Jagdish | | Kenny | | Rokurou | | Immaculada | +------------+ + - sql: 'Select the first two names (no ordering specified):' + result: SELECT * FROM members LIMIT 2; +---------+ | name | +---------+ | + Jagdish | | Kenny | +---------+ + - sql: 'All the names in alphabetical order:' + result: SELECT * FROM members ORDER BY name; +------------+ | name | +------------+ + | Immaculada | | Jagdish | | Kenny | | Rokurou | +------------+ + - sql: 'The first two names, ordered alphabetically:' + result: SELECT * FROM members ORDER BY name LIMIT 2; +------------+ | name | + - name: LINESTRING + category_id: geometry_constructors + category_label: Geometry Constructors + tags: + - geometry_constructors + aliases: [] + signature: + display: LINESTRING(pt1,pt2,...) + args: + - name: pt1 + optional: false + type: any + - name: pt2 + optional: false + type: any + - name: '...' + optional: false + type: any + summary: Constructs a WKB LineString value from a number of WKB Point arguments. + description: Constructs a WKB LineString value from a number of WKB Point arguments. + If any argument is not a WKB Point, the return value is NULL. If the number + of Point arguments is less than two, the return value is NULL. + examples: + - sql: SET @ls = 'LineString(1 1,2 2,3 3)'; + result: SELECT AsText(EndPoint(GeomFromText(@ls))); +-------------------------------------+ + | AsText(EndPoint(GeomFromText(@ls))) | +-------------------------------------+ + | POINT(3 3) | +-------------------------------------+ + - sql: "CREATE TABLE gis_line (g LINESTRING);\nINSERT INTO gis_line VALUES\n\ + \ (LineFromText('LINESTRING(0 0,0 10,10 0)')),\n (LineStringFromText('LINESTRING(10\ + \ 10,20 10,20 20,10 20,10 10)')),\n (LineStringFromWKB(AsWKB(LineString(Point(10,\ + \ 10), Point(40, 10)))));" + result: 'URL: https://mariadb.com/kb/en/linestring/' + - name: LN + category_id: numeric + category_label: Numeric Functions + tags: + - numeric + aliases: [] + signature: + display: LN(X) + args: + - name: X + optional: false + type: any + summary: Returns the natural logarithm of X; that is, the base-e logarithm of + X. + description: Returns the natural logarithm of X; that is, the base-e logarithm + of X. If X is less than or equal to 0, or NULL, then NULL is returned. The inverse + of this function is EXP(). + examples: + - sql: SELECT LN(2); + result: +-------------------+ | LN(2) | +-------------------+ | + 0.693147180559945 | +-------------------+ + - sql: SELECT LN(-2); + result: +--------+ | LN(-2) | +--------+ | NULL | +--------+ + - sql: 'URL: https://mariadb.com/kb/en/ln/' + - name: LOAD_FILE + category_id: string + category_label: String Functions + tags: + - string + aliases: [] + signature: + display: LOAD_FILE(file_name) + args: + - name: file_name + optional: false + type: any + summary: Reads the file and returns the file contents as a string. + description: Reads the file and returns the file contents as a string. To use + this function, the file must be located on the server host, you must specify + the full path name to the file, and you must have the FILE privilege. The file + must be readable by all and it must be less than the size, in bytes, of the + max_allowed_packet system variable. If the secure_file_priv system variable + is set to a non-empty directory name, the file to be loaded must be located + in that directory. If the file does not exist or cannot be read because one + of the preceding conditions is not satisfied, the function returns NULL. Since + MariaDB 5.1, the character_set_filesystem system variable has controlled interpretation + of file names that are given as literal strings. Statements using the LOAD_FILE() + function are not safe for statement based replication. This is because the slave + will execute the LOAD_FILE() command itself. If the file doesn't exist on the + slave, the function will return NULL. + examples: + - sql: UPDATE t SET blob_col=LOAD_FILE('/tmp/picture') WHERE id=1; + result: 'URL: https://mariadb.com/kb/en/load_file/' + - name: LOCALTIME + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: LOCALTIME([precision]) + args: + - name: precision + optional: true + type: any + summary: LOCALTIME and LOCALTIME() are synonyms for NOW(). + description: 'LOCALTIME and LOCALTIME() are synonyms for NOW(). URL: https://mariadb.com/kb/en/localtime/' + examples: [] + - name: LOCALTIMESTAMP + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: LOCALTIMESTAMP([precision]) + args: + - name: precision + optional: true + type: any + summary: LOCALTIMESTAMP and LOCALTIMESTAMP() are synonyms for NOW(). + description: 'LOCALTIMESTAMP and LOCALTIMESTAMP() are synonyms for NOW(). URL: + https://mariadb.com/kb/en/localtimestamp/' + examples: [] + - name: LOCATE + category_id: string + category_label: String Functions + tags: + - string + aliases: [] + signature: + display: LOCATE(substr,str) + args: + - name: substr + optional: false + type: any + - name: str + optional: false + type: any + summary: The first syntax returns the position of the first occurrence of substring + description: The first syntax returns the position of the first occurrence of + substring substr in string str. The second syntax returns the position of the + first occurrence of substring substr in string str, starting at position pos. + Returns 0 if substr is not in str. LOCATE() performs a case-insensitive search. + If any argument is NULL, returns NULL. INSTR() is the same as the two-argument + form of LOCATE(), except that the order of the arguments is reversed. + examples: + - sql: SELECT LOCATE('bar', 'foobarbar'); + result: +----------------------------+ | LOCATE('bar', 'foobarbar') | +----------------------------+ + | 4 | +----------------------------+ + - sql: SELECT LOCATE('My', 'Maria'); + result: +-----------------------+ | LOCATE('My', 'Maria') | +-----------------------+ + | 0 | +-----------------------+ + - sql: SELECT LOCATE('bar', 'foobarbar', 5); + result: +-------------------------------+ | LOCATE('bar', 'foobarbar', 5) | + +-------------------------------+ | 7 | +-------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/locate/' + - name: LOG + category_id: numeric + category_label: Numeric Functions + tags: + - numeric + aliases: [] + signature: + display: LOG(X) + args: + - name: X + optional: false + type: any + summary: If called with one parameter, this function returns the natural logarithm + of + description: If called with one parameter, this function returns the natural logarithm + of X. If X is less than or equal to 0, then NULL is returned. If called with + two parameters, it returns the logarithm of X to the base B. If B is <= 1 or + X <= 0, the function returns NULL. If any argument is NULL, the function returns + NULL. The inverse of this function (when called with a single argument) is the + EXP() function. + examples: + - sql: 'LOG(X):' + result: SELECT LOG(2); +-------------------+ | LOG(2) | +-------------------+ + | 0.693147180559945 | +-------------------+ + - sql: SELECT LOG(-2); + result: +---------+ | LOG(-2) | +---------+ | NULL | +---------+ + - sql: LOG(B,X) + result: SELECT LOG(2,16); +-----------+ | LOG(2,16) | +-----------+ | 4 + | +-----------+ + - sql: SELECT LOG(3,27); + result: +-----------+ | LOG(3,27) | +-----------+ | 3 | +-----------+ + - sql: SELECT LOG(3,1); + result: +----------+ | LOG(3,1) | +----------+ + - name: LOG10 + category_id: numeric + category_label: Numeric Functions + tags: + - numeric + aliases: [] + signature: + display: LOG10(X) + args: + - name: X + optional: false + type: any + summary: Returns the base-10 logarithm of X. + description: Returns the base-10 logarithm of X. + examples: + - sql: SELECT LOG10(2); + result: +-------------------+ | LOG10(2) | +-------------------+ | + 0.301029995663981 | +-------------------+ + - sql: SELECT LOG10(100); + result: +------------+ | LOG10(100) | +------------+ | 2 | +------------+ + - sql: SELECT LOG10(-100); + result: +-------------+ | LOG10(-100) | +-------------+ | NULL | +-------------+ + - sql: 'URL: https://mariadb.com/kb/en/log10/' + - name: LOG2 + category_id: numeric + category_label: Numeric Functions + tags: + - numeric + aliases: [] + signature: + display: LOG2(X) + args: + - name: X + optional: false + type: any + summary: Returns the base-2 logarithm of X. + description: Returns the base-2 logarithm of X. + examples: + - sql: SELECT LOG2(4398046511104); + result: +---------------------+ | LOG2(4398046511104) | +---------------------+ + | 42 | +---------------------+ + - sql: SELECT LOG2(65536); + result: +-------------+ | LOG2(65536) | +-------------+ | 16 | +-------------+ + - sql: SELECT LOG2(-100); + result: +------------+ | LOG2(-100) | +------------+ | NULL | +------------+ + - sql: 'URL: https://mariadb.com/kb/en/log2/' + - name: LOWER + category_id: string + category_label: String Functions + tags: + - string + aliases: [] + signature: + display: LOWER(str) + args: + - name: str + optional: false + type: any + summary: Returns the string str with all characters changed to lowercase according + to + description: Returns the string str with all characters changed to lowercase according + to the current character set mapping. The default is latin1 (cp1252 West European). + LCASE is a synonym for LOWER + examples: + - sql: SELECT LOWER('QUADRATICALLY'); + result: +------------------------+ | LOWER('QUADRATICALLY') | +------------------------+ + | quadratically | +------------------------+ + - sql: 'LOWER() (and UPPER()) are ineffective when applied to binary strings (BINARY, + VARBINARY, BLOB). To perform lettercase conversion, CONVERT the string to + a non-binary string:' + result: SET @str = BINARY 'North Carolina'; + - sql: SELECT LOWER(@str), LOWER(CONVERT(@str USING latin1)); + result: +----------------+-----------------------------------+ | LOWER(@str) | + LOWER(CONVERT(@str USING latin1)) | +----------------+-----------------------------------+ + | North Carolina | north carolina | +----------------+-----------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/lower/' + - name: LPAD + category_id: string + category_label: String Functions + tags: + - string + aliases: [] + signature: + display: LPAD(str, len [,padstr]) + args: + - name: str + optional: false + type: any + - name: len [ + optional: false + type: any + - name: padstr] + optional: false + type: any + summary: Returns the string str, left-padded with the string padstr to a length + of len + description: Returns the string str, left-padded with the string padstr to a length + of len characters. If str is longer than len, the return value is shortened + to len characters. If padstr is omitted, the LPAD function pads spaces. Prior + to MariaDB 10.3.1, the padstr parameter was mandatory. Returns NULL if given + a NULL argument. If the result is empty (zero length), returns either an empty + string or, from MariaDB 10.3.6 with SQL_MODE=Oracle, NULL. The Oracle mode version + of the function can be accessed outside of Oracle mode by using LPAD_ORACLE + as the function name. + examples: + - sql: SELECT LPAD('hello',10,'.'); + result: +----------------------+ | LPAD('hello',10,'.') | +----------------------+ + | .....hello | +----------------------+ + - sql: SELECT LPAD('hello',2,'.'); + result: +---------------------+ | LPAD('hello',2,'.') | +---------------------+ + | he | +---------------------+ + - sql: From MariaDB 10.3.1, with the pad string defaulting to space. + result: SELECT LPAD('hello',10); +------------------+ | LPAD('hello',10) | +------------------+ + | hello | +------------------+ + - sql: 'Oracle mode version from MariaDB 10.3.6:' + result: SELECT LPAD('',0),LPAD_ORACLE('',0); +------------+-------------------+ + | LPAD('',0) | LPAD_ORACLE('',0) | +------------+-------------------+ | | + NULL | +------------+-------------------+ + - sql: 'URL: https://mariadb.com/kb/en/lpad/' + - name: LTRIM + category_id: string + category_label: String Functions + tags: + - string + aliases: [] + signature: + display: LTRIM(str) + args: + - name: str + optional: false + type: any + summary: Returns the string str with leading space characters removed. + description: Returns the string str with leading space characters removed. Returns + NULL if given a NULL argument. If the result is empty, returns either an empty + string, or, from MariaDB 10.3.6 with SQL_MODE=Oracle, NULL. The Oracle mode + version of the function can be accessed outside of Oracle mode by using LTRIM_ORACLE + as the function name. + examples: + - sql: SELECT QUOTE(LTRIM(' MariaDB ')); + result: +-------------------------------+ | QUOTE(LTRIM(' MariaDB ')) | + +-------------------------------+ | 'MariaDB ' | +-------------------------------+ + - sql: 'Oracle mode version from MariaDB 10.3.6:' + result: SELECT LTRIM(''),LTRIM_ORACLE(''); +-----------+------------------+ + | LTRIM('') | LTRIM_ORACLE('') | +-----------+------------------+ | | + NULL | +-----------+------------------+ + - sql: 'URL: https://mariadb.com/kb/en/ltrim/' + - name: MAKEDATE + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: MAKEDATE(year,dayofyear) + args: + - name: year + optional: false + type: any + - name: dayofyear + optional: false + type: any + summary: Returns a date, given year and day-of-year values. + description: Returns a date, given year and day-of-year values. dayofyear must + be greater than 0 or the result is NULL. + examples: + - sql: SELECT MAKEDATE(2011,31), MAKEDATE(2011,32); + result: +-------------------+-------------------+ | MAKEDATE(2011,31) | MAKEDATE(2011,32) + | +-------------------+-------------------+ | 2011-01-31 | 2011-02-01 | + +-------------------+-------------------+ + - sql: SELECT MAKEDATE(2011,365), MAKEDATE(2014,365); + result: +--------------------+--------------------+ | MAKEDATE(2011,365) | MAKEDATE(2014,365) + | +--------------------+--------------------+ | 2011-12-31 | 2014-12-31 | + +--------------------+--------------------+ + - sql: SELECT MAKEDATE(2011,0); + result: +------------------+ | MAKEDATE(2011,0) | +------------------+ | NULL | + +------------------+ + - sql: 'URL: https://mariadb.com/kb/en/makedate/' + - name: MAKETIME + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: MAKETIME(hour,minute,second) + args: + - name: hour + optional: false + type: any + - name: minute + optional: false + type: any + - name: second + optional: false + type: any + summary: Returns a time value calculated from the hour, minute, and second arguments. + description: Returns a time value calculated from the hour, minute, and second + arguments. If minute or second are out of the range 0 to 60, NULL is returned. + The hour can be in the range -838 to 838, outside of which the value is truncated + with a warning. + examples: + - sql: SELECT MAKETIME(13,57,33); + result: +--------------------+ | MAKETIME(13,57,33) | +--------------------+ + | 13:57:33 | +--------------------+ + - sql: SELECT MAKETIME(-13,57,33); + result: +---------------------+ | MAKETIME(-13,57,33) | +---------------------+ + | -13:57:33 | +---------------------+ + - sql: SELECT MAKETIME(13,67,33); + result: +--------------------+ | MAKETIME(13,67,33) | +--------------------+ + | NULL | +--------------------+ + - sql: SELECT MAKETIME(-1000,57,33); + result: +-----------------------+ | MAKETIME(-1000,57,33) | +-----------------------+ + | -838:59:59 | +-----------------------+ + - sql: SHOW WARNINGS; + result: '+---------+------+-----------------------------------------------+ + | Level | Code | Message | +---------+------+-----------------------------------------------+ + | Warning | 1292 | Truncated incorrect time value: ''-1000:57:33'' | +---------+------+-----------------------------------------------+' + - sql: 'URL: https://mariadb.com/kb/en/maketime/' + - name: MAKE_SET + category_id: string + category_label: String Functions + tags: + - string + aliases: [] + signature: + display: MAKE_SET(bits,str1,str2,...) + args: + - name: bits + optional: false + type: any + - name: str1 + optional: false + type: any + - name: str2 + optional: false + type: any + - name: '...' + optional: false + type: any + summary: Returns a set value (a string containing substrings separated by "," + description: Returns a set value (a string containing substrings separated by + "," characters) consisting of the strings that have the corresponding bit in + bits set. str1 corresponds to bit 0, str2 to bit 1, and so on. NULL values in + str1, str2, ... are not appended to the result. + examples: + - sql: SELECT MAKE_SET(1,'a','b','c'); + result: +-------------------------+ | MAKE_SET(1,'a','b','c') | +-------------------------+ + | a | +-------------------------+ + - sql: SELECT MAKE_SET(1 | 4,'hello','nice','world'); + result: +----------------------------------------+ | MAKE_SET(1 | 4,'hello','nice','world') + | +----------------------------------------+ | hello,world | + +----------------------------------------+ + - sql: SELECT MAKE_SET(1 | 4,'hello','nice',NULL,'world'); + result: +---------------------------------------------+ | MAKE_SET(1 | 4,'hello','nice',NULL,'world') + | +---------------------------------------------+ | hello | + +---------------------------------------------+ + - sql: SELECT QUOTE(MAKE_SET(0,'a','b','c')); + result: +--------------------------------+ | QUOTE(MAKE_SET(0,'a','b','c')) + | +--------------------------------+ | '' | +--------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/make_set/' + - name: MASTER_GTID_WAIT + category_id: miscellaneous + category_label: Miscellaneous Functions + tags: + - miscellaneous + aliases: [] + signature: + display: MASTER_GTID_WAIT(gtid-list[, timeout) + args: + - name: gtid-list[ + optional: false + type: any + - name: timeout + optional: false + type: any + summary: This function takes a string containing a comma-separated list of global + description: "This function takes a string containing a comma-separated list of\ + \ global\ntransaction id's (similar to the value of, for example, gtid_binlog_pos).\ + \ It\nwaits until the value of gtid_slave_pos has the same or higher seq_no\ + \ within\nall replication domains specified in the gtid-list; in other words,\ + \ it waits\nuntil the slave has reached the specified GTID position.\n\nAn optional\ + \ second argument gives a timeout in seconds. If the timeout expires\nbefore\ + \ the specified GTID position is reached, then the function returns -1.\nPassing\ + \ NULL or a negative number for the timeout means no timeout, and the\nfunction\ + \ will wait indefinitely.\n\nIf the wait completes without a timeout, 0 is returned.\ + \ Passing NULL for the\ngtid-list makes the function return NULL immediately,\ + \ without waiting.\n\nThe gtid-list may be the empty string, in which case MASTER_GTID_WAIT()\n\ + returns immediately. If the gtid-list contains fewer domains than\ngtid_slave_pos,\ + \ then only those domains are waited upon. If gtid-list contains\na domain that\ + \ is not present in @@gtid_slave_pos, then MASTER_GTID_WAIT() will\nwait until\ + \ an event containing such domain_id arrives on the slave (or until\ntimed out\ + \ or killed).\n\nMASTER_GTID_WAIT() can be useful to ensure that a slave has\ + \ caught up to a\nmaster. Simply take the value of gtid_binlog_pos on the master,\ + \ and use it in\na MASTER_GTID_WAIT() call on the slave; when the call completes,\ + \ the slave\nwill have caught up with that master position.\n\nMASTER_GTID_WAIT()\ + \ can also be used in client applications together with the\nlast_gtid session\ + \ variable. This is useful in a read-scaleout replication\nsetup, where the\ + \ application writes to a single master but divides the reads\nout to a number\ + \ of slaves to distribute the load. In such a setup, there is a\nrisk that an\ + \ application could first do an update on the master, and then a\nbit later\ + \ do a read on a slave, and if the slave is not fast enough, the data\nread\ + \ from the slave might not include the update just made, possibly confusing\n\ + the application and/or the end-user. One way to avoid this is to request the\n\ + value of last_gtid on the master just after the update. Then before doing the\n\ + read on the slave, do a MASTER_GTID_WAIT() on the value obtained from the\n\ + master; this will ensure that the read is not performed until the slave has\n\ + replicated sufficiently far for the update to have become visible.\n\nNote that\ + \ MASTER_GTID_WAIT() can be used even if the slave is configured not\nto use\ + \ GTID for connections (CHANGE MASTER TO master_use_gtid=no). This is\nbecause\ + \ from MariaDB 10, GTIDs are always logged on the master server, and\nalways\ + \ recorded on the slave servers.\n\nDifferences to MASTER_POS_WAIT()\n--------------------------------\n\ + \n* MASTER_GTID_WAIT() is global; it waits for any master connection to reach\n\ + \ the specified GTID position. MASTER_POS_WAIT() works only against a\n specific\ + \ connection. This also means that while MASTER_POS_WAIT() aborts if\n ..." + examples: [] + - name: MASTER_POS_WAIT + category_id: miscellaneous + category_label: Miscellaneous Functions + tags: + - miscellaneous + aliases: [] + signature: + display: MASTER_POS_WAIT(log_name,log_pos[,timeout,["connection_name"]]) + args: + - name: log_name + optional: false + type: any + - name: log_pos[ + optional: false + type: any + - name: timeout + optional: false + type: any + - name: '"connection_name"]' + optional: true + type: any + summary: This function is useful in replication for controlling primary/replica + description: 'This function is useful in replication for controlling primary/replica + synchronization. It blocks until the replica has read and applied all updates + up to the specified position (log_name,log_pos) in the primary log. The return + value is the number of log events the replica had to wait for to advance to + the specified position. The function returns NULL if the replica SQL thread + is not started, the replica''s primary information is not initialized, the arguments + are incorrect, or an error occurs. It returns -1 if the timeout has been exceeded. + If the replica SQL thread stops while MASTER_POS_WAIT() is waiting, the function + returns NULL. If the replica is past the specified position, the function returns + immediately. If a timeout value is specified, MASTER_POS_WAIT() stops waiting + when timeout seconds have elapsed. timeout must be greater than 0; a zero or + negative timeout means no timeout. The connection_name is used when you are + using multi-source-replication. If you don''t specify it, it''s set to the value + of the default_master_connection system variable. Statements using the MASTER_POS_WAIT() + function are not safe for statement-based replication. URL: https://mariadb.com/kb/en/master_pos_wait/' + examples: [] + - name: MAX + category_id: group_by + category_label: Functions and Modifiers for Use with GROUP BY + tags: + - group_by + aliases: [] + signature: + display: MAX([DISTINCT] expr) + args: + - name: '[DISTINCT] expr' + optional: false + type: any + summary: Returns the largest, or maximum, value of expr. + description: Returns the largest, or maximum, value of expr. MAX() can also take + a string argument in which case it returns the maximum string value. The DISTINCT + keyword can be used to find the maximum of the distinct values of expr, however, + this produces the same result as omitting DISTINCT. Note that SET and ENUM fields + are currently compared by their string value rather than their relative position + in the set, so MAX() may produce a different highest result than ORDER BY DESC. + It is an aggregate function, and so can be used with the GROUP BY clause. MAX() + can be used as a window function. MAX() returns NULL if there were no matching + rows. + examples: + - sql: CREATE TABLE student (name CHAR(10), test CHAR(10), score TINYINT); + result: INSERT INTO student VALUES + - sql: "('Esben', 'SQL', 43), ('Esben', 'Tuning', 31),\n ('Kaolin', 'SQL', 56),\ + \ ('Kaolin', 'Tuning', 88),\n ('Tatiana', 'SQL', 87), ('Tatiana', 'Tuning',\ + \ 83);" + result: SELECT name, MAX(score) FROM student GROUP BY name; +---------+------------+ + | name | MAX(score) | +---------+------------+ | Chun | 75 | + | Esben | 43 | | Kaolin | 88 | | Tatiana | 87 | + +---------+------------+ + - sql: 'MAX string:' + result: SELECT MAX(name) FROM student; +-----------+ | MAX(name) | +-----------+ + | Tatiana | +-----------+ + - sql: 'Be careful to avoid this common mistake, not grouping correctly and returning + mismatched data:' + result: SELECT name,test,MAX(SCORE) FROM student; +------+------+------------+ + - name: MBRContains + category_id: mbr + category_label: MBR + tags: + - mbr + aliases: [] + signature: + display: MBRContains(g1,g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + summary: Returns 1 or 0 to indicate whether the Minimum Bounding Rectangle of + g1 + description: Returns 1 or 0 to indicate whether the Minimum Bounding Rectangle + of g1 contains the Minimum Bounding Rectangle of g2. This tests the opposite + relationship as MBRWithin(). + examples: + - sql: SET @g1 = GeomFromText('Polygon((0 0,0 3,3 3,3 0,0 0))'); + result: SET @g2 = GeomFromText('Point(1 1)'); + - sql: SELECT MBRContains(@g1,@g2), MBRContains(@g2,@g1); + result: +----------------------+----------------------+ | MBRContains(@g1,@g2) + | MBRContains(@g2,@g1) | +----------------------+----------------------+ | 1 + | 0 | +----------------------+----------------------+ + - sql: 'URL: https://mariadb.com/kb/en/mbrcontains/' + - name: MBRDisjoint + category_id: mbr + category_label: MBR + tags: + - mbr + aliases: [] + signature: + display: MBRDisjoint(g1,g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + summary: Returns 1 or 0 to indicate whether the Minimum Bounding Rectangles of + the two + description: Returns 1 or 0 to indicate whether the Minimum Bounding Rectangles + of the two geometries g1 and g2 are disjoint. Two geometries are disjoint if + they do not intersect, that is touch or overlap. + examples: + - sql: SET @g1 = GeomFromText('Polygon((0 0,0 3,3 3,3 0,0 0))'); SET @g2 = GeomFromText('Polygon((4 + 4,4 7,7 7,7 4,4 4))'); SELECTmbrdisjoint(@g1,@g2); + result: +----------------------+ | mbrdisjoint(@g1,@g2) | +----------------------+ + | 1 | +----------------------+ + - sql: SET @g1 = GeomFromText('Polygon((0 0,0 3,3 3,3 0,0 0))'); SET @g2 = GeomFromText('Polygon((3 + 3,3 6,6 6,6 3,3 3))'); SELECT mbrdisjoint(@g1,@g2); + result: +----------------------+ | mbrdisjoint(@g1,@g2) | +----------------------+ + | 0 | +----------------------+ + - sql: 'URL: https://mariadb.com/kb/en/mbrdisjoint/' + - name: MBREqual + category_id: mbr + category_label: MBR + tags: + - mbr + aliases: [] + signature: + display: MBREqual(g1,g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + summary: Returns 1 or 0 to indicate whether the Minimum Bounding Rectangles of + the two + description: Returns 1 or 0 to indicate whether the Minimum Bounding Rectangles + of the two geometries g1 and g2 are the same. + examples: + - sql: SET @g1=GEOMFROMTEXT('LINESTRING(0 0, 1 2)'); SET @g2=GEOMFROMTEXT('POLYGON((0 + 0, 0 2, 1 2, 1 0, 0 0))'); SELECT MbrEqual(@g1,@g2); + result: +-------------------+ | MbrEqual(@g1,@g2) | +-------------------+ | 1 + | +-------------------+ + - sql: SET @g1=GEOMFROMTEXT('LINESTRING(0 0, 1 3)'); SET @g2=GEOMFROMTEXT('POLYGON((0 + 0, 0 2, 1 4, 1 0, 0 0))'); SELECT MbrEqual(@g1,@g2); + result: +-------------------+ | MbrEqual(@g1,@g2) | +-------------------+ | 0 + | +-------------------+ + - sql: 'URL: https://mariadb.com/kb/en/mbrequal/' + - name: MBRIntersects + category_id: mbr + category_label: MBR + tags: + - mbr + aliases: [] + signature: + display: MBRIntersects(g1,g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + summary: Returns 1 or 0 to indicate whether the Minimum Bounding Rectangles of + the two + description: Returns 1 or 0 to indicate whether the Minimum Bounding Rectangles + of the two geometries g1 and g2 intersect. + examples: + - sql: SET @g1 = GeomFromText('Polygon((0 0,0 3,3 3,3 0,0 0))'); SET @g2 = GeomFromText('Polygon((3 + 3,3 6,6 6,6 3,3 3))'); SELECT mbrintersects(@g1,@g2); + result: +------------------------+ | mbrintersects(@g1,@g2) | +------------------------+ + | 1 | +------------------------+ + - sql: SET @g1 = GeomFromText('Polygon((0 0,0 3,3 3,3 0,0 0))'); SET @g2 = GeomFromText('Polygon((4 + 4,4 7,7 7,7 4,4 4))'); SELECT mbrintersects(@g1,@g2); + result: +------------------------+ | mbrintersects(@g1,@g2) | +------------------------+ + | 0 | +------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/mbrintersects/' + - name: MBROverlaps + category_id: mbr + category_label: MBR + tags: + - mbr + aliases: [] + signature: + display: MBROverlaps(g1,g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + summary: Returns 1 or 0 to indicate whether the Minimum Bounding Rectangles of + the two + description: Returns 1 or 0 to indicate whether the Minimum Bounding Rectangles + of the two geometries g1 and g2 overlap. The term spatially overlaps is used + if two geometries intersect and their intersection results in a geometry of + the same dimension but not equal to either of the given geometries. + examples: + - sql: SET @g1 = GeomFromText('Polygon((0 0,0 3,3 3,3 0,0 0))'); SET @g2 = GeomFromText('Polygon((4 + 4,4 7,7 7,7 4,4 4))'); SELECT mbroverlaps(@g1,@g2); + result: +----------------------+ | mbroverlaps(@g1,@g2) | +----------------------+ + | 0 | +----------------------+ + - sql: SET @g1 = GeomFromText('Polygon((0 0,0 3,3 3,3 0,0 0))'); SET @g2 = GeomFromText('Polygon((3 + 3,3 6,6 6,6 3,3 3))'); SELECT mbroverlaps(@g1,@g2); + result: +----------------------+ | mbroverlaps(@g1,@g2) | +----------------------+ + | 0 | +----------------------+ + - sql: SET @g1 = GeomFromText('Polygon((0 0,0 4,4 4,4 0,0 0))'); SET @g2 = GeomFromText('Polygon((3 + 3,3 6,6 6,6 3,3 3))'); SELECT mbroverlaps(@g1,@g2); + result: +----------------------+ | mbroverlaps(@g1,@g2) | +----------------------+ + | 1 | +----------------------+ + - sql: 'URL: https://mariadb.com/kb/en/mbroverlaps/' + - name: MBRTouches + category_id: mbr + category_label: MBR + tags: + - mbr + aliases: [] + signature: + display: MBRTouches(g1,g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + summary: Returns 1 or 0 to indicate whether the Minimum Bounding Rectangles of + the two + description: Returns 1 or 0 to indicate whether the Minimum Bounding Rectangles + of the two geometries g1 and g2 touch. Two geometries spatially touch if the + interiors of the geometries do not intersect, but the boundary of one of the + geometries intersects either the boundary or the interior of the other. + examples: + - sql: SET @g1 = GeomFromText('Polygon((0 0,0 3,3 3,3 0,0 0))'); SET @g2 = GeomFromText('Polygon((4 + 4,4 7,7 7,7 4,4 4))'); SELECT mbrtouches(@g1,@g2); + result: +---------------------+ | mbrtouches(@g1,@g2) | +---------------------+ + | 0 | +---------------------+ + - sql: SET @g1 = GeomFromText('Polygon((0 0,0 3,3 3,3 0,0 0))'); SET @g2 = GeomFromText('Polygon((3 + 3,3 6,6 6,6 3,3 3))'); SELECT mbrtouches(@g1,@g2); + result: +---------------------+ | mbrtouches(@g1,@g2) | +---------------------+ + | 1 | +---------------------+ + - sql: SET @g1 = GeomFromText('Polygon((0 0,0 4,4 4,4 0,0 0))'); SET @g2 = GeomFromText('Polygon((3 + 3,3 6,6 6,6 3,3 3))'); SELECT mbrtouches(@g1,@g2); + result: +---------------------+ | mbrtouches(@g1,@g2) | +---------------------+ + | 0 | +---------------------+ + - sql: 'URL: https://mariadb.com/kb/en/mbrtouches/' + - name: MBRWithin + category_id: mbr + category_label: MBR + tags: + - mbr + aliases: [] + signature: + display: MBRWithin(g1,g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + summary: Returns 1 or 0 to indicate whether the Minimum Bounding Rectangle of + g1 is + description: Returns 1 or 0 to indicate whether the Minimum Bounding Rectangle + of g1 is within the Minimum Bounding Rectangle of g2. This tests the opposite + relationship as MBRContains(). + examples: + - sql: SET @g1 = GeomFromText('Polygon((0 0,0 3,3 3,3 0,0 0))'); SET @g2 = GeomFromText('Polygon((0 + 0,0 5,5 5,5 0,0 0))'); SELECT MBRWithin(@g1,@g2), MBRWithin(@g2,@g1); + result: +--------------------+--------------------+ | MBRWithin(@g1,@g2) | MBRWithin(@g2,@g1) + | +--------------------+--------------------+ | 1 | 0 + | +--------------------+--------------------+ + - sql: 'URL: https://mariadb.com/kb/en/mbrwithin/' + - name: MD5 + category_id: encryption + category_label: Encryption Functions + tags: + - encryption + aliases: [] + signature: + display: MD5(str) + args: + - name: str + optional: false + type: any + summary: Calculates an MD5 128-bit checksum for the string. + description: Calculates an MD5 128-bit checksum for the string. The return value + is a 32-hex digit string, and as of MariaDB 5.5, is a nonbinary string in the + connection character set and collation, determined by the values of the character_set_connection + and collation_connection system variables. Before 5.5, the return value was + a binary string. NULL is returned if the argument was NULL. + examples: + - sql: SELECT MD5('testing'); + result: +----------------------------------+ | MD5('testing') | + +----------------------------------+ | ae2b1fca515949e5d54fb22b8ed95575 | + +----------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/md5/' + - name: MEDIUMINT + category_id: data_types + category_label: Data Types + tags: + - data_types + aliases: [] + signature: + display: MEDIUMINT(M) + args: + - name: M + optional: false + type: any + summary: A medium-sized integer. + description: A medium-sized integer. The signed range is -8388608 to 8388607. + The unsigned range is 0 to 16777215. ZEROFILL pads the integer with zeroes and + assumes UNSIGNED (even if UNSIGNED is not specified). INT3 is a synonym for + MEDIUMINT. For details on the attributes, see Numeric Data Type Overview. + examples: + - sql: CREATE TABLE mediumints (a MEDIUMINT,b MEDIUMINT UNSIGNED,c MEDIUMINT ZEROFILL); + result: DESCRIBE mediumints; +-------+--------------------------------+------+-----+---------+-------+ + | Field | Type | Null | Key | Default | Extra | + +-------+--------------------------------+------+-----+---------+-------+ + | a | mediumint(9) | YES | | NULL | | + | b | mediumint(8) unsigned | YES | | NULL | | + | c | mediumint(8) unsigned zerofill | YES | | NULL | | + +-------+--------------------------------+------+-----+---------+-------+ + - sql: 'With strict_mode set, the default from MariaDB 10.2.4:' + result: INSERT INTO mediumints VALUES (-10,-10,-10); + - sql: 'INSERT INTO mediumints VALUES (-10,10,-10); ERROR 1264 (22003): Out of + range value for column ''c'' at row 1' + result: INSERT INTO mediumints VALUES (-10,10,10); + - sql: 'INSERT INTO mediumints VALUES (8388608,8388608,8388608); ERROR 1264 (22003): + Out of range value for column ''a'' at row 1' + result: INSERT INTO mediumints VALUES (8388607,8388608,8388608); + - sql: SELECT * FROM mediumints; + result: +---------+---------+----------+ | a | b | c | +---------+---------+----------+ + | -10 | 10 | 00000010 | | 8388607 | 8388608 | 08388608 | +---------+---------+----------+ + - sql: 'With strict_mode unset, the default until MariaDB 10.2.3:' + result: '...' + - name: MICROSECOND + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: MICROSECOND(expr) + args: + - name: expr + optional: false + type: any + summary: Returns the microseconds from the time or datetime expression expr as + a number + description: Returns the microseconds from the time or datetime expression expr + as a number in the range from 0 to 999999. If expr is a time with no microseconds, + zero is returned, while if expr is a date with no time, zero with a warning + is returned. + examples: + - sql: SELECT MICROSECOND('12:00:00.123456'); + result: +--------------------------------+ | MICROSECOND('12:00:00.123456') + | +--------------------------------+ | 123456 | +--------------------------------+ + - sql: SELECT MICROSECOND('2009-12-31 23:59:59.000010'); + result: +-------------------------------------------+ | MICROSECOND('2009-12-31 + 23:59:59.000010') | +-------------------------------------------+ | 10 + | +-------------------------------------------+ + - sql: SELECT MICROSECOND('2013-08-07 12:13:14'); + result: +------------------------------------+ | MICROSECOND('2013-08-07 12:13:14') + | +------------------------------------+ | 0 + | +------------------------------------+ + - sql: SELECT MICROSECOND('2013-08-07'); + result: +---------------------------+ | MICROSECOND('2013-08-07') | +---------------------------+ + | 0 | +---------------------------+ + - sql: SHOW WARNINGS; + result: '+---------+------+----------------------------------------------+ | + Level | Code | Message | +---------+------+----------------------------------------------+ + | Warning | 1292 | Truncated incorrect time value: ''2013-08-07'' | +---------+------+----------------------------------------------+' + - sql: 'URL: https://mariadb.com/kb/en/microsecond/' + - name: MID + category_id: string + category_label: String Functions + tags: + - string + aliases: [] + signature: + display: MID(str,pos,len) + args: + - name: str + optional: false + type: any + - name: pos + optional: false + type: any + - name: len + optional: false + type: any + summary: MID(str,pos,len) is a synonym for SUBSTRING(str,pos,len). + description: MID(str,pos,len) is a synonym for SUBSTRING(str,pos,len). + examples: + - sql: SELECT MID('abcd',4,1); + result: +-----------------+ | MID('abcd',4,1) | +-----------------+ | d | + +-----------------+ + - sql: SELECT MID('abcd',2,2); + result: +-----------------+ | MID('abcd',2,2) | +-----------------+ | bc | + +-----------------+ + - sql: 'A negative starting position:' + result: SELECT MID('abcd',-2,4); +------------------+ | MID('abcd',-2,4) | +------------------+ + | cd | +------------------+ + - sql: 'URL: https://mariadb.com/kb/en/mid/' + - name: MIN + category_id: group_by + category_label: Functions and Modifiers for Use with GROUP BY + tags: + - group_by + aliases: [] + signature: + display: MIN([DISTINCT] expr) + args: + - name: '[DISTINCT] expr' + optional: false + type: any + summary: Returns the minimum value of expr. + description: Returns the minimum value of expr. MIN() may take a string argument, + in which case it returns the minimum string value. The DISTINCT keyword can + be used to find the minimum of the distinct values of expr, however, this produces + the same result as omitting DISTINCT. Note that SET and ENUM fields are currently + compared by their string value rather than their relative position in the set, + so MIN() may produce a different lowest result than ORDER BY ASC. It is an aggregate + function, and so can be used with the GROUP BY clause. MIN() can be used as + a window function. MIN() returns NULL if there were no matching rows. + examples: + - sql: CREATE TABLE student (name CHAR(10), test CHAR(10), score TINYINT); + result: INSERT INTO student VALUES + - sql: "('Esben', 'SQL', 43), ('Esben', 'Tuning', 31),\n ('Kaolin', 'SQL', 56),\ + \ ('Kaolin', 'Tuning', 88),\n ('Tatiana', 'SQL', 87), ('Tatiana', 'Tuning',\ + \ 83);" + result: SELECT name, MIN(score) FROM student GROUP BY name; +---------+------------+ + | name | MIN(score) | +---------+------------+ | Chun | 73 | + | Esben | 31 | | Kaolin | 56 | | Tatiana | 83 | + +---------+------------+ + - sql: 'MIN() with a string:' + result: SELECT MIN(name) FROM student; +-----------+ | MIN(name) | +-----------+ + | Chun | +-----------+ + - sql: 'Be careful to avoid this common mistake, not grouping correctly and returning + mismatched data:' + result: SELECT name,test,MIN(score) FROM student; +------+------+------------+ + - name: MINUTE + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: MINUTE(time) + args: + - name: time + optional: false + type: any + summary: Returns the minute for time, in the range 0 to 59. + description: Returns the minute for time, in the range 0 to 59. + examples: + - sql: SELECT MINUTE('2013-08-03 11:04:03'); + result: +-------------------------------+ | MINUTE('2013-08-03 11:04:03') | + +-------------------------------+ | 4 | +-------------------------------+ + - sql: SELECT MINUTE ('23:12:50'); + result: +---------------------+ | MINUTE ('23:12:50') | +---------------------+ + | 12 | +---------------------+ + - sql: 'URL: https://mariadb.com/kb/en/minute/' + - name: MLineFromText + category_id: wkt + category_label: WKT + tags: + - wkt + aliases: [] + signature: + display: MLineFromText(wkt[,srid]) + args: + - name: wkt[ + optional: false + type: any + - name: srid] + optional: false + type: any + summary: Constructs a MULTILINESTRING value using its WKT representation and SRID. + description: Constructs a MULTILINESTRING value using its WKT representation and + SRID. MLineFromText() and MultiLineStringFromText() are synonyms. + examples: + - sql: "CREATE TABLE gis_multi_line (g MULTILINESTRING);\nSHOW FIELDS FROM gis_multi_line;\n\ + INSERT INTO gis_multi_line VALUES\n (MultiLineStringFromText('MULTILINESTRING((10\ + \ 48,10 21,10 0),(16 0,16\n23,16 48))')),\n (MLineFromText('MULTILINESTRING((10\ + \ 48,10 21,10 0))')),\n (MLineFromWKB(AsWKB(MultiLineString(\n LineString(Point(1,\ + \ 2), Point(3, 5)),\n LineString(Point(2, 5), Point(5, 8), Point(21, 7))))));" + result: 'URL: https://mariadb.com/kb/en/mlinefromtext/' + - name: MLineFromWKB + category_id: wkb + category_label: WKB + tags: + - wkb + aliases: [] + signature: + display: MLineFromWKB(wkb[,srid]) + args: + - name: wkb[ + optional: false + type: any + - name: srid] + optional: false + type: any + summary: Constructs a MULTILINESTRING value using its WKB representation and SRID. + description: Constructs a MULTILINESTRING value using its WKB representation and + SRID. MLineFromWKB() and MultiLineStringFromWKB() are synonyms. + examples: + - sql: SET @g = ST_AsBinary(MLineFromText('MULTILINESTRING((10 48,10 21,10 0),(16 + 0,16 23,16 48))')); + result: SELECT ST_AsText(MLineFromWKB(@g)); +--------------------------------------------------------+ + | ST_AsText(MLineFromWKB(@g)) | +--------------------------------------------------------+ + | MULTILINESTRING((10 48,10 21,10 0),(16 0,16 23,16 48)) | +--------------------------------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/mlinefromwkb/' + - name: MOD + category_id: numeric + category_label: Numeric Functions + tags: + - numeric + aliases: [] + signature: + display: MOD(N,M) + args: + - name: N + optional: false + type: any + - name: M + optional: false + type: any + summary: Modulo operation. + description: Modulo operation. Returns the remainder of N divided by M. See also + Modulo Operator. If the ERROR_ON_DIVISION_BY_ZERO SQL_MODE is used, any number + modulus zero produces an error. Otherwise, it returns NULL. The integer part + of a division can be obtained using DIV. + examples: + - sql: SELECT 1042 % 50; + result: +-----------+ | 1042 % 50 | +-----------+ | 42 | +-----------+ + - sql: SELECT MOD(234, 10); + result: +--------------+ | MOD(234, 10) | +--------------+ | 4 | + +--------------+ + - sql: SELECT 253 % 7; + result: +---------+ | 253 % 7 | +---------+ | 1 | +---------+ + - sql: SELECT MOD(29,9); + result: +-----------+ | MOD(29,9) | +-----------+ | 2 | +-----------+ + - sql: SELECT 29 MOD 9; + result: +----------+ | 29 MOD 9 | +----------+ | 2 | +----------+ + - sql: 'URL: https://mariadb.com/kb/en/mod/' + - name: MONTH + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: MONTH(date) + args: + - name: date + optional: false + type: any + summary: Returns the month for date in the range 1 to 12 for January to December, + or 0 + description: Returns the month for date in the range 1 to 12 for January to December, + or 0 for dates such as '0000-00-00' or '2008-00-00' that have a zero month part. + examples: + - sql: SELECT MONTH('2019-01-03'); + result: +---------------------+ | MONTH('2019-01-03') | +---------------------+ + | 1 | +---------------------+ + - sql: SELECT MONTH('2019-00-03'); + result: +---------------------+ | MONTH('2019-00-03') | +---------------------+ + | 0 | +---------------------+ + - sql: 'URL: https://mariadb.com/kb/en/month/' + - name: MONTHNAME + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: MONTHNAME(date) + args: + - name: date + optional: false + type: any + summary: Returns the full name of the month for date. + description: Returns the full name of the month for date. The language used for + the name is controlled by the value of the lc_time_names system variable. See + server locale for more on the supported locales. + examples: + - sql: SELECT MONTHNAME('2019-02-03'); + result: +-------------------------+ | MONTHNAME('2019-02-03') | +-------------------------+ + | February | +-------------------------+ + - sql: 'Changing the locale:' + result: SET lc_time_names = 'fr_CA'; + - sql: SELECT MONTHNAME('2019-05-21'); + result: +-------------------------+ | MONTHNAME('2019-05-21') | +-------------------------+ + | mai | +-------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/monthname/' + - name: MPointFromText + category_id: wkt + category_label: WKT + tags: + - wkt + aliases: [] + signature: + display: MPointFromText(wkt[,srid]) + args: + - name: wkt[ + optional: false + type: any + - name: srid] + optional: false + type: any + summary: Constructs a MULTIPOINT value using its WKT representation and SRID. + description: Constructs a MULTIPOINT value using its WKT representation and SRID. + MPointFromText() and MultiPointFromText() are synonyms. + examples: + - sql: "CREATE TABLE gis_multi_point (g MULTIPOINT);\nSHOW FIELDS FROM gis_multi_point;\n\ + INSERT INTO gis_multi_point VALUES\n (MultiPointFromText('MULTIPOINT(0 0,10\ + \ 10,10 20,20 20)')),\n (MPointFromText('MULTIPOINT(1 1,11 11,11 21,21 21)')),\n\ + \ (MPointFromWKB(AsWKB(MultiPoint(Point(3, 6), Point(4, 10)))));" + result: 'URL: https://mariadb.com/kb/en/mpointfromtext/' + - name: MPointFromWKB + category_id: wkb + category_label: WKB + tags: + - wkb + aliases: [] + signature: + display: MPointFromWKB(wkb[,srid]) + args: + - name: wkb[ + optional: false + type: any + - name: srid] + optional: false + type: any + summary: Constructs a MULTIPOINT value using its WKB representation and SRID. + description: Constructs a MULTIPOINT value using its WKB representation and SRID. + MPointFromWKB() and MultiPointFromWKB() are synonyms. + examples: + - sql: SET @g = ST_AsBinary(MPointFromText('MultiPoint( 1 1, 2 2, 5 3, 7 2, 9 + 3, 8 4, 6 6, 6 9, 4 9, 1 5 )')); + result: SELECT ST_AsText(MPointFromWKB(@g)); +-----------------------------------------------------+ + | ST_AsText(MPointFromWKB(@g)) | +-----------------------------------------------------+ + | MULTIPOINT(1 1,2 2,5 3,7 2,9 3,8 4,6 6,6 9,4 9,1 5) | +-----------------------------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/mpointfromwkb/' + - name: MPolyFromText + category_id: wkt + category_label: WKT + tags: + - wkt + aliases: [] + signature: + display: MPolyFromText(wkt[,srid]) + args: + - name: wkt[ + optional: false + type: any + - name: srid] + optional: false + type: any + summary: Constructs a MULTIPOLYGON value using its WKT representation and SRID. + description: Constructs a MULTIPOLYGON value using its WKT representation and + SRID. MPolyFromText() and MultiPolygonFromText() are synonyms. + examples: + - sql: "CREATE TABLE gis_multi_polygon (g MULTIPOLYGON);\nSHOW FIELDS FROM gis_multi_polygon;\n\ + INSERT INTO gis_multi_polygon VALUES\n (MultiPolygonFromText('MULTIPOLYGON(\n\ + \ ((28 26,28 0,84 0,84 42,28 26),(52 18,66 23,73 9,48 6,52 18)),\n ((59\ + \ 18,67 18,67 13,59 13,59 18)))')),\n (MPolyFromText('MULTIPOLYGON(\n ((28\ + \ 26,28 0,84 0,84 42,28 26),(52 18,66 23,73 9,48 6,52 18)),\n ((59 18,67\ + \ 18,67 13,59 13,59 18)))')),\n (MPolyFromWKB(AsWKB(MultiPolygon(Polygon(\n\ + \ LineString(Point(0, 3), Point(3, 3), Point(3, 0), Point(0, 3)))))));" + result: 'URL: https://mariadb.com/kb/en/mpolyfromtext/' + - name: MPolyFromWKB + category_id: wkb + category_label: WKB + tags: + - wkb + aliases: [] + signature: + display: MPolyFromWKB(wkb[,srid]) + args: + - name: wkb[ + optional: false + type: any + - name: srid] + optional: false + type: any + summary: Constructs a MULTIPOLYGON value using its WKB representation and SRID. + description: Constructs a MULTIPOLYGON value using its WKB representation and + SRID. MPolyFromWKB() and MultiPolygonFromWKB() are synonyms. + examples: + - sql: SET @g = ST_AsBinary(MPointFromText('MULTIPOLYGON(((28 26,28 0,84 0,84 + 42,28 26),(52 18,66 23,73 9,48 6,52 18)),((59 18,67 18,67 13,59 13,59 18)))')); + result: SELECT ST_AsText(MPolyFromWKB(@g))\G + - sql: 'ST_AsText(MPolyFromWKB(@g)): MULTIPOLYGON(((28 26,28 0,84 0,84 42,28 26),(52 + 18,66 23,73 9,48 6,52 18)),((59 18,67 18,67 13,59 13,59 18)))' + result: 'URL: https://mariadb.com/kb/en/mpolyfromwkb/' + - name: MULTILINESTRING + category_id: geometry_constructors + category_label: Geometry Constructors + tags: + - geometry_constructors + aliases: [] + signature: + display: MULTILINESTRING(ls1,ls2,...) + args: + - name: ls1 + optional: false + type: any + - name: ls2 + optional: false + type: any + - name: '...' + optional: false + type: any + summary: Constructs a WKB MultiLineString value using WKB LineString arguments. + description: "Constructs a WKB MultiLineString value using WKB LineString arguments.\ + \ If any\nargument is not a WKB LineString, the return value is NULL.\n\nExample\n\ + -------\n\nCREATE TABLE gis_multi_line (g MULTILINESTRING);\nINSERT INTO gis_multi_line\ + \ VALUES\n (MultiLineStringFromText('MULTILINESTRING((10 48,10 21,10 0),(16\ + \ 0,16 23,16\n48))')),\n (MLineFromText('MULTILINESTRING((10 48,10 21,10 0))')),\n\ + \ (MLineFromWKB(AsWKB(MultiLineString(LineString(Point(1, 2), \n Point(3, 5)),\ + \ LineString(Point(2, 5),Point(5, 8),Point(21, 7))))));\n\nURL: https://mariadb.com/kb/en/multilinestring/" + examples: [] + - name: MULTIPOINT + category_id: geometry_constructors + category_label: Geometry Constructors + tags: + - geometry_constructors + aliases: [] + signature: + display: MULTIPOINT(pt1,pt2,...) + args: + - name: pt1 + optional: false + type: any + - name: pt2 + optional: false + type: any + - name: '...' + optional: false + type: any + summary: Constructs a WKB MultiPoint value using WKB Point arguments. + description: Constructs a WKB MultiPoint value using WKB Point arguments. If any + argument is not a WKB Point, the return value is NULL. + examples: + - sql: SET @g = ST_GEOMFROMTEXT('MultiPoint( 1 1, 2 2, 5 3, 7 2, 9 3, 8 4, 6 6, + 6 9, 4 9, 1 5 )'); + result: CREATE TABLE gis_multi_point (g MULTIPOINT); + - sql: "(MultiPointFromText('MULTIPOINT(0 0,10 10,10 20,20 20)')),\n (MPointFromText('MULTIPOINT(1\ + \ 1,11 11,11 21,21 21)')),\n (MPointFromWKB(AsWKB(MultiPoint(Point(3, 6),\ + \ Point(4, 10)))));" + result: 'URL: https://mariadb.com/kb/en/multipoint/' + - name: MULTIPOLYGON + category_id: geometry_constructors + category_label: Geometry Constructors + tags: + - geometry_constructors + aliases: [] + signature: + display: MULTIPOLYGON(poly1,poly2,...) + args: + - name: poly1 + optional: false + type: any + - name: poly2 + optional: false + type: any + - name: '...' + optional: false + type: any + summary: Constructs a WKB MultiPolygon value from a set of WKB Polygon arguments. + description: "Constructs a WKB MultiPolygon value from a set of WKB Polygon arguments.\ + \ If\nany argument is not a WKB Polygon, the return value is NULL.\n\nExample\n\ + -------\n\nCREATE TABLE gis_multi_polygon (g MULTIPOLYGON);\nINSERT INTO gis_multi_polygon\ + \ VALUES\n (MultiPolygonFromText('MULTIPOLYGON(((28 26,28 0,84 0,84 42,28 26),(52\n\ + 18,66 23,73 9,48 6,52 18)),\n ((59 18,67 18,67 13,59 13,59 18)))')),\n (MPolyFromText('MULTIPOLYGON(((28\ + \ 26,28 0,84 0,84 42,28 26),(52 18,66\n23,73 9,48 6,52 18)),\n ((59 18,67\ + \ 18,67 13,59 13,59 18)))')),\n (MPolyFromWKB(AsWKB(MultiPolygon(Polygon(LineString(\n\ + \ Point(0, 3), Point(3, 3), Point(3, 0), Point(0, 3)))))));\n\nURL: https://mariadb.com/kb/en/multipolygon/" + examples: [] + - name: NAME_CONST + category_id: miscellaneous + category_label: Miscellaneous Functions + tags: + - miscellaneous + aliases: [] + signature: + display: NAME_CONST(name,value) + args: + - name: name + optional: false + type: any + - name: value + optional: false + type: any + summary: Returns the given value. + description: 'Returns the given value. When used to produce a result set column, + NAME_CONST() causes the column to have the given name. The arguments should + be constants. This function is used internally when replicating stored procedures. + It makes little sense to use it explicitly in SQL statements, and it was not + supposed to be used like that. SELECT NAME_CONST(''myname'', 14); +--------+ + | myname | +--------+ | 14 | +--------+ URL: https://mariadb.com/kb/en/name_const/' + examples: [] + - name: NATURAL_SORT_KEY + category_id: string + category_label: String Functions + tags: + - string + aliases: [] + signature: + display: NATURAL_SORT_KEY(str) + args: + - name: str + optional: false + type: any + summary: The NATURAL_SORT_KEY function is used for sorting that is closer to natural + description: The NATURAL_SORT_KEY function is used for sorting that is closer + to natural sorting. Strings are sorted in alphabetical order, while numbers + are treated in a way such that, for example, 10 is greater than 2, whereas in + other forms of sorting, 2 would be greater than 10, just like z is greater than + ya. There are multiple natural sort implementations, differing in the way they + handle leading zeroes, fractions, i18n, negatives, decimals and so on. MariaDB's + implementation ignores leading zeroes when performing the sort. You can use + also use NATURAL_SORT_KEY with generated columns. The value is not stored permanently + in the table. When using a generated column, the virtual column must be longer + than the base column to cater for embedded numbers in the string and MDEV-24582. + examples: + - sql: Strings and Numbers ------------------- + result: CREATE TABLE t1 (c TEXT); + - sql: INSERT INTO t1 VALUES ('b1'),('a2'),('a11'),('a1'); + result: SELECT c FROM t1; +------+ | c | +------+ | b1 | | a2 | | a11 | + | a1 | +------+ + - sql: SELECT c FROM t1 ORDER BY c; + result: +------+ | c | +------+ | a1 | | a11 | | a2 | | b1 | +------+ + - sql: 'Unsorted, regular sort and natural sort:' + result: TRUNCATE t1; + - sql: "INSERT INTO t1 VALUES\n ..." + - name: NOW + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: + - CURRENT_TIMESTAMP + signature: + display: NOW([precision]) + args: + - name: precision + optional: true + type: any + summary: Returns the current date and time as a value in 'YYYY-MM-DD HH:MM:SS' + or + description: Returns the current date and time as a value in 'YYYY-MM-DD HH:MM:SS' + or YYYYMMDDHHMMSS.uuuuuu format, depending on whether the function is used in + a string or numeric context. The value is expressed in the current time zone. + The optional precision determines the microsecond precision. See Microseconds + in MariaDB. NOW() (or its synonyms) can be used as the default value for TIMESTAMP + columns as well as, since MariaDB 10.0.1, DATETIME columns. Before MariaDB 10.0.1, + it was only possible for a single TIMESTAMP column per table to contain the + CURRENT_TIMESTAMP as its default. When displayed in the INFORMATION_SCHEMA.COLUMNS + table, a default CURRENT TIMESTAMP is displayed as CURRENT_TIMESTAMP up until + MariaDB 10.2.2, and as current_timestamp() from MariaDB 10.2.3, due to to MariaDB + 10.2 accepting expressions in the DEFAULT clause. Changing the timestamp system + variable with a SET timestamp statement affects the value returned by NOW(), + but not by SYSDATE(). + examples: + - sql: SELECT NOW(); + result: +---------------------+ | NOW() | +---------------------+ + | 2010-03-27 13:13:25 | +---------------------+ + - sql: SELECT NOW() + 0; + result: +-----------------------+ | NOW() + 0 | +-----------------------+ + | 20100327131329.000000 | +-----------------------+ + - sql: 'With precision:' + result: SELECT CURRENT_TIMESTAMP(2); +------------------------+ | CURRENT_TIMESTAMP(2) | + +------------------------+ | 2018-07-10 09:47:26.24 | +------------------------+ + - sql: 'Used as a default TIMESTAMP:' + result: CREATE TABLE t (createdTS TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP); + - sql: '...' + - name: NTH_VALUE + category_id: window + category_label: Window Functions + tags: + - window + aliases: [] + signature: + display: NTH_VALUE(expr[, num_row]) + args: + - name: expr[ + optional: false + type: any + - name: num_row] + optional: false + type: any + summary: The NTH_VALUE function returns the value evaluated at row number num_row + of + description: 'The NTH_VALUE function returns the value evaluated at row number + num_row of the window frame, starting from 1, or NULL if the row does not exist. + URL: https://mariadb.com/kb/en/nth_value/' + examples: [] + - name: NTILE + category_id: window + category_label: Window Functions + tags: + - window + aliases: [] + signature: + display: NTILE(expr) + args: + - name: expr + optional: false + type: any + summary: NTILE() is a window function that returns an integer indicating which + group a + description: NTILE() is a window function that returns an integer indicating which + group a given row falls into. The number of groups is specified in the argument + (expr), starting at one. Ordered rows in the partition are divided into the + specified number of groups with as equal a size as possible. + examples: + - sql: "create table t1 (\n pk int primary key,\n a int,\n b int\n );" + result: insert into t1 values + - sql: "(12 , 0, 10),\n (13 , 1, 10),\n (14 , 1, 10),\n (18 , 2, 10),\n (15\ + \ , 2, 20),\n (16 , 2, 20),\n (17 , 2, 20),\n (19 , 4, 20),\n (20 , 4,\ + \ 20);" + result: select pk, a, b, + - sql: from t1; + result: +----+------+------+-----------------------------+ | pk | a | b | + ntile(1) over (order by pk) | +----+------+------+-----------------------------+ + | 11 | 0 | 10 | 1 | | 12 | 0 | 10 | 1 + | | 13 | 1 | 10 | 1 | | 14 | 1 | 10 | 1 + | | 15 | 2 | 20 | 1 | | 16 | 2 | 20 | 1 + | | 17 | 2 | 20 | 1 | | 18 | 2 | 10 | 1 + | | 19 | 4 | 20 | 1 | | 20 | 4 | 20 | 1 + | +----+------+------+-----------------------------+ + - sql: "select pk, a, b,\n ntile(4) over (order by pk)\n from t1;" + result: +----+------+------+-----------------------------+ | pk | a | b | + ntile(4) over (order by pk) | +----+------+------+-----------------------------+ + - name: NULLIF + category_id: control_flow + category_label: Control Flow Functions + tags: + - control_flow + aliases: [] + signature: + display: NULLIF(expr1,expr2) + args: + - name: expr1 + optional: false + type: any + - name: expr2 + optional: false + type: any + summary: Returns NULL if expr1 = expr2 is true, otherwise returns expr1. + description: Returns NULL if expr1 = expr2 is true, otherwise returns expr1. This + is the same as CASE WHEN expr1 = expr2 THEN NULL ELSE expr1 END. + examples: + - sql: SELECT NULLIF(1,1); + result: +-------------+ | NULLIF(1,1) | +-------------+ | NULL | +-------------+ + - sql: SELECT NULLIF(1,2); + result: +-------------+ | NULLIF(1,2) | +-------------+ | 1 | +-------------+ + - sql: 'URL: https://mariadb.com/kb/en/nullif/' + - name: NVL2 + category_id: control_flow + category_label: Control Flow Functions + tags: + - control_flow + aliases: [] + signature: + display: NVL2(expr1,expr2,expr3) + args: + - name: expr1 + optional: false + type: any + - name: expr2 + optional: false + type: any + - name: expr3 + optional: false + type: any + summary: The NVL2 function returns a value based on whether a specified expression + is + description: The NVL2 function returns a value based on whether a specified expression + is NULL or not. If expr1 is not NULL, then NVL2 returns expr2. If expr1 is NULL, + then NVL2 returns expr3. + examples: + - sql: SELECT NVL2(NULL,1,2); + result: +----------------+ | NVL2(NULL,1,2) | +----------------+ | 2 + | +----------------+ + - sql: SELECT NVL2('x',1,2); + result: +---------------+ | NVL2('x',1,2) | +---------------+ | 1 + | +---------------+ + - sql: 'URL: https://mariadb.com/kb/en/nvl2/' + - name: OCT + category_id: numeric + category_label: Numeric Functions + tags: + - numeric + aliases: [] + signature: + display: OCT(N) + args: + - name: N + optional: false + type: any + summary: Returns a string representation of the octal value of N, where N is a + longlong + description: Returns a string representation of the octal value of N, where N + is a longlong (BIGINT) number. This is equivalent to CONV(N,10,8). Returns NULL + if N is NULL. + examples: + - sql: SELECT OCT(34); + result: +---------+ | OCT(34) | +---------+ | 42 | +---------+ + - sql: SELECT OCT(12); + result: +---------+ | OCT(12) | +---------+ | 14 | +---------+ + - sql: 'URL: https://mariadb.com/kb/en/oct/' + - name: OCTET_LENGTH + category_id: string + category_label: String Functions + tags: + - string + aliases: [] + signature: + display: OCTET_LENGTH(str) + args: + - name: str + optional: false + type: any + summary: OCTET_LENGTH() returns the length of the given string, in octets (bytes). + description: OCTET_LENGTH() returns the length of the given string, in octets + (bytes). This is a synonym for LENGTHB(), and, when Oracle mode from MariaDB + 10.3 is not set, a synonym for LENGTH(). A multi-byte character counts as multiple + bytes. This means that for a string containing five two-byte characters, OCTET_LENGTH() + returns 10, whereas CHAR_LENGTH() returns 5. If str is not a string value, it + is converted into a string. If str is NULL, the function returns NULL. + examples: + - sql: 'When Oracle mode from MariaDB 10.3 is not set:' + result: "SELECT CHAR_LENGTH('\u03C0'), LENGTH('\u03C0'), LENGTHB('\u03C0'),\ + \ OCTET_LENGTH('\u03C0');\n+-------------------+--------------+---------------+--------------------+\n\ + | CHAR_LENGTH('\u03C0') | LENGTH('\u03C0') | LENGTHB('\u03C0') | OCTET_LENGTH('\u03C0\ + ') |\n+-------------------+--------------+---------------+--------------------+\n\ + | 1 | 2 | 2 | 2 |\n\ + +-------------------+--------------+---------------+--------------------+" + - sql: 'In Oracle mode from MariaDB 10.3:' + result: "SELECT CHAR_LENGTH('\u03C0'), LENGTH('\u03C0'), LENGTHB('\u03C0'),\ + \ OCTET_LENGTH('\u03C0');\n+-------------------+--------------+---------------+--------------------+\n\ + | CHAR_LENGTH('\u03C0') | LENGTH('\u03C0') | LENGTHB('\u03C0') | OCTET_LENGTH('\u03C0\ + ') |\n+-------------------+--------------+---------------+--------------------+\n\ + | 1 | 1 | 2 | 2 |\n\ + +-------------------+--------------+---------------+--------------------+" + - sql: 'URL: https://mariadb.com/kb/en/octet_length/' + - name: OLD_PASSWORD + category_id: encryption + category_label: Encryption Functions + tags: + - encryption + aliases: [] + signature: + display: OLD_PASSWORD(str) + args: + - name: str + optional: false + type: any + summary: OLD_PASSWORD() was added to MySQL when the implementation of PASSWORD() + was + description: 'OLD_PASSWORD() was added to MySQL when the implementation of PASSWORD() + was changed to improve security. OLD_PASSWORD() returns the value of the old + (pre-MySQL 4.1) implementation of PASSWORD() as a string, and is intended to + permit you to reset passwords for any pre-4.1 clients that need to connect to + a more recent MySQL server version, or any version of MariaDB, without locking + them out. As of MariaDB 5.5, the return value is a nonbinary string in the connection + character set and collation, determined by the values of the character_set_connection + and collation_connection system variables. Before 5.5, the return value was + a binary string. The return value is 16 bytes in length, or NULL if the argument + was NULL. URL: https://mariadb.com/kb/en/old_password/' + examples: [] + - name: ORD + category_id: string + category_label: String Functions + tags: + - string + aliases: [] + signature: + display: ORD(str) + args: + - name: str + optional: false + type: any + summary: If the leftmost character of the string str is a multi-byte character, + returns + description: 'If the leftmost character of the string str is a multi-byte character, + returns the code for that character, calculated from the numeric values of its + constituent bytes using this formula: (1st byte code) + (2nd byte code x 256) + + (3rd byte code x 256 x 256) ... If the leftmost character is not a multi-byte + character, ORD() returns the same value as the ASCII() function.' + examples: + - sql: SELECT ORD('2'); + result: +----------+ | ORD('2') | +----------+ | 50 | +----------+ + - sql: 'URL: https://mariadb.com/kb/en/ord/' + - name: OVERLAPS + category_id: geometry_relations + category_label: Geometry Relations + tags: + - geometry_relations + aliases: [] + signature: + display: OVERLAPS(g1,g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + summary: Returns 1 or 0 to indicate whether g1 spatially overlaps g2. + description: 'Returns 1 or 0 to indicate whether g1 spatially overlaps g2. The + term spatially overlaps is used if two geometries intersect and their intersection + results in a geometry of the same dimension but not equal to either of the given + geometries. OVERLAPS() is based on the original MySQL implementation and uses + object bounding rectangles, while ST_OVERLAPS() uses object shapes. URL: https://mariadb.com/kb/en/overlaps/' + examples: [] + - name: PASSWORD + category_id: encryption + category_label: Encryption Functions + tags: + - encryption + aliases: [] + signature: + display: PASSWORD(str) + args: + - name: str + optional: false + type: any + summary: The PASSWORD() function is used for hashing passwords for use in + description: "The PASSWORD() function is used for hashing passwords for use in\n\ + authentication by the MariaDB server. It is not intended for use in other\n\ + applications.\n\nCalculates and returns a hashed password string from the plaintext\ + \ password\nstr. Returns an empty string (>= MariaDB 10.0.4) if the argument\ + \ was NULL.\n\nThe return value is a nonbinary string in the connection character\ + \ set and\ncollation, determined by the values of the character_set_connection\ + \ and\ncollation_connection system variables.\n\nThis is the function that is\ + \ used for hashing MariaDB passwords for storage in\nthe Password column of\ + \ the user table (see privileges), usually used with the\nSET PASSWORD statement.\ + \ It is not intended for use in other applications.\n\nUntil MariaDB 10.3, the\ + \ return value is 41-bytes in length, and the first\ncharacter is always '*'.\ + \ From MariaDB 10.4, the function takes into account\nthe authentication plugin\ + \ where applicable (A CREATE USER or SET PASSWORD\nstatement). For example,\ + \ when used in conjunction with a user authenticated by\nthe ed25519 plugin,\ + \ the statement will create a longer hash:\n\nCREATE USER edtest@localhost IDENTIFIED\ + \ VIA ed25519 USING PASSWORD('secret');\n\nCREATE USER edtest2@localhost IDENTIFIED\ + \ BY 'secret';\n\nSELECT CONCAT(user, '@', host, ' => ', JSON_DETAILED(priv))\ + \ FROM\nmysql.global_priv\n WHERE user LIKE 'edtest%'\\G\n***************************\ + \ 1. row ***************************\nCONCAT(user, '@', host, ' => ', JSON_DETAILED(priv)):\ + \ edtest@localhost => {\n...\n \"plugin\": \"ed25519\",\n \"authentication_string\"\ + : \"ZIgUREUg5PVgQ6LskhXmO+eZLS0nC8be6HPjYWR4YJY\",\n...\n}\n***************************\ + \ 2. row ***************************\nCONCAT(user, '@', host, ' => ', JSON_DETAILED(priv)):\ + \ edtest2@localhost => {\n...\n \"plugin\": \"mysql_native_password\",\n \"\ + authentication_string\": \"*14E65567ABDB5135D0CFD9A70B3032C179A49EE7\",\n...\n\ + }\n\nThe behavior of this function is affected by the value of the old_passwords\n\ + system variable. If this is set to 1 (0 is default), MariaDB reverts to using\n\ + the mysql_old_password authentication plugin by default for newly created\n\ + users and passwords." + examples: + - sql: '...' + - name: PERCENTILE_CONT + category_id: window + category_label: Window Functions + tags: + - window + aliases: [] + signature: + display: PERCENTILE_CONT + args: [] + summary: PERCENTILE_CONT() (standing for continuous percentile) is a window function + description: 'PERCENTILE_CONT() (standing for continuous percentile) is a window + function which returns a value which corresponds to the given fraction in the + sort order. If required, it will interpolate between adjacent input items. Essentially, + the following process is followed to find the value to return: * Get the number + of rows in the partition, denoted by N * RN = p*(N-1), where p denotes the argument + to the PERCENTILE_CONT function * calculate the FRN(floor row number) and CRN(column + row number for the group( FRN= floor(RN) and CRN = ceil(RN)) * look up rows + FRN and CRN * If (CRN = FRN = RN) then the result is (value of expression from + row at RN) * Otherwise the result is * (CRN - RN) * (value of expression for + row at FRN) + * (RN - FRN) * (value of expression for row at CRN) The MEDIAN + function is a specific case of PERCENTILE_CONT, equivalent to PERCENTILE_CONT(0.5).' + examples: + - sql: CREATE TABLE book_rating (name CHAR(30), star_rating TINYINT); + result: INSERT INTO book_rating VALUES ('Lord of the Ladybirds', 5); + - sql: INSERT INTO book_rating VALUES ('Lady of the Flies', 1); INSERT INTO book_rating + VALUES ('Lady of the Flies', 2); INSERT INTO book_rating VALUES ('Lady of + the Flies', 5); + result: SELECT name, PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY star_rating) + - sql: FROM book_rating; + result: +-----------------------+--------------+ | name | pc | + +-----------------------+--------------+ | Lord of the Ladybirds | 4.0000000000 + | | Lord of the Ladybirds | 4.0000000000 | | Lady of the Flies | 2.0000000000 + | | Lady of the Flies | 2.0000000000 | | Lady of the Flies | 2.0000000000 + | +-----------------------+--------------+ + - sql: "SELECT name, PERCENTILE_CONT(1) WITHIN GROUP (ORDER BY star_rating)\n\ + \ OVER (PARTITION BY name) AS pc\n FROM book_rating;" + result: +-----------------------+--------------+ | name | pc | + +-----------------------+--------------+ | Lord of the Ladybirds | 5.0000000000 + | + - name: PERCENTILE_DISC + category_id: window + category_label: Window Functions + tags: + - window + aliases: [] + signature: + display: PERCENTILE_DISC + args: [] + summary: PERCENTILE_DISC() (standing for discrete percentile) is a window function + description: 'PERCENTILE_DISC() (standing for discrete percentile) is a window + function which returns the first value in the set whose ordered position is + the same or more than the specified fraction. Essentially, the following process + is followed to find the value to return: * Get the number of rows in the partition. + * Walk through the partition, in order, until finding the the first row with + CUME_DIST() >= function_argument.' + examples: + - sql: CREATE TABLE book_rating (name CHAR(30), star_rating TINYINT); + result: INSERT INTO book_rating VALUES ('Lord of the Ladybirds', 5); + - sql: INSERT INTO book_rating VALUES ('Lady of the Flies', 1); INSERT INTO book_rating + VALUES ('Lady of the Flies', 2); INSERT INTO book_rating VALUES ('Lady of + the Flies', 5); + result: SELECT name, PERCENTILE_DISC(0.5) WITHIN GROUP (ORDER BY star_rating) + - sql: +-----------------------+------+ + result: '| name | pc | +-----------------------+------+ | + Lord of the Ladybirds | 3 | | Lord of the Ladybirds | 3 | | Lady of + the Flies | 2 | | Lady of the Flies | 2 | | Lady of the Flies | 2 + | +-----------------------+------+' + - sql: "SELECT name, PERCENTILE_DISC(0) WITHIN GROUP (ORDER BY star_rating)\n\ + \ OVER (PARTITION BY name) AS pc FROM book_rating;" + result: +-----------------------+------+ | name | pc | +-----------------------+------+ + | Lord of the Ladybirds | 3 | | Lord of the Ladybirds | 3 | | Lady of + the Flies | 1 | | Lady of the Flies | 1 | | Lady of the Flies | 1 + | +-----------------------+------+ + - sql: "SELECT name, PERCENTILE_DISC(1) WITHIN GROUP (ORDER BY star_rating)\n\ + \ OVER (PARTITION BY name) AS pc FROM book_rating;" + result: +-----------------------+------+ + - name: PERCENT_RANK + category_id: window + category_label: Window Functions + tags: + - window + aliases: [] + signature: + display: PERCENT_RANK + args: [] + summary: PERCENT_RANK() is a window function that returns the relative percent + rank of + description: 'PERCENT_RANK() is a window function that returns the relative percent + rank of a given row. The following formula is used to calculate the percent + rank: (rank - 1) / (number of rows in the window or partition - 1)' + examples: + - sql: "create table t1 (\n pk int primary key,\n a int,\n b int\n);" + result: insert into t1 values + - sql: ( 2 , 0, 10), ( 3 , 1, 10), ( 4 , 1, 10), ( 8 , 2, 10), ( 5 , 2, 20), ( + 6 , 2, 20), ( 7 , 2, 20), ( 9 , 4, 20), (10 , 4, 20); + result: select pk, a, b, + - sql: "percent_rank() over (order by a) as pct_rank,\n cume_dist() over (order\ + \ by a) as cume_dist\nfrom t1;" + result: +----+------+------+------+--------------+--------------+ | pk | a | + b | rank | pct_rank | cume_dist | +----+------+------+------+--------------+--------------+ + | 1 | 0 | 10 | 1 | 0.0000000000 | 0.2000000000 | | 2 | 0 | 10 + | 1 | 0.0000000000 | 0.2000000000 | | 3 | 1 | 10 | 3 | 0.2222222222 + | 0.4000000000 | | 4 | 1 | 10 | 3 | 0.2222222222 | 0.4000000000 | + | 5 | 2 | 20 | 5 | 0.4444444444 | 0.8000000000 | | 6 | 2 | 20 + | 5 | 0.4444444444 | 0.8000000000 | | 7 | 2 | 20 | 5 | 0.4444444444 + | 0.8000000000 | | 8 | 2 | 10 | 5 | 0.4444444444 | 0.8000000000 | + | 9 | 4 | 20 | 9 | 0.8888888889 | 1.0000000000 | | 10 | 4 | 20 + | 9 | 0.8888888889 | 1.0000000000 | +----+------+------+------+--------------+--------------+ + - sql: "select pk, a, b,\n percent_rank() over (order by pk) as pct_rank,\n\ + \ cume_dist() over (order by pk) as cume_dist\nfrom t1 order by pk;\n ..." + - name: PERIOD_ADD + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: PERIOD_ADD(P,N) + args: + - name: P + optional: false + type: any + - name: N + optional: false + type: any + summary: Adds N months to period P. + description: Adds N months to period P. P is in the format YYMM or YYYYMM, and + is not a date value. If P contains a two-digit year, values from 00 to 69 are + converted to from 2000 to 2069, while values from 70 are converted to 1970 upwards. + Returns a value in the format YYYYMM. + examples: + - sql: SELECT PERIOD_ADD(200801,2); + result: +----------------------+ | PERIOD_ADD(200801,2) | +----------------------+ + | 200803 | +----------------------+ + - sql: SELECT PERIOD_ADD(6910,2); + result: +--------------------+ | PERIOD_ADD(6910,2) | +--------------------+ + | 206912 | +--------------------+ + - sql: SELECT PERIOD_ADD(7010,2); + result: +--------------------+ | PERIOD_ADD(7010,2) | +--------------------+ + | 197012 | +--------------------+ + - sql: 'URL: https://mariadb.com/kb/en/period_add/' + - name: PERIOD_DIFF + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: PERIOD_DIFF(P1,P2) + args: + - name: P1 + optional: false + type: any + - name: P2 + optional: false + type: any + summary: Returns the number of months between periods P1 and P2. + description: Returns the number of months between periods P1 and P2. P1 and P2 + can be in the format YYMM or YYYYMM, and are not date values. If P1 or P2 contains + a two-digit year, values from 00 to 69 are converted to from 2000 to 2069, while + values from 70 are converted to 1970 upwards. + examples: + - sql: SELECT PERIOD_DIFF(200802,200703); + result: +----------------------------+ | PERIOD_DIFF(200802,200703) | +----------------------------+ + | 11 | +----------------------------+ + - sql: SELECT PERIOD_DIFF(6902,6803); + result: +------------------------+ | PERIOD_DIFF(6902,6803) | +------------------------+ + | 11 | +------------------------+ + - sql: SELECT PERIOD_DIFF(7002,6803); + result: +------------------------+ | PERIOD_DIFF(7002,6803) | +------------------------+ + | -1177 | +------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/period_diff/' + - name: PI + category_id: numeric + category_label: Numeric Functions + tags: + - numeric + aliases: [] + signature: + display: PI + args: [] + summary: "Returns the value of \u03C0 (pi)." + description: "Returns the value of \u03C0 (pi). The default number of decimal\ + \ places displayed is\nsix, but MariaDB uses the full double-precision value\ + \ internally." + examples: + - sql: SELECT PI(); + result: +----------+ | PI() | +----------+ | 3.141593 | +----------+ + - sql: SELECT PI()+0.0000000000000000000000; + result: +-------------------------------+ | PI()+0.0000000000000000000000 | + +-------------------------------+ | 3.1415926535897931159980 | +-------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/pi/' + - name: POINT + category_id: geometry_constructors + category_label: Geometry Constructors + tags: + - geometry_constructors + aliases: [] + signature: + display: POINT(x,y) + args: + - name: x + optional: false + type: any + - name: y + optional: false + type: any + summary: Constructs a WKB Point using the given coordinates. + description: Constructs a WKB Point using the given coordinates. + examples: + - sql: SET @g = ST_GEOMFROMTEXT('Point(1 1)'); + result: CREATE TABLE gis_point (g POINT); + - sql: "(PointFromText('POINT(10 10)')),\n (PointFromText('POINT(20 10)')),\n\ + \ (PointFromText('POINT(20 20)')),\n (PointFromWKB(AsWKB(PointFromText('POINT(10\ + \ 20)'))));" + result: 'URL: https://mariadb.com/kb/en/point/' + - name: POLYGON + category_id: geometry_constructors + category_label: Geometry Constructors + tags: + - geometry_constructors + aliases: [] + signature: + display: POLYGON(ls1,ls2,...) + args: + - name: ls1 + optional: false + type: any + - name: ls2 + optional: false + type: any + - name: '...' + optional: false + type: any + summary: Constructs a WKB Polygon value from a number of WKB LineString arguments. + description: Constructs a WKB Polygon value from a number of WKB LineString arguments. + If any argument does not represent the WKB of a LinearRing (that is, not a closed + and simple LineString) the return value is NULL. Note that according to the + OpenGIS standard, a POLYGON should have exactly one ExteriorRing and all other + rings should lie within that ExteriorRing and thus be the InteriorRings. Practically, + however, some systems, including MariaDB's, permit polygons to have several + 'ExteriorRings'. In the case of there being multiple, non-overlapping exterior + rings ST_NUMINTERIORRINGS() will return 1. + examples: + - sql: SET @g = ST_GEOMFROMTEXT('POLYGON((1 1,1 5,4 9,6 9,9 3,7 2,1 1))'); + result: CREATE TABLE gis_polygon (g POLYGON); + - sql: "(PolygonFromText('POLYGON((10 10,20 10,20 20,10 20,10 10))')),\n (PolyFromText('POLYGON((0\ + \ 0,50 0,50 50,0 50,0 0), (10 10,20 10,20 20,10\n20,10 10))')),\n (PolyFromWKB(AsWKB(Polygon(LineString(Point(0,\ + \ 0), Point(30, 0), Point(30,\n30), Point(0, 0))))));" + result: 'Non-overlapping ''polygon'':' + - sql: "SELECT ST_NumInteriorRings(ST_PolyFromText('POLYGON((0 0,10 0,10 10,0\ + \ 10,0 0),\n (-1 -1,-5 -1,-5 -5,-1 -5,-1 -1))')) AS NumInteriorRings;" + result: +------------------+ | NumInteriorRings | +------------------+ | 1 + | +------------------+ + - sql: 'URL: https://mariadb.com/kb/en/polygon/' + - name: POSITION + category_id: string + category_label: String Functions + tags: + - string + aliases: [] + signature: + display: POSITION(substr IN str) + args: + - name: substr IN str + optional: false + type: any + summary: POSITION(substr IN str) is a synonym for LOCATE(substr,str). + description: 'POSITION(substr IN str) is a synonym for LOCATE(substr,str). It''s + part of ODBC 3.0. URL: https://mariadb.com/kb/en/position/' + examples: [] + - name: POW + category_id: numeric + category_label: Numeric Functions + tags: + - numeric + aliases: [] + signature: + display: POW(X,Y) + args: + - name: X + optional: false + type: any + - name: Y + optional: false + type: any + summary: Returns the value of X raised to the power of Y. + description: Returns the value of X raised to the power of Y. POWER() is a synonym. + examples: + - sql: SELECT POW(2,3); + result: +----------+ | POW(2,3) | +----------+ | 8 | +----------+ + - sql: SELECT POW(2,-2); + result: +-----------+ | POW(2,-2) | +-----------+ | 0.25 | +-----------+ + - sql: 'URL: https://mariadb.com/kb/en/pow/' + - name: POWER + category_id: numeric + category_label: Numeric Functions + tags: + - numeric + aliases: [] + signature: + display: POWER(X,Y) + args: + - name: X + optional: false + type: any + - name: Y + optional: false + type: any + summary: This is a synonym for POW(), which returns the value of X raised to the + power + description: 'This is a synonym for POW(), which returns the value of X raised + to the power of Y. URL: https://mariadb.com/kb/en/power/' + examples: [] + - name: QUARTER + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: QUARTER(date) + args: + - name: date + optional: false + type: any + summary: Returns the quarter of the year for date, in the range 1 to 4. + description: Returns the quarter of the year for date, in the range 1 to 4. Returns + 0 if month contains a zero value, or NULL if the given value is not otherwise + a valid date (zero values are accepted). + examples: + - sql: SELECT QUARTER('2008-04-01'); + result: +-----------------------+ | QUARTER('2008-04-01') | +-----------------------+ + | 2 | +-----------------------+ + - sql: SELECT QUARTER('2019-00-01'); + result: +-----------------------+ | QUARTER('2019-00-01') | +-----------------------+ + | 0 | +-----------------------+ + - sql: 'URL: https://mariadb.com/kb/en/quarter/' + - name: QUOTE + category_id: string + category_label: String Functions + tags: + - string + aliases: [] + signature: + display: QUOTE(str) + args: + - name: str + optional: false + type: any + summary: Quotes a string to produce a result that can be used as a properly escaped + description: Quotes a string to produce a result that can be used as a properly + escaped data value in an SQL statement. The string is returned enclosed by single + quotes and with each instance of single quote ("'"), backslash ("\"), ASCII + NUL, and Control-Z preceded by a backslash. If the argument is NULL, the return + value is the word "NULL" without enclosing single quotes. + examples: + - sql: SELECT QUOTE("Don't!"); + result: +-----------------+ | QUOTE("Don't!") | +-----------------+ | 'Don\'t!' | + +-----------------+ + - sql: SELECT QUOTE(NULL); + result: +-------------+ | QUOTE(NULL) | +-------------+ | NULL | +-------------+ + - sql: 'URL: https://mariadb.com/kb/en/quote/' + - name: RADIANS + category_id: numeric + category_label: Numeric Functions + tags: + - numeric + aliases: [] + signature: + display: RADIANS(X) + args: + - name: X + optional: false + type: any + summary: Returns the argument X, converted from degrees to radians. + description: "Returns the argument X, converted from degrees to radians. Note\ + \ that \u03C0 radians\nequals 180 degrees.\n\nThis is the converse of the DEGREES()\ + \ function." + examples: + - sql: SELECT RADIANS(45); + result: +-------------------+ | RADIANS(45) | +-------------------+ | + 0.785398163397448 | +-------------------+ + - sql: SELECT RADIANS(90); + result: +-----------------+ | RADIANS(90) | +-----------------+ | 1.5707963267949 + | +-----------------+ + - sql: SELECT RADIANS(PI()); + result: +--------------------+ | RADIANS(PI()) | +--------------------+ + | 0.0548311355616075 | +--------------------+ + - sql: SELECT RADIANS(180); + result: +------------------+ | RADIANS(180) | +------------------+ | 3.14159265358979 + | +------------------+ + - sql: 'URL: https://mariadb.com/kb/en/radians/' + - name: RAND + category_id: numeric + category_label: Numeric Functions + tags: + - numeric + aliases: [] + signature: + display: RAND + args: [] + summary: Returns a random DOUBLE precision floating point value v in the range + 0 <= v < + description: 'Returns a random DOUBLE precision floating point value v in the + range 0 <= v < 1.0. If a constant integer argument N is specified, it is used + as the seed value, which produces a repeatable sequence of column values. In + the example below, note that the sequences of values produced by RAND(3) is + the same both places where it occurs. In a WHERE clause, RAND() is evaluated + each time the WHERE is executed. Statements using the RAND() function are not + safe for statement-based replication. Practical uses -------------- The expression + to get a random integer from a given range is the following: FLOOR(min_value + + RAND() * (max_value - min_value +1)) RAND() is often used to read random rows + from a table, as follows: SELECT * FROM my_table ORDER BY RAND() LIMIT 10; Note, + however, that this technique should never be used on a large table as it will + be extremely slow. MariaDB will read all rows in the table, generate a random + value for each of them, order them, and finally will apply the LIMIT clause.' + examples: + - sql: CREATE TABLE t (i INT); + result: INSERT INTO t VALUES(1),(2),(3); + - sql: SELECT i, RAND() FROM t; + result: +------+-------------------+ | i | RAND() | +------+-------------------+ + | 1 | 0.255651095188829 | | 2 | 0.833920199269355 | | 3 | 0.40264774151393 + | +------+-------------------+ + - sql: SELECT i, RAND(3) FROM t; + result: +------+-------------------+ | i | RAND(3) | +------+-------------------+ + | 1 | 0.90576975597606 | | 2 | 0.373079058130345 | | 3 | 0.148086053457191 + | + - name: RANDOM_BYTES + category_id: encryption + category_label: Encryption Functions + tags: + - encryption + aliases: [] + signature: + display: RANDOM_BYTES(length) + args: + - name: length + optional: false + type: any + summary: Given a length from 1 to 1024, generates a binary string of length consisting + description: 'Given a length from 1 to 1024, generates a binary string of length + consisting of random bytes generated by the SSL library''s random number generator. + See the RAND_bytes() function documentation of your SSL library for information + on the random number generator. In the case of OpenSSL, a cryptographically + secure pseudo random generator (CSPRNG) is used. Statements containing the RANDOM_BYTES + function are unsafe for statement-based replication. An error occurs if length + is outside the range 1 to 1024. URL: https://mariadb.com/kb/en/random_bytes/' + examples: [] + - name: RANK + category_id: window + category_label: Window Functions + tags: + - window + aliases: [] + signature: + display: RANK + args: [] + summary: RANK() is a window function that displays the number of a given row, + starting + description: RANK() is a window function that displays the number of a given row, + starting at one and following the ORDER BY sequence of the window function, + with identical values receiving the same result. It is similar to the ROW_NUMBER() + function except that in that function, identical values will receive a different + row number for each result. + examples: + - sql: 'The distinction between DENSE_RANK(), RANK() and ROW_NUMBER():' + result: CREATE TABLE student(course VARCHAR(10), mark int, name varchar(10)); + - sql: "INSERT INTO student VALUES\n ('Maths', 60, 'Thulile'),\n ('Maths', 60,\ + \ 'Pritha'),\n ('Maths', 70, 'Voitto'),\n ('Maths', 55, 'Chun'),\n ('Biology',\ + \ 60, 'Bilal'),\n ('Biology', 70, 'Roger');" + result: SELECT + - sql: "DENSE_RANK() OVER (PARTITION BY course ORDER BY mark DESC) AS dense_rank,\n\ + \ ROW_NUMBER() OVER (PARTITION BY course ORDER BY mark DESC) AS row_num,\n\ + \ course, mark, name\nFROM student ORDER BY course, mark DESC;" + result: +------+------------+---------+---------+------+---------+ | rank | + dense_rank | row_num | course | mark | name | +------+------------+---------+---------+------+---------+ + | 1 | 1 | 1 | Biology | 70 | Roger | | 2 | 2 + | 2 | Biology | 60 | Bilal | | 1 | 1 | 1 | Maths | 70 + | Voitto | | 2 | 2 | 2 | Maths | 60 | Thulile | | 2 + | 2 | 3 | Maths | 60 | Pritha | | 4 | 3 | 4 + | Maths | 55 | Chun | +------+------------+---------+---------+------+---------+ + - sql: 'URL: https://mariadb.com/kb/en/rank/' + - name: REGEXP_INSTR + category_id: string + category_label: String Functions + tags: + - string + aliases: [] + signature: + display: REGEXP_INSTR(subject, pattern) + args: + - name: subject + optional: false + type: any + - name: pattern + optional: false + type: any + summary: Returns the position of the first occurrence of the regular expression + pattern + description: Returns the position of the first occurrence of the regular expression + pattern in the string subject, or 0 if pattern was not found. The positions + start with 1 and are measured in characters (i.e. not in bytes), which is important + for multi-byte character sets. You can cast a multi-byte character set to BINARY + to get offsets in bytes. The function follows the case sensitivity rules of + the effective collation. Matching is performed case insensitively for case insensitive + collations, and case sensitively for case sensitive collations and for binary + data. The collation case sensitivity can be overwritten using the (?i) and (?-i) + PCRE flags. MariaDB uses the PCRE regular expression library for enhanced regular + expression performance, and REGEXP_INSTR was introduced as part of this enhancement. + examples: + - sql: SELECT REGEXP_INSTR('abc','b'); -> 2 + result: SELECT REGEXP_INSTR('abc','x'); + - sql: "SELECT REGEXP_INSTR('BJ\xD6RN','N');\n-> 5" + result: 'Casting a multi-byte character set as BINARY to get offsets in bytes:' + - sql: "SELECT REGEXP_INSTR(BINARY 'BJ\xD6RN','N') AS cast_utf8_to_binary;\n->\ + \ 6" + result: 'Case sensitivity:' + - sql: SELECT REGEXP_INSTR('ABC','b'); -> 2 + result: SELECT REGEXP_INSTR('ABC' COLLATE utf8_bin,'b'); + - sql: SELECT REGEXP_INSTR(BINARY'ABC','b'); -> 0 + result: SELECT REGEXP_INSTR('ABC','(?-i)b'); + - sql: SELECT REGEXP_INSTR('ABC' COLLATE utf8_bin,'(?i)b'); -> 2 + result: 'URL: https://mariadb.com/kb/en/regexp_instr/' + - name: REGEXP_REPLACE + category_id: string + category_label: String Functions + tags: + - string + aliases: [] + signature: + display: REGEXP_REPLACE(subject, pattern, replace) + args: + - name: subject + optional: false + type: any + - name: pattern + optional: false + type: any + - name: replace + optional: false + type: any + summary: REGEXP_REPLACE returns the string subject with all occurrences of the + regular + description: REGEXP_REPLACE returns the string subject with all occurrences of + the regular expression pattern replaced by the string replace. If no occurrences + are found, then subject is returned as is. The replace string can have backreferences + to the subexpressions in the form \N, where N is a number from 1 to 9. The function + follows the case sensitivity rules of the effective collation. Matching is performed + case insensitively for case insensitive collations, and case sensitively for + case sensitive collations and for binary data. The collation case sensitivity + can be overwritten using the (?i) and (?-i) PCRE flags. MariaDB uses the PCRE + regular expression library for enhanced regular expression performance, and + REGEXP_REPLACE was introduced as part of this enhancement. The default_regex_flags + variable addresses the remaining compatibilities between PCRE and the old regex + library. + examples: + - sql: SELECT REGEXP_REPLACE('ab12cd','[0-9]','') AS remove_digits; -> abcd + result: SELECT + - sql: '''<.+?>'','' '') AS strip_html; -> title body' + result: Backreferences to the subexpressions in the form \N, where N is a number + from + - sql: SELECT REGEXP_REPLACE('James Bond','^(.*) (.*)$','\\2, \\1') AS reorder_name; + -> Bond, James + result: 'Case insensitive and case sensitive matches:' + - sql: SELECT REGEXP_REPLACE('ABC','b','-') AS case_insensitive; -> A-C + result: SELECT REGEXP_REPLACE('ABC' COLLATE utf8_bin,'b','-') AS case_sensitive; + - sql: SELECT REGEXP_REPLACE(BINARY 'ABC','b','-') AS binary_data; -> ABC + result: '...' + - name: REGEXP_SUBSTR + category_id: string + category_label: String Functions + tags: + - string + aliases: [] + signature: + display: REGEXP_SUBSTR(subject,pattern) + args: + - name: subject + optional: false + type: any + - name: pattern + optional: false + type: any + summary: Returns the part of the string subject that matches the regular expression + description: Returns the part of the string subject that matches the regular expression + pattern, or an empty string if pattern was not found. The function follows the + case sensitivity rules of the effective collation. Matching is performed case + insensitively for case insensitive collations, and case sensitively for case + sensitive collations and for binary data. The collation case sensitivity can + be overwritten using the (?i) and (?-i) PCRE flags. MariaDB uses the PCRE regular + expression library for enhanced regular expression performance, and REGEXP_SUBSTR + was introduced as part of this enhancement. The default_regex_flags variable + addresses the remaining compatibilities between PCRE and the old regex library. + examples: + - sql: SELECT REGEXP_SUBSTR('ab12cd','[0-9]+'); -> 12 + result: SELECT REGEXP_SUBSTR( + - sql: '''https?://[^/]*''); -> https://mariadb.org' + result: SELECT REGEXP_SUBSTR('ABC','b'); + - sql: SELECT REGEXP_SUBSTR('ABC' COLLATE utf8_bin,'b'); -> + result: SELECT REGEXP_SUBSTR(BINARY'ABC','b'); + - sql: SELECT REGEXP_SUBSTR('ABC','(?i)b'); -> B + result: SELECT REGEXP_SUBSTR('ABC' COLLATE utf8_bin,'(?+i)b'); + - sql: 'URL: https://mariadb.com/kb/en/regexp_substr/' + - name: RELEASE_ALL_LOCKS + category_id: miscellaneous + category_label: Miscellaneous Functions + tags: + - miscellaneous + aliases: [] + signature: + display: RELEASE_ALL_LOCKS + args: [] + summary: Releases all named locks held by the current session. + description: Releases all named locks held by the current session. Returns the + number of locks released, or 0 if none were held. Statements using the RELEASE_ALL_LOCKS + function are not safe for statement-based replication. + examples: + - sql: SELECT RELEASE_ALL_LOCKS(); + result: +---------------------+ | RELEASE_ALL_LOCKS() | +---------------------+ + | 0 | +---------------------+ + - sql: SELECT GET_LOCK('lock1',10); + result: +----------------------+ | GET_LOCK('lock1',10) | +----------------------+ + | 1 | +----------------------+ + - sql: SELECT RELEASE_ALL_LOCKS(); + result: +---------------------+ | RELEASE_ALL_LOCKS() | +---------------------+ + | 1 | +---------------------+ + - sql: 'URL: https://mariadb.com/kb/en/release_all_locks/' + - name: RELEASE_LOCK + category_id: miscellaneous + category_label: Miscellaneous Functions + tags: + - miscellaneous + aliases: [] + signature: + display: RELEASE_LOCK(str) + args: + - name: str + optional: false + type: any + summary: Releases the lock named by the string str that was obtained with GET_LOCK(). + description: Releases the lock named by the string str that was obtained with + GET_LOCK(). Returns 1 if the lock was released, 0 if the lock was not established + by this thread (in which case the lock is not released), and NULL if the named + lock did not exist. The lock does not exist if it was never obtained by a call + to GET_LOCK() or if it has previously been released. str is case insensitive. + If str is an empty string or NULL, RELEASE_LOCK() returns NULL and does nothing. + Statements using the RELEASE_LOCK function are not safe for statement-based + replication. The DO statement is convenient to use with RELEASE_LOCK(). + examples: + - sql: 'Connection1:' + result: SELECT GET_LOCK('lock1',10); +----------------------+ | GET_LOCK('lock1',10) + | +----------------------+ | 1 | +----------------------+ + - sql: 'Connection 2:' + result: SELECT GET_LOCK('lock2',10); +----------------------+ | GET_LOCK('lock2',10) + | +----------------------+ | 1 | +----------------------+ + - sql: 'Connection 1:' + result: SELECT RELEASE_LOCK('lock1'), RELEASE_LOCK('lock2'), RELEASE_LOCK('lock3'); + +-----------------------+-----------------------+-----------------------+ + | RELEASE_LOCK('lock1') | RELEASE_LOCK('lock2') | RELEASE_LOCK('lock3') | + +-----------------------+-----------------------+-----------------------+ + | 1 | 0 | NULL | + +-----------------------+-----------------------+-----------------------+ + - sql: 'It is possible to hold the same lock recursively. This example is viewed + using the metadata_lock_info plugin:' + result: SELECT GET_LOCK('lock3',10); +----------------------+ | GET_LOCK('lock3',10) + | + - name: RETURN + category_id: compound_statements + category_label: Compound Statements + tags: + - compound_statements + aliases: [] + signature: + display: RETURN(SELECT COUNT(DISTINCT User) + args: + - name: SELECT COUNT(DISTINCT User + optional: false + type: any + summary: END; + description: 'END; URL: https://mariadb.com/kb/en/return/' + examples: [] + - name: REVERSE + category_id: string + category_label: String Functions + tags: + - string + aliases: [] + signature: + display: REVERSE(str) + args: + - name: str + optional: false + type: any + summary: Returns the string str with the order of the characters reversed. + description: Returns the string str with the order of the characters reversed. + examples: + - sql: SELECT REVERSE('desserts'); + result: +---------------------+ | REVERSE('desserts') | +---------------------+ + | stressed | +---------------------+ + - sql: 'URL: https://mariadb.com/kb/en/reverse/' + - name: RIGHT + category_id: string + category_label: String Functions + tags: + - string + aliases: [] + signature: + display: RIGHT(str,len) + args: + - name: str + optional: false + type: any + - name: len + optional: false + type: any + summary: Returns the rightmost len characters from the string str, or NULL if + any + description: Returns the rightmost len characters from the string str, or NULL + if any argument is NULL. + examples: + - sql: SELECT RIGHT('MariaDB', 2); + result: +---------------------+ | RIGHT('MariaDB', 2) | +---------------------+ + | DB | +---------------------+ + - sql: 'URL: https://mariadb.com/kb/en/right/' + - name: ROLLBACK + category_id: transactions + category_label: Transactions + tags: + - transactions + aliases: [] + signature: + display: ROLLBACK(the keyword WORK is simply noise and can be omitted without + changing the effect) + args: + - name: the keyword WORK is simply noise and can be omitted without changing + the effect + optional: false + type: any + summary: The optional AND CHAIN clause is a convenience for initiating a new + description: "The optional AND CHAIN clause is a convenience for initiating a\ + \ new\ntransaction as soon as the old transaction terminates. If AND CHAIN is\n\ + specified, then there is effectively nothing between the old and new\ntransactions,\ + \ although they remain separate. The characteristics of the new\ntransaction\ + \ will be the same as the characteristics of the old one - that is,\nthe new\ + \ transaction will have the same access mode, isolation level and\ndiagnostics\ + \ area size (we'll discuss all of these shortly) as the transaction\njust terminated.\ + \ The AND NO CHAIN option just tells your DBMS to end the\ntransaction - that\ + \ is, these four SQL statements are equivalent:\n\nROLLBACK; \nROLLBACK WORK;\ + \ \nROLLBACK AND NO CHAIN; \nROLLBACK WORK AND NO CHAIN;\n\nAll of them end\ + \ a transaction without saving any transaction characteristics.\nThe only other\ + \ options, the equivalent statements:\n\nROLLBACK AND CHAIN;\nROLLBACK WORK\ + \ AND CHAIN;\n\nboth tell your DBMS to end a transaction, but to save that transaction's\n\ + characteristics for the next transaction.\n\nROLLBACK is much simpler than COMMIT:\ + \ it may involve no more than a few\ndeletions (of Cursors, locks, prepared\ + \ SQL statements and log-file entries).\nIt's usually assumed that ROLLBACK\ + \ can't fail, although such a thing is\nconceivable (for example, an encompassing\ + \ transaction might reject an attempt\nto ROLLBACK because it's lining up for\ + \ a COMMIT).\n\nROLLBACK cancels all effects of a transaction. It does not cancel\ + \ effects on\nobjects outside the DBMS's control (for example the values in\ + \ host program\nvariables or the settings made by some SQL/CLI function calls).\ + \ But in\ngeneral, it is a convenient statement for those situations when you\ + \ say \"oops,\nthis isn't working\" or when you simply don't care whether your\ + \ temporary work\nbecomes permanent or not.\n\nHere is a moot question. If all\ + \ you've been doing is SELECTs, so that there\nhave been no data changes, should\ + \ you end the transaction with ROLLBACK or\nCOMMIT? It shouldn't really matter\ + \ because both ROLLBACK and COMMIT do the\nsame transaction-terminating job.\ + \ However, the popular conception is that\nROLLBACK implies failure, so after\ + \ a successful series of SELECT statements\nthe convention is to end the transaction\ + \ with COMMIT rather than ROLLBACK.\n\nMariaDB (and most other DBMSs) supports\ + \ rollback of SQL-data change\nstatements, but not of SQL-Schema statements.\ + \ This means that if you use any\nof CREATE, ALTER, DROP, GRANT, REVOKE, you\ + \ are implicitly committing at\nexecution time.\n\nINSERT INTO Table_2 VALUES(5);\ + \ \n ..." + examples: [] + - name: ROUND + category_id: numeric + category_label: Numeric Functions + tags: + - numeric + aliases: [] + signature: + display: ROUND(X) + args: + - name: X + optional: false + type: any + summary: Rounds the argument X to D decimal places. + description: 'Rounds the argument X to D decimal places. D defaults to 0 if not + specified. D can be negative to cause D digits left of the decimal point of + the value X to become zero. The rounding algorithm depends on the data type + of X: * for floating point types (FLOAT, DOUBLE) the C libraries rounding function + is used, so the behavior *may* differ between operating systems * for fixed + point types (DECIMAL, DEC/NUMBER/FIXED) the "round half up" rule is used, meaning + that e.g. a value ending in exactly .5 is always rounded up.' + examples: + - sql: SELECT ROUND(-1.23); + result: +--------------+ | ROUND(-1.23) | +--------------+ | -1 | + +--------------+ + - sql: SELECT ROUND(-1.58); + result: +--------------+ | ROUND(-1.58) | +--------------+ | -2 | + +--------------+ + - sql: SELECT ROUND(1.58); + result: +-------------+ | ROUND(1.58) | +-------------+ | 2 | +-------------+ + - sql: SELECT ROUND(1.298, 1); + result: +-----------------+ | ROUND(1.298, 1) | +-----------------+ | 1.3 + | +-----------------+ + - sql: SELECT ROUND(1.298, 0); + result: +-----------------+ | ROUND(1.298, 0) | +-----------------+ | 1 + | +-----------------+ + - sql: "SELECT ROUND(23.298, -1);\n ..." + - name: ROW + category_id: data_types + category_label: Data Types + tags: + - data_types + aliases: [] + signature: + display: ROW( [{, }... ]) + args: + - name: [{ + optional: false + type: any + - name: }... ] + optional: false + type: any + summary: ROW is a data type for stored procedure variables. + description: "ROW is a data type for stored procedure variables.\n\nFeatures\n\ + --------\n\nROW fields as normal variables\n------------------------------\n\ + \nROW fields (members) act as normal variables, and are able to appear in all\n\ + query parts where a stored procedure variable is allowed:\n\n* Assignment is\ + \ using the := operator and the SET command:\n\na.x:= 10;\na.x:= b.x;\nSET a.x=\ + \ 10, a.y=20, a.z= b.z;\n\n* Passing to functions and operators:\n\nSELECT f1(rec.a),\ + \ rec.a<10;\n\n* Clauses (select list, WHERE, HAVING, LIMIT, etc...,):\n\nSELECT\ + \ var.a, t1.b FROM t1 WHERE t1.b=var.b LIMIT var.c;\n\n* INSERT values:\n\n\ + INSERT INTO t1 VALUES (rec.a, rec.b, rec.c);\n\n* SELECT .. INTO targets\n\n\ + SELECT a,b INTO rec.a, rec.b FROM t1 WHERE t1.id=10;\n\n* Dynamic SQL out parameters\ + \ (EXECUTE and EXECUTE IMMEDIATE)\n\nEXECUTE IMMEDIATE 'CALL proc_with_out_param(?)'\ + \ USING rec.a;\n\nROW type variables as FETCH targets\n-----------------------------------\n\ + \nROW type variables are allowed as FETCH targets:\n\nFETCH cur INTO rec;\n\n\ + where cur is a CURSOR and rec is a ROW type stored procedure variable.\n\nNote,\ + \ currently an attempt to use FETCH for a ROW type variable returns this\nerror:\n\ + \nERROR 1328 (HY000): Incorrect number of FETCH variables\n ..." + examples: [] + - name: ROWNUM + category_id: information + category_label: Information Functions + tags: + - information + aliases: [] + signature: + display: ROWNUM + args: [] + summary: ROWNUM() returns the current number of accepted rows in the current context. + description: 'ROWNUM() returns the current number of accepted rows in the current + context. It main purpose is to emulate the ROWNUM pseudo column in Oracle. For + MariaDB native applications, we recommend the usage of LIMIT, as it is easier + to use and gives more predictable results than the usage of ROWNUM(). The main + difference between using LIMIT and ROWNUM() to limit the rows in the result + is that LIMIT works on the result set while ROWNUM works on the number of accepted + rows (before any ORDER or GROUP BY clauses). The following queries will return + the same results: SELECT * from t1 LIMIT 10; SELECT * from t1 WHERE ROWNUM() + <= 10; While the following may return different results based on in which orders + the rows are found: SELECT * from t1 ORDER BY a LIMIT 10; SELECT * from t1 ORDER + BY a WHERE ROWNUM() <= 10; The recommended way to use ROWNUM to limit the number + of returned rows and get predictable results is to have the query in a subquery + and test for ROWNUM() in the outer query: SELECT * FROM (select * from t1 ORDER + BY a) WHERE ROWNUM() <= 10; ROWNUM() can be used in the following contexts: + * SELECT * INSERT * UPDATE * DELETE * LOAD DATA INFILE Used in other contexts, + ROWNUM() will return 0.' + examples: + - sql: INSERT INTO t1 VALUES (1,ROWNUM()),(2,ROWNUM()),(3,ROWNUM()); + result: INSERT INTO t1 VALUES (1),(2) returning a, ROWNUM(); + - sql: UPDATE t1 SET row_num_column=ROWNUM(); + result: DELETE FROM t1 WHERE a < 10 AND ROWNUM() < 2; + - sql: "LOAD DATA INFILE 'filename' into table t1 fields terminated by ','\n lines\ + \ terminated by \"\n\" (a,b) set c=ROWNUM();" + result: '...' + - name: ROW_COUNT + category_id: information + category_label: Information Functions + tags: + - information + aliases: [] + signature: + display: ROW_COUNT + args: [] + summary: ROW_COUNT() returns the number of rows updated, inserted or deleted by + the + description: 'ROW_COUNT() returns the number of rows updated, inserted or deleted + by the preceding statement. This is the same as the row count that the mariadb + client displays and the value from the mysql_affected_rows() C API function. + Generally: * For statements which return a result set (such as SELECT, SHOW, + DESC or HELP), returns -1, even when the result set is empty. This is also true + for administrative statements, such as OPTIMIZE. * For DML statements other + than SELECT and for ALTER TABLE, returns the number of affected rows. * For + DDL statements (including TRUNCATE) and for other statements which don''t return + any result set (such as USE, DO, SIGNAL or DEALLOCATE PREPARE), returns 0. For + UPDATE, affected rows is by default the number of rows that were actually changed. + If the CLIENT_FOUND_ROWS flag to mysql_real_connect() is specified when connecting + to mysqld, affected rows is instead the number of rows matched by the WHERE + clause. For REPLACE, deleted rows are also counted. So, if REPLACE deletes a + row and adds a new row, ROW_COUNT() returns 2. For INSERT ... ON DUPLICATE KEY, + updated rows are counted twice. So, if INSERT adds a new rows and modifies another + row, ROW_COUNT() returns 3. ROW_COUNT() does not take into account rows that + are not directly deleted/updated by the last statement. This means that rows + deleted by foreign keys or triggers are not counted. Warning: You can use ROW_COUNT() + with prepared statements, but you need to call it after EXECUTE, not after DEALLOCATE + PREPARE, because the row count for allocate prepare is always 0. Warning: When + used after a CALL statement, this function returns the number of rows affected + by the last statement in the procedure, not by the whole procedure. Warning: + After INSERT DELAYED, ROW_COUNT() returns the number of the rows you tried to + insert, not the number of the successful writes. This information can also be + found in the diagnostics area. Statements using the ROW_COUNT() function are + not safe for statement-based replication.' + examples: + - sql: "CREATE TABLE t (A INT);\n ..." + - name: ROW_NUMBER + category_id: window + category_label: Window Functions + tags: + - window + aliases: [] + signature: + display: ROW_NUMBER + args: [] + summary: ROW_NUMBER() is a window function that displays the number of a given + row, + description: ROW_NUMBER() is a window function that displays the number of a given + row, starting at one and following the ORDER BY sequence of the window function, + with identical values receiving different row numbers. It is similar to the + RANK() and DENSE_RANK() functions except that in that function, identical values + will receive the same rank for each result. + examples: + - sql: 'The distinction between DENSE_RANK(), RANK() and ROW_NUMBER():' + result: CREATE TABLE student(course VARCHAR(10), mark int, name varchar(10)); + - sql: "INSERT INTO student VALUES\n ('Maths', 60, 'Thulile'),\n ('Maths', 60,\ + \ 'Pritha'),\n ('Maths', 70, 'Voitto'),\n ('Maths', 55, 'Chun'),\n ('Biology',\ + \ 60, 'Bilal'),\n ('Biology', 70, 'Roger');" + result: SELECT + - sql: "DENSE_RANK() OVER (PARTITION BY course ORDER BY mark DESC) AS dense_rank,\n\ + \ ROW_NUMBER() OVER (PARTITION BY course ORDER BY mark DESC) AS row_num,\n\ + \ course, mark, name\nFROM student ORDER BY course, mark DESC;" + result: +------+------------+---------+---------+------+---------+ | rank | + dense_rank | row_num | course | mark | name | +------+------------+---------+---------+------+---------+ + | 1 | 1 | 1 | Biology | 70 | Roger | | 2 | 2 + | 2 | Biology | 60 | Bilal | | 1 | 1 | 1 | Maths | 70 + | Voitto | | 2 | 2 | 2 | Maths | 60 | Thulile | | 2 + | 2 | 3 | Maths | 60 | Pritha | | 4 | 3 | 4 + | Maths | 55 | Chun | +------+------------+---------+---------+------+---------+ + - sql: 'URL: https://mariadb.com/kb/en/row_number/' + - name: RPAD + category_id: string + category_label: String Functions + tags: + - string + aliases: [] + signature: + display: RPAD(str, len [, padstr]) + args: + - name: str + optional: false + type: any + - name: len [ + optional: false + type: any + - name: padstr] + optional: false + type: any + summary: Returns the string str, right-padded with the string padstr to a length + of len + description: Returns the string str, right-padded with the string padstr to a + length of len characters. If str is longer than len, the return value is shortened + to len characters. If padstr is omitted, the RPAD function pads spaces. Prior + to MariaDB 10.3.1, the padstr parameter was mandatory. Returns NULL if given + a NULL argument. If the result is empty (a length of zero), returns either an + empty string, or, from MariaDB 10.3.6 with SQL_MODE=Oracle, NULL. The Oracle + mode version of the function can be accessed outside of Oracle mode by using + RPAD_ORACLE as the function name. + examples: + - sql: SELECT RPAD('hello',10,'.'); + result: +----------------------+ | RPAD('hello',10,'.') | +----------------------+ + | hello..... | +----------------------+ + - sql: SELECT RPAD('hello',2,'.'); + result: +---------------------+ | RPAD('hello',2,'.') | +---------------------+ + | he | +---------------------+ + - sql: From MariaDB 10.3.1, with the pad string defaulting to space. + result: SELECT RPAD('hello',30); +--------------------------------+ | RPAD('hello',30) | + +--------------------------------+ | hello | +--------------------------------+ + - sql: 'Oracle mode version from MariaDB 10.3.6:' + result: SELECT RPAD('',0),RPAD_ORACLE('',0); +------------+-------------------+ + | RPAD('',0) | RPAD_ORACLE('',0) | +------------+-------------------+ | | + NULL | +------------+-------------------+ + - sql: 'URL: https://mariadb.com/kb/en/rpad/' + - name: RTRIM + category_id: string + category_label: String Functions + tags: + - string + aliases: [] + signature: + display: RTRIM(str) + args: + - name: str + optional: false + type: any + summary: Returns the string str with trailing space characters removed. + description: Returns the string str with trailing space characters removed. Returns + NULL if given a NULL argument. If the result is empty, returns either an empty + string, or, from MariaDB 10.3.6 with SQL_MODE=Oracle, NULL. The Oracle mode + version of the function can be accessed outside of Oracle mode by using RTRIM_ORACLE + as the function name. + examples: + - sql: SELECT QUOTE(RTRIM('MariaDB ')); + result: +-----------------------------+ | QUOTE(RTRIM('MariaDB ')) | +-----------------------------+ + | 'MariaDB' | +-----------------------------+ + - sql: 'Oracle mode version from MariaDB 10.3.6:' + result: SELECT RTRIM(''),RTRIM_ORACLE(''); +-----------+------------------+ + | RTRIM('') | RTRIM_ORACLE('') | +-----------+------------------+ | | + NULL | +-----------+------------------+ + - sql: 'URL: https://mariadb.com/kb/en/rtrim/' + - name: SCHEMA + category_id: information + category_label: Information Functions + tags: + - information + aliases: [] + signature: + display: SCHEMA + args: [] + summary: This function is a synonym for DATABASE(). + description: 'This function is a synonym for DATABASE(). URL: https://mariadb.com/kb/en/schema/' + examples: [] + - name: SECOND + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: SECOND(time) + args: + - name: time + optional: false + type: any + summary: Returns the second for a given time (which can include microseconds), + in the + description: Returns the second for a given time (which can include microseconds), + in the range 0 to 59, or NULL if not given a valid time value. + examples: + - sql: SELECT SECOND('10:05:03'); + result: +--------------------+ | SECOND('10:05:03') | +--------------------+ + | 3 | +--------------------+ + - sql: SELECT SECOND('10:05:01.999999'); + result: +---------------------------+ | SECOND('10:05:01.999999') | +---------------------------+ + | 1 | +---------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/second/' + - name: SEC_TO_TIME + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: SEC_TO_TIME(seconds) + args: + - name: seconds + optional: false + type: any + summary: Returns the seconds argument, converted to hours, minutes, and seconds, + as a + description: Returns the seconds argument, converted to hours, minutes, and seconds, + as a TIME value. The range of the result is constrained to that of the TIME + data type. A warning occurs if the argument corresponds to a value outside that + range. The time will be returned in the format hh:mm:ss, or hhmmss if used in + a numeric calculation. + examples: + - sql: SELECT SEC_TO_TIME(12414); + result: +--------------------+ | SEC_TO_TIME(12414) | +--------------------+ + | 03:26:54 | +--------------------+ + - sql: SELECT SEC_TO_TIME(12414)+0; + result: +----------------------+ | SEC_TO_TIME(12414)+0 | +----------------------+ + | 32654 | +----------------------+ + - sql: SELECT SEC_TO_TIME(9999999); + result: +----------------------+ | SEC_TO_TIME(9999999) | +----------------------+ + | 838:59:59 | +----------------------+ + - sql: SHOW WARNINGS; + result: '+---------+------+-------------------------------------------+ | Level | + Code | Message | +---------+------+-------------------------------------------+ + | Warning | 1292 | Truncated incorrect time value: ''9999999'' | +---------+------+-------------------------------------------+' + - sql: 'URL: https://mariadb.com/kb/en/sec_to_time/' + - name: SESSION_USER + category_id: information + category_label: Information Functions + tags: + - information + aliases: [] + signature: + display: SESSION_USER + args: [] + summary: SESSION_USER() is a synonym for USER(). + description: 'SESSION_USER() is a synonym for USER(). URL: https://mariadb.com/kb/en/session_user/' + examples: [] + - name: SETVAL + category_id: sequences + category_label: Sequences + tags: + - sequences + aliases: [] + signature: + display: SETVAL(sequence_name, next_value, [is_used, [round]]) + args: + - name: sequence_name + optional: false + type: any + - name: next_value + optional: false + type: any + - name: '[is_used' + optional: false + type: any + - name: round] + optional: true + type: any + summary: Set the next value to be returned for a SEQUENCE. + description: Set the next value to be returned for a SEQUENCE. This function is + compatible with PostgreSQL syntax, extended with the round argument. If the + is_used argument is not given or is 1 or true, then the next used value will + one after the given value. If is_used is 0 or false then the next generated + value will be the given value. If round is used then it will set the round value + (or the internal cycle count, starting at zero) for the sequence. If round is + not used, it's assumed to be 0. next_value must be an integer literal. For SEQUENCE + tables defined with CYCLE (see CREATE SEQUENCE) one should use both next_value + and round to define the next value. In this case the current sequence value + is defined to be round, next_value. The result returned by SETVAL() is next_value + or NULL if the given next_value and round is smaller than the current value. + SETVAL() will not set the SEQUENCE value to a something that is less than its + current value. This is needed to ensure that SETVAL() is replication safe. If + you want to set the SEQUENCE to a smaller number use ALTER SEQUENCE. If CYCLE + is used, first round and then next_value are compared to see if the value is + bigger than the current value. Internally, in the MariaDB server, SETVAL() is + used to inform slaves that a SEQUENCE has changed value. The slave may get SETVAL() + statements out of order, but this is ok as only the biggest one will have an + effect. SETVAL requires the INSERT privilege. + examples: + - sql: SELECT setval(foo, 42); -- Next nextval will return 43 SELECT + setval(foo, 42, true); -- Same as above SELECT setval(foo, 42, false); -- + Next nextval will return 42 + result: 'SETVAL setting higher and lower values on a sequence with an increment + of 10:' + - sql: SELECT NEXTVAL(s); + result: +------------+ | NEXTVAL(s) | +------------+ | 50 | +------------+ + - name: SFORMAT + category_id: string + category_label: String Functions + tags: + - string + aliases: [] + signature: + display: SFORMAT("The answer is {}.", 42) + args: + - name: '"The answer is {}."' + optional: false + type: any + - name: '42' + optional: false + type: any + summary: +----------------------------------+ + description: "+----------------------------------+\n| SFORMAT(\"The answer is\ + \ {}.\", 42) |\n+----------------------------------+\n| The answer is 42. \ + \ |\n+----------------------------------+\n\nCREATE TABLE test_sformat(mdb_release\ + \ char(6), mdev int, feature char(20));\n\nINSERT INTO test_sformat VALUES('10.7.0',\ + \ 25015, 'Python style sformat'), \n ('10.7.0', 4958, 'UUID');\n\nSELECT * FROM\ + \ test_sformat;\n+-------------+-------+----------------------+\n| mdb_release\ + \ | mdev | feature |\n+-------------+-------+----------------------+\n\ + | 10.7.0 | 25015 | Python style sformat |\n| 10.7.0 | 4958 | UUID\ + \ |\n+-------------+-------+----------------------+\n\nSELECT\ + \ SFORMAT('MariaDB Server {} has a preview for MDEV-{} which is about\n{}',\ + \ \n mdb_release, mdev, feature) AS 'Preview Release Examples'\n FROM test_sformat;\n\ + +------------------------------------------------------------------------------\n\ + ---------+\n| Preview Release Examples \ + \ \n |\n+------------------------------------------------------------------------------\n\ + ---------+\n| MariaDB Server 10.7.0 has a preview for MDEV-25015 which is about\ + \ Python\nstyle sformat |\n| MariaDB Server 10.7.0 has a preview for MDEV-4958\ + \ which is about UUID \n |\n+------------------------------------------------------------------------------\n\ + ---------+\n\nURL: https://mariadb.com/kb/en/sformat/" + examples: [] + - name: SHA1 + category_id: encryption + category_label: Encryption Functions + tags: + - encryption + aliases: [] + signature: + display: SHA1(str) + args: + - name: str + optional: false + type: any + summary: Calculates an SHA-1 160-bit checksum for the string str, as described + in RFC + description: Calculates an SHA-1 160-bit checksum for the string str, as described + in RFC 3174 (Secure Hash Algorithm). The value is returned as a string of 40 + hex digits, or NULL if the argument was NULL. As of MariaDB 5.5, the return + value is a nonbinary string in the connection character set and collation, determined + by the values of the character_set_connection and collation_connection system + variables. Before 5.5, the return value was a binary string. + examples: + - sql: SELECT SHA1('some boring text'); + result: +------------------------------------------+ | SHA1('some boring text') | + +------------------------------------------+ | af969fc2085b1bb6d31e517d5c456def5cdd7093 + | +------------------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/sha1/' + - name: SHA2 + category_id: encryption + category_label: Encryption Functions + tags: + - encryption + aliases: [] + signature: + display: SHA2(str,hash_len) + args: + - name: str + optional: false + type: any + - name: hash_len + optional: false + type: any + summary: Given a string str, calculates an SHA-2 checksum, which is considered + more + description: Given a string str, calculates an SHA-2 checksum, which is considered + more cryptographically secure than its SHA-1 equivalent. The SHA-2 family includes + SHA-224, SHA-256, SHA-384, and SHA-512, and the hash_len must correspond to + one of these, i.e. 224, 256, 384 or 512. 0 is equivalent to 256. The return + value is a nonbinary string in the connection character set and collation, determined + by the values of the character_set_connection and collation_connection system + variables. NULL is returned if the hash length is not valid, or the string str + is NULL. SHA2 will only work if MariaDB was has been configured with TLS support. + examples: + - sql: SELECT SHA2('Maria',224); + result: +----------------------------------------------------------+ | SHA2('Maria',224) | + +----------------------------------------------------------+ | 6cc67add32286412efcab9d0e1675a43a5c2ef3cec8879f81516ff83 + | +----------------------------------------------------------+ + - sql: SELECT SHA2('Maria',256); + result: +------------------------------------------------------------------+ + | SHA2('Maria',256) | +------------------------------------------------------------------+ + | 9ff18ebe7449349f358e3af0b57cf7a032c1c6b2272cb2656ff85eb112232f16 | +------------------------------------------------------------------+ + - sql: SELECT SHA2('Maria',0); + result: +------------------------------------------------------------------+ + | SHA2('Maria',0) | +------------------------------------------------------------------+ + | 9ff18ebe7449349f358e3af0b57cf7a032c1c6b2272cb2656ff85eb112232f16 | +------------------------------------------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/sha2/' + - name: SIGN + category_id: numeric + category_label: Numeric Functions + tags: + - numeric + aliases: [] + signature: + display: SIGN(X) + args: + - name: X + optional: false + type: any + summary: Returns the sign of the argument as -1, 0, or 1, depending on whether + X is + description: Returns the sign of the argument as -1, 0, or 1, depending on whether + X is negative, zero, or positive. + examples: + - sql: SELECT SIGN(-32); + result: +-----------+ | SIGN(-32) | +-----------+ | -1 | +-----------+ + - sql: SELECT SIGN(0); + result: +---------+ | SIGN(0) | +---------+ | 0 | +---------+ + - sql: SELECT SIGN(234); + result: +-----------+ | SIGN(234) | +-----------+ | 1 | +-----------+ + - sql: 'URL: https://mariadb.com/kb/en/sign/' + - name: SIN + category_id: numeric + category_label: Numeric Functions + tags: + - numeric + aliases: [] + signature: + display: SIN(X) + args: + - name: X + optional: false + type: any + summary: Returns the sine of X, where X is given in radians. + description: Returns the sine of X, where X is given in radians. + examples: + - sql: SELECT SIN(1.5707963267948966); + result: +-------------------------+ | SIN(1.5707963267948966) | +-------------------------+ + | 1 | +-------------------------+ + - sql: SELECT SIN(PI()); + result: +----------------------+ | SIN(PI()) | +----------------------+ + | 1.22460635382238e-16 | +----------------------+ + - sql: SELECT ROUND(SIN(PI())); + result: +------------------+ | ROUND(SIN(PI())) | +------------------+ | 0 + | +------------------+ + - sql: 'URL: https://mariadb.com/kb/en/sin/' + - name: SLEEP + category_id: miscellaneous + category_label: Miscellaneous Functions + tags: + - miscellaneous + aliases: [] + signature: + display: SLEEP(duration) + args: + - name: duration + optional: false + type: any + summary: Sleeps (pauses) for the number of seconds given by the duration argument, + then + description: 'Sleeps (pauses) for the number of seconds given by the duration + argument, then returns 0. If SLEEP() is interrupted, it returns 1. The duration + may have a fractional part given in microseconds. Statements using the SLEEP() + function are not safe for statement-based replication. Example ------- SELECT + SLEEP(5.5); +------------+ | SLEEP(5.5) | +------------+ | 0 | +------------+ + 1 row in set (5.50 sec) URL: https://mariadb.com/kb/en/sleep/' + examples: [] + - name: SMALLINT + category_id: data_types + category_label: Data Types + tags: + - data_types + aliases: [] + signature: + display: SMALLINT(M) + args: + - name: M + optional: false + type: any + summary: A small integer. + description: 'A small integer. The signed range is -32768 to 32767. The unsigned + range is 0 to 65535. If a column has been set to ZEROFILL, all values will be + prepended by zeros so that the SMALLINT value contains a number of M digits. + Note: If the ZEROFILL attribute has been specified, the column will automatically + become UNSIGNED. INT2 is a synonym for SMALLINT. For more details on the attributes, + see Numeric Data Type Overview.' + examples: + - sql: CREATE TABLE smallints (a SMALLINT,b SMALLINT UNSIGNED,c SMALLINT ZEROFILL); + result: 'With strict_mode set, the default from MariaDB 10.2.4:' + - sql: 'INSERT INTO smallints VALUES (-10,-10,-10); ERROR 1264 (22003): Out of + range value for column ''b'' at row 1' + result: INSERT INTO smallints VALUES (-10,10,-10); + - sql: INSERT INTO smallints VALUES (-10,10,10); + result: INSERT INTO smallints VALUES (32768,32768,32768); + - sql: INSERT INTO smallints VALUES (32767,32768,32768); + result: SELECT * FROM smallints; +-------+-------+-------+ | a | b | + c | +-------+-------+-------+ | -10 | 10 | 00010 | | 32767 | 32768 + | 32768 | +-------+-------+-------+ + - sql: 'With strict_mode unset, the default until MariaDB 10.2.3:' + result: INSERT INTO smallints VALUES (-10,-10,-10); + - sql: 'Warning (Code 1264): Out of range value for column ''b'' at row 1 Warning + (Code 1264): Out of range value for column ''c'' at row 1' + result: INSERT INTO smallints VALUES (-10,10,-10); + - sql: '...' + - name: SOUNDEX + category_id: string + category_label: String Functions + tags: + - string + aliases: [] + signature: + display: SOUNDEX(str) + args: + - name: str + optional: false + type: any + summary: Returns a soundex string from str. + description: "Returns a soundex string from str. Two strings that sound almost\ + \ the same\nshould have identical soundex strings. A standard soundex string\ + \ is four\ncharacters long, but the SOUNDEX() function returns an arbitrarily\ + \ long\nstring. You can use SUBSTRING() on the result to get a standard soundex\n\ + string. All non-alphabetic characters in str are ignored. All international\n\ + alphabetic characters outside the A-Z range are treated as vowels.\n\nImportant:\ + \ When using SOUNDEX(), you should be aware of the following details:\n\n* This\ + \ function, as currently implemented, is intended to work well with\n strings\ + \ that are in the English language only. Strings in other languages may\n not\ + \ produce reasonable results.\n\n* This function implements the original Soundex\ + \ algorithm, not the more\npopular enhanced version (also described by D. Knuth).\ + \ The difference is that\noriginal version discards vowels first and duplicates\ + \ second, whereas the\nenhanced version discards duplicates first and vowels\ + \ second." + examples: + - sql: SOUNDEX('Hello'); + result: +------------------+ | SOUNDEX('Hello') | +------------------+ | H400 | + +------------------+ + - sql: SELECT SOUNDEX('MariaDB'); + result: +--------------------+ | SOUNDEX('MariaDB') | +--------------------+ + | M631 | +--------------------+ + - sql: SELECT SOUNDEX('Knowledgebase'); + result: +--------------------------+ | SOUNDEX('Knowledgebase') | +--------------------------+ + | K543212 | +--------------------------+ + - sql: SELECT givenname, surname FROM users WHERE SOUNDEX(givenname) = SOUNDEX("robert"); + result: +-----------+---------+ | givenname | surname | +-----------+---------+ + | Roberto | Castro | +-----------+---------+ + - sql: 'URL: https://mariadb.com/kb/en/soundex/' + - name: SPACE + category_id: string + category_label: String Functions + tags: + - string + aliases: [] + signature: + display: SPACE(N) + args: + - name: N + optional: false + type: any + summary: Returns a string consisting of N space characters. + description: Returns a string consisting of N space characters. If N is NULL, + returns NULL. + examples: + - sql: SELECT QUOTE(SPACE(6)); + result: +-----------------+ | QUOTE(SPACE(6)) | +-----------------+ | ' ' | + +-----------------+ + - sql: 'URL: https://mariadb.com/kb/en/space/' + - name: SPIDER_BG_DIRECT_SQL + category_id: spider + category_label: Spider Functions + tags: + - spider + aliases: [] + signature: + display: SPIDER_BG_DIRECT_SQL('sql', 'tmp_table_list', 'parameters') + args: + - name: '''sql''' + optional: false + type: any + - name: '''tmp_table_list''' + optional: false + type: any + - name: '''parameters''' + optional: false + type: any + summary: Executes the given SQL statement in the background on the remote server, + as + description: Executes the given SQL statement in the background on the remote + server, as defined in the parameters listing. If the query returns a result-set, + it sttores the results in the given temporary table. When the given SQL statement + executes successfully, this function returns the number of called UDF's. It + returns 0 when the given SQL statement fails. This function is a UDF installed + with the Spider storage engine. + examples: + - sql: "SELECT SPIDER_BG_DIRECT_SQL('SELECT * FROM example_table', '',\n 'srv\ + \ \"node1\", port \"8607\"') AS \"Direct Query\";" + result: +--------------+ | Direct Query | +--------------+ | 1 | + +--------------+ + - sql: Parameters ---------- + result: error_rw_mode + - sql: '* Description: Returns empty results on network error. 0 : Return error + on getting network error. 1: Return 0 records on getting network error.' + result: '* Default Table Value: 0' + - sql: 'URL: https://mariadb.com/kb/en/spider_bg_direct_sql/' + - name: SPIDER_COPY_TABLES + category_id: spider + category_label: Spider Functions + tags: + - spider + aliases: [] + signature: + display: SPIDER_COPY_TABLES(spider_table_name, source_link_id, destination_link_id_list + [,parameters]) + args: + - name: spider_table_name + optional: false + type: any + - name: source_link_id + optional: false + type: any + - name: destination_link_id_list [ + optional: false + type: any + - name: parameters] + optional: false + type: any + summary: A UDF installed with the Spider Storage Engine, this function copies + table + description: 'A UDF installed with the Spider Storage Engine, this function copies + table data from source_link_id to destination_link_id_list. The service does + not need to be stopped in order to copy. If the Spider table is partitioned, + the name must be of the format table_name#P#partition_name. The partition name + can be viewed in the mysql.spider_tables table, for example: SELECT table_name + FROM mysql.spider_tables; +-------------+ | table_name | +-------------+ | + spt_a#P#pt1 | | spt_a#P#pt2 | | spt_a#P#pt3 | +-------------+ Returns 1 if the + data was copied successfully, or 0 if copying the data failed. URL: https://mariadb.com/kb/en/spider_copy_tables/' + examples: [] + - name: SPIDER_DIRECT_SQL + category_id: spider + category_label: Spider Functions + tags: + - spider + aliases: [] + signature: + display: SPIDER_DIRECT_SQL('sql', 'tmp_table_list', 'parameters') + args: + - name: '''sql''' + optional: false + type: any + - name: '''tmp_table_list''' + optional: false + type: any + - name: '''parameters''' + optional: false + type: any + summary: A UDF installed with the Spider Storage Engine, this function is used + to + description: A UDF installed with the Spider Storage Engine, this function is + used to execute the SQL string sql on the remote server, as defined in parameters. + If any resultsets are returned, they are stored in the tmp_table_list. The function + returns 1 if the SQL executes successfully, or 0 if it fails. + examples: + - sql: SELECT SPIDER_DIRECT_SQL('SELECT * FROM s', '', 'srv "node1", port "8607"'); + result: +----------------------------------------------------------------------+ + | SPIDER_DIRECT_SQL('SELECT * FROM s', '', 'srv "node1", port "8607"') | +----------------------------------------------------------------------+ + | 1 | +----------------------------------------------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/spider_direct_sql/' + - name: SPIDER_FLUSH_TABLE_MON_CACHE + category_id: spider + category_label: Spider Functions + tags: + - spider + aliases: [] + signature: + display: SPIDER_FLUSH_TABLE_MON_CACHE + args: [] + summary: A UDF installed with the Spider Storage Engine, this function is used + for + description: A UDF installed with the Spider Storage Engine, this function is + used for refreshing monitoring server information. It returns a value of 1. + examples: + - sql: SELECT SPIDER_FLUSH_TABLE_MON_CACHE(); + result: +--------------------------------+ | SPIDER_FLUSH_TABLE_MON_CACHE() + | +--------------------------------+ | 1 | +--------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/spider_flush_table_mon_cache/' + - name: SQRT + category_id: numeric + category_label: Numeric Functions + tags: + - numeric + aliases: [] + signature: + display: SQRT(X) + args: + - name: X + optional: false + type: any + summary: Returns the square root of X. + description: Returns the square root of X. If X is negative, NULL is returned. + examples: + - sql: SELECT SQRT(4); + result: +---------+ | SQRT(4) | +---------+ | 2 | +---------+ + - sql: SELECT SQRT(20); + result: +------------------+ | SQRT(20) | +------------------+ | 4.47213595499958 + | +------------------+ + - sql: SELECT SQRT(-16); + result: +-----------+ | SQRT(-16) | +-----------+ | NULL | +-----------+ + - sql: SELECT SQRT(1764); + result: +------------+ | SQRT(1764) | +------------+ | 42 | +------------+ + - sql: 'URL: https://mariadb.com/kb/en/sqrt/' + - name: STD + category_id: group_by + category_label: Functions and Modifiers for Use with GROUP BY + tags: + - group_by + aliases: [] + signature: + display: STD(expr) + args: + - name: expr + optional: false + type: any + summary: Returns the population standard deviation of expr. + description: Returns the population standard deviation of expr. This is an extension + to standard SQL. The standard SQL function STDDEV_POP() can be used instead. + It is an aggregate function, and so can be used with the GROUP BY clause. STD() + can be used as a window function. This function returns NULL if there were no + matching rows. + examples: + - sql: 'As an aggregate function:' + result: CREATE OR REPLACE TABLE stats (category VARCHAR(2), x INT); + - sql: "INSERT INTO stats VALUES\n ('a',1),('a',2),('a',3),\n ('b',11),('b',12),('b',20),('b',30),('b',60);" + result: SELECT category, STDDEV_POP(x), STDDEV_SAMP(x), VAR_POP(x) + - sql: +----------+---------------+----------------+------------+ + result: '| category | STDDEV_POP(x) | STDDEV_SAMP(x) | VAR_POP(x) | +----------+---------------+----------------+------------+ + | a | 0.8165 | 1.0000 | 0.6667 | | b | 18.0400 + | 20.1693 | 325.4400 | +----------+---------------+----------------+------------+' + - sql: 'As a window function:' + result: CREATE OR REPLACE TABLE student_test (name CHAR(10), test CHAR(10), + score + - sql: "INSERT INTO student_test VALUES\n ('Chun', 'SQL', 75), ('Chun', 'Tuning',\ + \ 73),\n ('Esben', 'SQL', 43), ('Esben', 'Tuning', 31),\n ('Kaolin', 'SQL',\ + \ 56), ('Kaolin', 'Tuning', 88),\n ('Tatiana', 'SQL', 87);" + result: SELECT name, test, score, STDDEV_POP(score) + - sql: +---------+--------+-------+----------------+ + result: '| name | test | score | stddev_results | +---------+--------+-------+----------------+ + | Chun | SQL | 75 | 16.9466 | | Chun | Tuning | 73 | 24.1247 + | | Esben | SQL | 43 | 16.9466 | | Esben | Tuning | 31 + | 24.1247 | | Kaolin | SQL | 56 | 16.9466 |' + - name: STDDEV + category_id: group_by + category_label: Functions and Modifiers for Use with GROUP BY + tags: + - group_by + aliases: [] + signature: + display: STDDEV(expr) + args: + - name: expr + optional: false + type: any + summary: Returns the population standard deviation of expr. + description: Returns the population standard deviation of expr. This function + is provided for compatibility with Oracle. The standard SQL function STDDEV_POP() + can be used instead. It is an aggregate function, and so can be used with the + GROUP BY clause. STDDEV() can be used as a window function. This function returns + NULL if there were no matching rows. + examples: + - sql: 'As an aggregate function:' + result: CREATE OR REPLACE TABLE stats (category VARCHAR(2), x INT); + - sql: "INSERT INTO stats VALUES\n ('a',1),('a',2),('a',3),\n ('b',11),('b',12),('b',20),('b',30),('b',60);" + result: SELECT category, STDDEV_POP(x), STDDEV_SAMP(x), VAR_POP(x) + - sql: +----------+---------------+----------------+------------+ + result: '| category | STDDEV_POP(x) | STDDEV_SAMP(x) | VAR_POP(x) | +----------+---------------+----------------+------------+ + | a | 0.8165 | 1.0000 | 0.6667 | | b | 18.0400 + | 20.1693 | 325.4400 | +----------+---------------+----------------+------------+' + - sql: 'As a window function:' + result: CREATE OR REPLACE TABLE student_test (name CHAR(10), test CHAR(10), + score + - sql: "INSERT INTO student_test VALUES\n ('Chun', 'SQL', 75), ('Chun', 'Tuning',\ + \ 73),\n ('Esben', 'SQL', 43), ('Esben', 'Tuning', 31),\n ('Kaolin', 'SQL',\ + \ 56), ('Kaolin', 'Tuning', 88),\n ('Tatiana', 'SQL', 87);" + result: SELECT name, test, score, STDDEV_POP(score) + - sql: +---------+--------+-------+----------------+ + result: '| name | test | score | stddev_results | +---------+--------+-------+----------------+ + | Chun | SQL | 75 | 16.9466 | | Chun | Tuning | 73 | 24.1247 + | | Esben | SQL | 43 | 16.9466 | | Esben | Tuning | 31 + | 24.1247 |' + - name: STDDEV_POP + category_id: group_by + category_label: Functions and Modifiers for Use with GROUP BY + tags: + - group_by + aliases: [] + signature: + display: STDDEV_POP(expr) + args: + - name: expr + optional: false + type: any + summary: Returns the population standard deviation of expr (the square root of + description: Returns the population standard deviation of expr (the square root + of VAR_POP()). You can also use STD() or STDDEV(), which are equivalent but + not standard SQL. It is an aggregate function, and so can be used with the GROUP + BY clause. STDDEV_POP() can be used as a window function. STDDEV_POP() returns + NULL if there were no matching rows. + examples: + - sql: 'As an aggregate function:' + result: CREATE OR REPLACE TABLE stats (category VARCHAR(2), x INT); + - sql: "INSERT INTO stats VALUES\n ('a',1),('a',2),('a',3),\n ('b',11),('b',12),('b',20),('b',30),('b',60);" + result: SELECT category, STDDEV_POP(x), STDDEV_SAMP(x), VAR_POP(x) + - sql: +----------+---------------+----------------+------------+ + result: '| category | STDDEV_POP(x) | STDDEV_SAMP(x) | VAR_POP(x) | +----------+---------------+----------------+------------+ + | a | 0.8165 | 1.0000 | 0.6667 | | b | 18.0400 + | 20.1693 | 325.4400 | +----------+---------------+----------------+------------+' + - sql: 'As a window function:' + result: CREATE OR REPLACE TABLE student_test (name CHAR(10), test CHAR(10), + score + - sql: "INSERT INTO student_test VALUES\n ('Chun', 'SQL', 75), ('Chun', 'Tuning',\ + \ 73),\n ('Esben', 'SQL', 43), ('Esben', 'Tuning', 31),\n ('Kaolin', 'SQL',\ + \ 56), ('Kaolin', 'Tuning', 88),\n ('Tatiana', 'SQL', 87);" + result: SELECT name, test, score, STDDEV_POP(score) + - sql: +---------+--------+-------+----------------+ + result: '| name | test | score | stddev_results | +---------+--------+-------+----------------+ + | Chun | SQL | 75 | 16.9466 | | Chun | Tuning | 73 | 24.1247 + | | Esben | SQL | 43 | 16.9466 | | Esben | Tuning | 31 + | 24.1247 |' + - name: STDDEV_SAMP + category_id: group_by + category_label: Functions and Modifiers for Use with GROUP BY + tags: + - group_by + aliases: [] + signature: + display: STDDEV_SAMP(expr) + args: + - name: expr + optional: false + type: any + summary: Returns the sample standard deviation of expr (the square root of VAR_SAMP()). + description: 'Returns the sample standard deviation of expr (the square root of + VAR_SAMP()). It is an aggregate function, and so can be used with the GROUP + BY clause. STDDEV_SAMP() can be used as a window function. STDDEV_SAMP() returns + NULL if there were no matching rows. URL: https://mariadb.com/kb/en/stddev_samp/' + examples: [] + - name: STRCMP + category_id: string + category_label: String Functions + tags: + - string + aliases: [] + signature: + display: STRCMP(expr1,expr2) + args: + - name: expr1 + optional: false + type: any + - name: expr2 + optional: false + type: any + summary: STRCMP() returns 0 if the strings are the same, -1 if the first argument + is + description: STRCMP() returns 0 if the strings are the same, -1 if the first argument + is smaller than the second according to the current sort order, and 1 if the + strings are otherwise not the same. Returns NULL is either argument is NULL. + examples: + - sql: SELECT STRCMP('text', 'text2'); + result: +-------------------------+ | STRCMP('text', 'text2') | +-------------------------+ + | -1 | +-------------------------+ + - sql: SELECT STRCMP('text2', 'text'); + result: +-------------------------+ | STRCMP('text2', 'text') | +-------------------------+ + | 1 | +-------------------------+ + - sql: SELECT STRCMP('text', 'text'); + result: +------------------------+ | STRCMP('text', 'text') | +------------------------+ + | 0 | +------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/strcmp/' + - name: STR_TO_DATE + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: STR_TO_DATE(str,format) + args: + - name: str + optional: false + type: any + - name: format + optional: false + type: any + summary: This is the inverse of the DATE_FORMAT() function. + description: "This is the inverse of the DATE_FORMAT() function. It takes a string\ + \ str and a\nformat string format. STR_TO_DATE() returns a DATETIME value if\ + \ the format\nstring contains both date and time parts, or a DATE or TIME value\ + \ if the\nstring contains only date or time parts.\n\nThe date, time, or datetime\ + \ values contained in str should be given in the\nformat indicated by format.\ + \ If str contains an illegal date, time, or datetime\nvalue, STR_TO_DATE() returns\ + \ NULL. An illegal value also produces a warning.\n\nUnder specific SQL_MODE\ + \ settings an error may also be generated if the str\nisn't a valid date:\n\n\ + * ALLOW_INVALID_DATES\n* NO_ZERO_DATE\n* NO_ZERO_IN_DATE\n\nThe options that\ + \ can be used by STR_TO_DATE(), as well as its inverse\nDATE_FORMAT() and the\ + \ FROM_UNIXTIME() function, are:\n\n+---------------------------+------------------------------------------------+\n\ + | Option | Description \ + \ |\n+---------------------------+------------------------------------------------+\n\ + | %a | Short weekday name in current locale \ + \ |\n| | (Variable lc_time_names). \ + \ |\n+---------------------------+------------------------------------------------+\n\ + | %b | Short form month name in current locale. For \ + \ |\n| | locale en_US this is one of: \ + \ |\n| | Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov\ + \ |\n| | or Dec. \ + \ |\n+---------------------------+------------------------------------------------+\n\ + | %c | Month with 1 or 2 digits. \ + \ |\n+---------------------------+------------------------------------------------+\n\ + | %D | Day with English suffix 'th', 'nd', 'st' or \ + \ |\n| | 'rd''. (1st, 2nd, 3rd...). \ + \ |\n+---------------------------+------------------------------------------------+\n\ + | %d | Day with 2 digits. \ + \ |\n+---------------------------+------------------------------------------------+\n\ + | %e | Day with 1 or 2 digits. \ + \ |\n+---------------------------+------------------------------------------------+\n\ + | %f | Microseconds 6 digits. \ + \ |\n+---------------------------+------------------------------------------------+\n\ + | %H | Hour with 2 digits between 00-23. \ + \ |\n+---------------------------+------------------------------------------------+\n\ + | %h | Hour with 2 digits between 01-12. \ + \ |\n+---------------------------+------------------------------------------------+\n\ + | %I | Hour with 2 digits between 01-12. \ + \ |\n+---------------------------+------------------------------------------------+\n\ + | %i | Minute with 2 digits. \ + \ |\n+---------------------------+------------------------------------------------+\n\ + | %j | Day of the year (001-366) \ + \ |\n ..." + examples: [] + - name: ST_AREA + category_id: polygon_properties + category_label: Polygon Properties + tags: + - polygon_properties + aliases: [] + signature: + display: ST_AREA(poly) + args: + - name: poly + optional: false + type: any + summary: Returns as a double-precision number the area of the Polygon value poly, + as + description: Returns as a double-precision number the area of the Polygon value + poly, as measured in its spatial reference system. ST_Area() and Area() are + synonyms. + examples: + - sql: SET @poly = 'Polygon((0 0,0 3,3 0,0 0),(1 1,1 2,2 1,1 1))'; + result: SELECT Area(GeomFromText(@poly)); +---------------------------+ | Area(GeomFromText(@poly)) + | +---------------------------+ | 4 | +---------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/st_area/' + - name: ST_AsBinary + category_id: wkb + category_label: WKB + tags: + - wkb + aliases: [] + signature: + display: ST_AsBinary(g) + args: + - name: g + optional: false + type: any + summary: Converts a value in internal geometry format to its WKB representation + and + description: Converts a value in internal geometry format to its WKB representation + and returns the binary result. ST_AsBinary(), AsBinary(), ST_AsWKB() and AsWKB() + are synonyms, + examples: + - sql: SET @poly = ST_GeomFromText('POLYGON((0 0,0 1,1 1,1 0,0 0))'); SELECT ST_AsBinary(@poly); + result: SELECT ST_AsText(ST_GeomFromWKB(ST_AsWKB(@poly))); +--------------------------------------------+ + | ST_AsText(ST_GeomFromWKB(ST_AsWKB(@poly))) | +--------------------------------------------+ + | POLYGON((0 0,0 1,1 1,1 0,0 0)) | +--------------------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/st_asbinary/' + - name: ST_AsGeoJSON + category_id: geojson + category_label: GeoJSON + tags: + - geojson + aliases: [] + signature: + display: ST_AsGeoJSON(g[, max_decimals[, options]]) + args: + - name: g[ + optional: false + type: any + - name: max_decimals[ + optional: false + type: any + - name: options]] + optional: false + type: any + summary: Returns the given geometry g as a GeoJSON element. + description: Returns the given geometry g as a GeoJSON element. The optional max_decimals + limits the maximum number of decimals displayed. The optional options flag can + be set to 1 to add a bounding box to the output. + examples: + - sql: SELECT ST_AsGeoJSON(ST_GeomFromText('POINT(5.3 7.2)')); + result: '+-------------------------------------------------+ | ST_AsGeoJSON(ST_GeomFromText(''POINT(5.3 + 7.2)'')) | +-------------------------------------------------+ | {"type": + "Point", "coordinates": [5.3, 7.2]} | +-------------------------------------------------+' + - sql: 'URL: https://mariadb.com/kb/en/geojson-st_asgeojson/' + - name: ST_AsText + category_id: wkt + category_label: WKT + tags: + - wkt + aliases: [] + signature: + display: ST_AsText(g) + args: + - name: g + optional: false + type: any + summary: Converts a value in internal geometry format to its WKT representation + and + description: Converts a value in internal geometry format to its WKT representation + and returns the string result. ST_AsText(), AsText(), ST_AsWKT() and AsWKT() + are all synonyms. + examples: + - sql: SET @g = 'LineString(1 1,4 4,6 6)'; + result: SELECT ST_AsText(ST_GeomFromText(@g)); +--------------------------------+ + | ST_AsText(ST_GeomFromText(@g)) | +--------------------------------+ | LINESTRING(1 + 1,4 4,6 6) | +--------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/st_astext/' + - name: ST_BOUNDARY + category_id: geometry_properties + category_label: Geometry Properties + tags: + - geometry_properties + aliases: [] + signature: + display: ST_BOUNDARY(g) + args: + - name: g + optional: false + type: any + summary: Returns a geometry that is the closure of the combinatorial boundary + of the + description: Returns a geometry that is the closure of the combinatorial boundary + of the geometry value g. BOUNDARY() is a synonym. + examples: + - sql: SELECT ST_AsText(ST_Boundary(ST_GeomFromText('LINESTRING(3 3,0 0, -3 3)'))); + result: +----------------------------------------------------------------------+ + | ST_AsText(ST_Boundary(ST_GeomFromText('LINESTRING(3 3,0 0, -3 3)'))) | +----------------------------------------------------------------------+ + | MULTIPOINT(3 3,-3 3) | +----------------------------------------------------------------------+ + - sql: SELECT ST_AsText(ST_Boundary(ST_GeomFromText('POLYGON((3 3,0 0, -3 3, 3 + 3))'))); + result: +--------------------------------------------------------------------------+ + | ST_AsText(ST_Boundary(ST_GeomFromText('POLYGON((3 3,0 0, -3 3, 3 3))'))) + | +--------------------------------------------------------------------------+ + | LINESTRING(3 3,0 0,-3 3,3 3) | + +--------------------------------------------------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/st_boundary/' + - name: ST_BUFFER + category_id: geometry_constructors + category_label: Geometry Constructors + tags: + - geometry_constructors + aliases: [] + signature: + display: ST_BUFFER(g1,r) + args: + - name: g1 + optional: false + type: any + - name: r + optional: false + type: any + summary: Returns a geometry that represents all points whose distance from geometry + g1 + description: Returns a geometry that represents all points whose distance from + geometry g1 is less than or equal to distance, or radius, r. Uses for this function + could include creating for example a new geometry representing a buffer zone + around an island. BUFFER() is a synonym. + examples: + - sql: 'Determining whether a point is within a buffer zone:' + result: SET @g1 = ST_GEOMFROMTEXT('POLYGON((10 10, 10 20, 20 20, 20 10, 10 10))'); + - sql: SET @g2 = ST_GEOMFROMTEXT('POINT(8 8)'); + result: SELECT ST_WITHIN(@g2,ST_BUFFER(@g1,5)); +---------------------------------+ + | ST_WITHIN(@g2,ST_BUFFER(@g1,5)) | +---------------------------------+ | 1 + | +---------------------------------+ + - sql: SELECT ST_WITHIN(@g2,ST_BUFFER(@g1,1)); + result: +---------------------------------+ | ST_WITHIN(@g2,ST_BUFFER(@g1,1)) + | +---------------------------------+ | 0 | + +---------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/st_buffer/' + - name: ST_CENTROID + category_id: polygon_properties + category_label: Polygon Properties + tags: + - polygon_properties + aliases: [] + signature: + display: ST_CENTROID(mpoly) + args: + - name: mpoly + optional: false + type: any + summary: Returns a point reflecting the mathematical centroid (geometric center) + for + description: Returns a point reflecting the mathematical centroid (geometric center) + for the MultiPolygon mpoly. The resulting point will not necessarily be on the + MultiPolygon. ST_Centroid() and Centroid() are synonyms. + examples: + - sql: SET @poly = ST_GeomFromText('POLYGON((0 0,20 0,20 20,0 20,0 0))'); SELECT + ST_AsText(ST_Centroid(@poly)) AS center; + result: +--------------+ | center | +--------------+ | POINT(10 10) | + +--------------+ + - sql: 'URL: https://mariadb.com/kb/en/st_centroid/' + - name: ST_CONTAINS + category_id: geometry_relations + category_label: Geometry Relations + tags: + - geometry_relations + aliases: [] + signature: + display: ST_CONTAINS(g1,g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + summary: Returns 1 or 0 to indicate whether a geometry g1 completely contains + geometry + description: Returns 1 or 0 to indicate whether a geometry g1 completely contains + geometry g2. ST_CONTAINS() uses object shapes, while CONTAINS(), based on the + original MySQL implementation, uses object bounding rectangles. ST_CONTAINS + tests the opposite relationship to ST_WITHIN(). + examples: + - sql: SET @g1 = ST_GEOMFROMTEXT('POLYGON((175 150, 20 40, 50 60, 125 100, 175 + 150))'); + result: SET @g2 = ST_GEOMFROMTEXT('POINT(174 149)'); + - sql: SELECT ST_CONTAINS(@g1,@g2); + result: +----------------------+ | ST_CONTAINS(@g1,@g2) | +----------------------+ + | 1 | +----------------------+ + - sql: SET @g2 = ST_GEOMFROMTEXT('POINT(175 151)'); + result: SELECT ST_CONTAINS(@g1,@g2); +----------------------+ | ST_CONTAINS(@g1,@g2) + | +----------------------+ | 0 | +----------------------+ + - sql: 'URL: https://mariadb.com/kb/en/st-contains/' + - name: ST_CONVEXHULL + category_id: geometry_constructors + category_label: Geometry Constructors + tags: + - geometry_constructors + aliases: [] + signature: + display: ST_CONVEXHULL + args: [] + summary: Given a geometry, returns a geometry that is the minimum convex geometry + description: Given a geometry, returns a geometry that is the minimum convex geometry + enclosing all geometries within the set. Returns NULL if the geometry value + is NULL or an empty value. ST_ConvexHull() and ConvexHull() are synonyms. + examples: + - sql: 'The ConvexHull of a single point is simply the single point:' + result: SET @g = ST_GEOMFROMTEXT('Point(0 0)'); + - sql: SELECT ST_ASTEXT(ST_CONVEXHULL(@g)); + result: +------------------------------+ | ST_ASTEXT(ST_CONVEXHULL(@g)) | +------------------------------+ + | POINT(0 0) | +------------------------------+ + - sql: SET @g = ST_GEOMFROMTEXT('MultiPoint(0 0, 1 2, 2 3)'); + result: SELECT ST_ASTEXT(ST_CONVEXHULL(@g)); +------------------------------+ + | ST_ASTEXT(ST_CONVEXHULL(@g)) | +------------------------------+ | POLYGON((0 + 0,1 2,2 3,0 0)) | +------------------------------+ + - sql: SET @g = ST_GEOMFROMTEXT('MultiPoint( 1 1, 2 2, 5 3, 7 2, 9 3, 8 4, 6 6, + 6 9, 4 9, 1 5 )'); + result: SELECT ST_ASTEXT(ST_CONVEXHULL(@g)); +----------------------------------------+ + | ST_ASTEXT(ST_CONVEXHULL(@g)) | +----------------------------------------+ + | POLYGON((1 1,1 5,4 9,6 9,9 3,7 2,1 1)) | +----------------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/st_convexhull/' + - name: ST_CROSSES + category_id: geometry_relations + category_label: Geometry Relations + tags: + - geometry_relations + aliases: [] + signature: + display: ST_CROSSES(g1,g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + summary: Returns 1 if geometry g1 spatially crosses geometry g2. + description: "Returns 1 if geometry g1 spatially crosses geometry g2. Returns\ + \ NULL if g1 is\na Polygon or a MultiPolygon, or if g2 is a Point or a MultiPoint.\ + \ Otherwise,\nreturns 0.\n\nThe term spatially crosses denotes a spatial relation\ + \ between two given\ngeometries that has the following properties:\n\n* The\ + \ two geometries intersect\n* Their intersection results in a geometry that\ + \ has a dimension that is one\n less than the maximum dimension of the two given\ + \ geometries\n* Their intersection is not equal to either of the two given geometries\n\ + \nST_CROSSES() uses object shapes, while CROSSES(), based on the original MySQL\n\ + implementation, uses object bounding rectangles." + examples: + - sql: SET @g1 = ST_GEOMFROMTEXT('LINESTRING(174 149, 176 151)'); + result: SET @g2 = ST_GEOMFROMTEXT('POLYGON((175 150, 20 40, 50 60, 125 100, + 175 + - sql: SELECT ST_CROSSES(@g1,@g2); + result: +---------------------+ | ST_CROSSES(@g1,@g2) | +---------------------+ + | 1 | +---------------------+ + - sql: SET @g1 = ST_GEOMFROMTEXT('LINESTRING(176 149, 176 151)'); + result: SELECT ST_CROSSES(@g1,@g2); +---------------------+ | ST_CROSSES(@g1,@g2) + | +---------------------+ | 0 | +---------------------+ + - sql: 'URL: https://mariadb.com/kb/en/st-crosses/' + - name: ST_DIFFERENCE + category_id: geometry_relations + category_label: Geometry Relations + tags: + - geometry_relations + aliases: [] + signature: + display: ST_DIFFERENCE(g1,g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + summary: Returns a geometry representing the point set difference of the given + geometry + description: 'Returns a geometry representing the point set difference of the + given geometry values. Example ------- SET @g1 = POINT(10,10), @g2 = POINT(20,20); + SELECT ST_AsText(ST_Difference(@g1, @g2)); +------------------------------------+ + | ST_AsText(ST_Difference(@g1, @g2)) | +------------------------------------+ + | POINT(10 10) | +------------------------------------+ + URL: https://mariadb.com/kb/en/st_difference/' + examples: [] + - name: ST_DIMENSION + category_id: geometry_properties + category_label: Geometry Properties + tags: + - geometry_properties + aliases: [] + signature: + display: ST_DIMENSION(g) + args: + - name: g + optional: false + type: any + summary: Returns the inherent dimension of the geometry value g. + description: Returns the inherent dimension of the geometry value g. The result + can be +------------------------------------+---------------------------------------+ + | Dimension | Definition | + +------------------------------------+---------------------------------------+ + | -1 | empty geometry | + +------------------------------------+---------------------------------------+ + | 0 | geometry with no length or area | + +------------------------------------+---------------------------------------+ + | 1 | geometry with no area but nonzero | + | | length | + +------------------------------------+---------------------------------------+ + | 2 | geometry with nonzero area | + +------------------------------------+---------------------------------------+ + ST_Dimension() and Dimension() are synonyms. + examples: + - sql: SELECT Dimension(GeomFromText('LineString(1 1,2 2)')); + result: +------------------------------------------------+ | Dimension(GeomFromText('LineString(1 + 1,2 2)')) | +------------------------------------------------+ | 1 + | +------------------------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/st_dimension/' + - name: ST_DISJOINT + category_id: geometry_relations + category_label: Geometry Relations + tags: + - geometry_relations + aliases: [] + signature: + display: ST_DISJOINT(g1,g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + summary: Returns 1 or 0 to indicate whether geometry g1 is spatially disjoint + from + description: Returns 1 or 0 to indicate whether geometry g1 is spatially disjoint + from (does not intersect with) geometry g2. ST_DISJOINT() uses object shapes, + while DISJOINT(), based on the original MySQL implementation, uses object bounding + rectangles. ST_DISJOINT() tests the opposite relationship to ST_INTERSECTS(). + examples: + - sql: SET @g1 = ST_GEOMFROMTEXT('POINT(0 0)'); + result: SET @g2 = ST_GEOMFROMTEXT('LINESTRING(2 0, 0 2)'); + - sql: SELECT ST_DISJOINT(@g1,@g2); + result: +----------------------+ | ST_DISJOINT(@g1,@g2) | +----------------------+ + | 1 | +----------------------+ + - sql: SET @g2 = ST_GEOMFROMTEXT('LINESTRING(0 0, 0 2)'); + result: SELECT ST_DISJOINT(@g1,@g2); +----------------------+ | ST_DISJOINT(@g1,@g2) + | +----------------------+ | 0 | +----------------------+ + - sql: 'URL: https://mariadb.com/kb/en/st_disjoint/' + - name: ST_DISTANCE + category_id: geometry_relations + category_label: Geometry Relations + tags: + - geometry_relations + aliases: [] + signature: + display: ST_DISTANCE(g1,g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + summary: Returns the distance between two geometries, or null if not given valid + inputs. + description: 'Returns the distance between two geometries, or null if not given + valid inputs. Example ------- SELECT ST_Distance(POINT(1,2),POINT(2,2)); +------------------------------------+ + | ST_Distance(POINT(1,2),POINT(2,2)) | +------------------------------------+ + | 1 | +------------------------------------+ + URL: https://mariadb.com/kb/en/st_distance/' + examples: [] + - name: ST_DISTANCE_SPHERE + category_id: geometry_relations + category_label: Geometry Relations + tags: + - geometry_relations + aliases: [] + signature: + display: ST_DISTANCE_SPHERE(g1,g2,[r]) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + - name: r + optional: true + type: any + summary: Returns the spherical distance between two geometries (point or multipoint) + on + description: 'Returns the spherical distance between two geometries (point or + multipoint) on a sphere with the optional radius r (default is the Earth radius + if r is not specified), or NULL if not given valid inputs. Example ------- set + @zenica = ST_GeomFromText(''POINT(17.907743 44.203438)''); set @sarajevo = + ST_GeomFromText(''POINT(18.413076 43.856258)''); SELECT ST_Distance_Sphere(@zenica, + @sarajevo); 55878.59337591705 URL: https://mariadb.com/kb/en/st_distance_sphere/' + examples: [] + - name: ST_ENDPOINT + category_id: linestring_properties + category_label: LineString Properties + tags: + - linestring_properties + aliases: [] + signature: + display: ST_ENDPOINT(ls) + args: + - name: ls + optional: false + type: any + summary: Returns the Point that is the endpoint of the LineString value ls. + description: Returns the Point that is the endpoint of the LineString value ls. + ST_EndPoint() and EndPoint() are synonyms. + examples: + - sql: SET @ls = 'LineString(1 1,2 2,3 3)'; + result: SELECT AsText(EndPoint(GeomFromText(@ls))); +-------------------------------------+ + | AsText(EndPoint(GeomFromText(@ls))) | +-------------------------------------+ + | POINT(3 3) | +-------------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/st_endpoint/' + - name: ST_ENVELOPE + category_id: geometry_properties + category_label: Geometry Properties + tags: + - geometry_properties + aliases: [] + signature: + display: ST_ENVELOPE(g) + args: + - name: g + optional: false + type: any + summary: Returns the Minimum Bounding Rectangle (MBR) for the geometry value g. + description: 'Returns the Minimum Bounding Rectangle (MBR) for the geometry value + g. The result is returned as a Polygon value. The polygon is defined by the + corner points of the bounding box: POLYGON((MINX MINY, MAXX MINY, MAXX MAXY, + MINX MAXY, MINX MINY)) ST_ENVELOPE() and ENVELOPE() are synonyms.' + examples: + - sql: SELECT AsText(ST_ENVELOPE(GeomFromText('LineString(1 1,4 4)'))); + result: +----------------------------------------------------------+ | AsText(ST_ENVELOPE(GeomFromText('LineString(1 + 1,4 4)'))) | +----------------------------------------------------------+ + | POLYGON((1 1,4 1,4 4,1 4,1 1)) | +----------------------------------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/st_envelope/' + - name: ST_EQUALS + category_id: geometry_relations + category_label: Geometry Relations + tags: + - geometry_relations + aliases: [] + signature: + display: ST_EQUALS(g1,g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + summary: Returns 1 or 0 to indicate whether geometry g1 is spatially equal to + geometry + description: Returns 1 or 0 to indicate whether geometry g1 is spatially equal + to geometry g2. ST_EQUALS() uses object shapes, while EQUALS(), based on the + original MySQL implementation, uses object bounding rectangles. + examples: + - sql: SET @g1 = ST_GEOMFROMTEXT('LINESTRING(174 149, 176 151)'); + result: SET @g2 = ST_GEOMFROMTEXT('LINESTRING(176 151, 174 149)'); + - sql: SELECT ST_EQUALS(@g1,@g2); + result: +--------------------+ | ST_EQUALS(@g1,@g2) | +--------------------+ + | 1 | +--------------------+ + - sql: SET @g1 = ST_GEOMFROMTEXT('POINT(0 2)'); + result: SET @g1 = ST_GEOMFROMTEXT('POINT(2 0)'); + - sql: SELECT ST_EQUALS(@g1,@g2); + result: +--------------------+ | ST_EQUALS(@g1,@g2) | +--------------------+ + | 0 | +--------------------+ + - sql: 'URL: https://mariadb.com/kb/en/st-equals/' + - name: ST_ExteriorRing + category_id: polygon_properties + category_label: Polygon Properties + tags: + - polygon_properties + aliases: [] + signature: + display: ST_ExteriorRing(poly) + args: + - name: poly + optional: false + type: any + summary: Returns the exterior ring of the Polygon value poly as a LineString. + description: Returns the exterior ring of the Polygon value poly as a LineString. + ST_ExteriorRing() and ExteriorRing() are synonyms. + examples: + - sql: SET @poly = 'Polygon((0 0,0 3,3 3,3 0,0 0),(1 1,1 2,2 2,2 1,1 1))'; + result: SELECT AsText(ExteriorRing(GeomFromText(@poly))); +-------------------------------------------+ + | AsText(ExteriorRing(GeomFromText(@poly))) | +-------------------------------------------+ + | LINESTRING(0 0,0 3,3 3,3 0,0 0) | +-------------------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/st_exteriorring/' + - name: ST_GEOMETRYN + category_id: geometry_properties + category_label: Geometry Properties + tags: + - geometry_properties + aliases: [] + signature: + display: ST_GEOMETRYN(gc,N) + args: + - name: gc + optional: false + type: any + - name: N + optional: false + type: any + summary: Returns the N-th geometry in the GeometryCollection gc. + description: 'Returns the N-th geometry in the GeometryCollection gc. Geometries + are numbered beginning with 1. ST_GeometryN() and GeometryN() are synonyms. + Example ------- SET @gc = ''GeometryCollection(Point(1 1),LineString(12 14, + 9 11))''; SELECT AsText(GeometryN(GeomFromText(@gc),1)); +----------------------------------------+ + | AsText(GeometryN(GeomFromText(@gc),1)) | +----------------------------------------+ + | POINT(1 1) | +----------------------------------------+ + URL: https://mariadb.com/kb/en/st_geometryn/' + examples: [] + - name: ST_GEOMETRYTYPE + category_id: geometry_properties + category_label: Geometry Properties + tags: + - geometry_properties + aliases: [] + signature: + display: ST_GEOMETRYTYPE(g) + args: + - name: g + optional: false + type: any + summary: Returns as a string the name of the geometry type of which the geometry + description: Returns as a string the name of the geometry type of which the geometry + instance g is a member. The name corresponds to one of the instantiable Geometry + subclasses. ST_GeometryType() and GeometryType() are synonyms. + examples: + - sql: SELECT GeometryType(GeomFromText('POINT(1 1)')); + result: +------------------------------------------+ | GeometryType(GeomFromText('POINT(1 + 1)')) | +------------------------------------------+ | POINT | + +------------------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/st_geometrytype/' + - name: ST_GeomCollFromText + category_id: wkt + category_label: WKT + tags: + - wkt + aliases: [] + signature: + display: ST_GeomCollFromText(wkt[,srid]) + args: + - name: wkt[ + optional: false + type: any + - name: srid] + optional: false + type: any + summary: Constructs a GEOMETRYCOLLECTION value using its WKT representation and + SRID. + description: "Constructs a GEOMETRYCOLLECTION value using its WKT representation\ + \ and SRID.\n\nST_GeomCollFromText(), ST_GeometryCollectionFromText(), GeomCollFromText()\ + \ and\nGeometryCollectionFromText() are all synonyms.\n\nExample\n-------\n\n\ + CREATE TABLE gis_geometrycollection (g GEOMETRYCOLLECTION);\nSHOW FIELDS FROM\ + \ gis_geometrycollection;\nINSERT INTO gis_geometrycollection VALUES\n (GeomCollFromText('GEOMETRYCOLLECTION(POINT(0\ + \ 0), LINESTRING(0 0,10\n10))')),\n (GeometryFromWKB(AsWKB(GeometryCollection(Point(44,\ + \ 6),\nLineString(Point(3, 6), Point(7, 9)))))),\n (GeomFromText('GeometryCollection()')),\n\ + \ (GeomFromText('GeometryCollection EMPTY'));\n\nURL: https://mariadb.com/kb/en/st_geomcollfromtext/" + examples: [] + - name: ST_GeomCollFromWKB + category_id: wkb + category_label: WKB + tags: + - wkb + aliases: [] + signature: + display: ST_GeomCollFromWKB(wkb[,srid]) + args: + - name: wkb[ + optional: false + type: any + - name: srid] + optional: false + type: any + summary: Constructs a GEOMETRYCOLLECTION value using its WKB representation and + SRID. + description: Constructs a GEOMETRYCOLLECTION value using its WKB representation + and SRID. ST_GeomCollFromWKB(), ST_GeometryCollectionFromWKB(), GeomCollFromWKB() + and GeometryCollectionFromWKB() are synonyms. + examples: + - sql: "SET @g = ST_AsBinary(ST_GeomFromText('GEOMETRYCOLLECTION(\n POLYGON((5\ + \ 5,10 5,10 10,5 5)),POINT(10 10))'));" + result: SELECT ST_AsText(ST_GeomCollFromWKB(@g)); +----------------------------------------------------------------+ + | ST_AsText(ST_GeomCollFromWKB(@g)) | +----------------------------------------------------------------+ + | GEOMETRYCOLLECTION(POLYGON((5 5,10 5,10 10,5 5)),POINT(10 10)) | +----------------------------------------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/st_geomcollfromwkb/' + - name: ST_GeomFromGeoJSON + category_id: geojson + category_label: GeoJSON + tags: + - geojson + aliases: [] + signature: + display: ST_GeomFromGeoJSON(g[, option]) + args: + - name: g[ + optional: false + type: any + - name: option] + optional: false + type: any + summary: Given a GeoJSON input g, returns a geometry object. + description: Given a GeoJSON input g, returns a geometry object. The option specifies + what to do if g contains geometries with coordinate dimensions higher than 2. + +---------------------------+------------------------------------------------+ + | Option | Description | + +---------------------------+------------------------------------------------+ + | 1 | Return an error (the default) | + +---------------------------+------------------------------------------------+ + | 2 - 4 | The document is accepted, but the coordinates | + | | for higher coordinate dimensions are stripped | + | | off. | + +---------------------------+------------------------------------------------+ + Note that this function did not work correctly before MariaDB 10.2.8 - see MDEV-12180. + examples: + - sql: 'SET @j = ''{ "type": "Point", "coordinates": [5.3, 15.0]}'';' + result: SELECT ST_AsText(ST_GeomFromGeoJSON(@j)); +-----------------------------------+ + | ST_AsText(ST_GeomFromGeoJSON(@j)) | +-----------------------------------+ + | POINT(5.3 15) | +-----------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/st_geomfromgeojson/' + - name: ST_GeomFromText + category_id: wkt + category_label: WKT + tags: + - wkt + aliases: [] + signature: + display: ST_GeomFromText(wkt[,srid]) + args: + - name: wkt[ + optional: false + type: any + - name: srid] + optional: false + type: any + summary: Constructs a geometry value of any type using its WKT representation + and SRID. + description: 'Constructs a geometry value of any type using its WKT representation + and SRID. GeomFromText(), GeometryFromText(), ST_GeomFromText() and ST_GeometryFromText() + are all synonyms. Example ------- SET @g = ST_GEOMFROMTEXT(''POLYGON((1 1,1 + 5,4 9,6 9,9 3,7 2,1 1))''); URL: https://mariadb.com/kb/en/st_geomfromtext/' + examples: [] + - name: ST_GeomFromWKB + category_id: wkb + category_label: WKB + tags: + - wkb + aliases: [] + signature: + display: ST_GeomFromWKB(wkb[,srid]) + args: + - name: wkb[ + optional: false + type: any + - name: srid] + optional: false + type: any + summary: Constructs a geometry value of any type using its WKB representation + and SRID. + description: Constructs a geometry value of any type using its WKB representation + and SRID. ST_GeomFromWKB(), ST_GeometryFromWKB(), GeomFromWKB() and GeometryFromWKB() + are synonyms. + examples: + - sql: SET @g = ST_AsBinary(ST_LineFromText('LINESTRING(0 4, 4 6)')); + result: SELECT ST_AsText(ST_GeomFromWKB(@g)); +-------------------------------+ + | ST_AsText(ST_GeomFromWKB(@g)) | +-------------------------------+ | LINESTRING(0 + 4,4 6) | +-------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/st_geomfromwkb/' + - name: ST_INTERSECTION + category_id: geometry_constructors + category_label: Geometry Constructors + tags: + - geometry_constructors + aliases: [] + signature: + display: ST_INTERSECTION(g1,g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + summary: Returns a geometry that is the intersection, or shared portion, of geometry + g1 + description: Returns a geometry that is the intersection, or shared portion, of + geometry g1 and geometry g2. + examples: + - sql: SET @g1 = ST_GEOMFROMTEXT('POINT(2 1)'); + result: SET @g2 = ST_GEOMFROMTEXT('LINESTRING(2 1, 0 2)'); + - sql: SELECT ASTEXT(ST_INTERSECTION(@g1,@g2)); + result: +----------------------------------+ | ASTEXT(ST_INTERSECTION(@g1,@g2)) + | +----------------------------------+ | POINT(2 1) | + +----------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/st_intersection/' + - name: ST_INTERSECTS + category_id: geometry_relations + category_label: Geometry Relations + tags: + - geometry_relations + aliases: [] + signature: + display: ST_INTERSECTS(g1,g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + summary: Returns 1 or 0 to indicate whether geometry g1 spatially intersects geometry + description: Returns 1 or 0 to indicate whether geometry g1 spatially intersects + geometry g2. ST_INTERSECTS() uses object shapes, while INTERSECTS(), based on + the original MySQL implementation, uses object bounding rectangles. ST_INTERSECTS() + tests the opposite relationship to ST_DISJOINT(). + examples: + - sql: SET @g1 = ST_GEOMFROMTEXT('POINT(0 0)'); + result: SET @g2 = ST_GEOMFROMTEXT('LINESTRING(0 0, 0 2)'); + - sql: SELECT ST_INTERSECTS(@g1,@g2); + result: +------------------------+ | ST_INTERSECTS(@g1,@g2) | +------------------------+ + | 1 | +------------------------+ + - sql: SET @g2 = ST_GEOMFROMTEXT('LINESTRING(2 0, 0 2)'); + result: SELECT ST_INTERSECTS(@g1,@g2); +------------------------+ | ST_INTERSECTS(@g1,@g2) + | +------------------------+ | 0 | +------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/st-intersects/' + - name: ST_ISCLOSED + category_id: geometry_properties + category_label: Geometry Properties + tags: + - geometry_properties + aliases: [] + signature: + display: ST_ISCLOSED(g) + args: + - name: g + optional: false + type: any + summary: Returns 1 if a given LINESTRING's start and end points are the same, + or 0 if + description: Returns 1 if a given LINESTRING's start and end points are the same, + or 0 if they are not the same. Before MariaDB 10.1.5, returns NULL if not given + a LINESTRING. After MariaDB 10.1.5, returns -1. ST_IsClosed() and IsClosed() + are synonyms. + examples: + - sql: SET @ls = 'LineString(0 0, 0 4, 4 4, 0 0)'; SELECT ST_ISCLOSED(GEOMFROMTEXT(@ls)); + result: +--------------------------------+ | ST_ISCLOSED(GEOMFROMTEXT(@ls)) + | +--------------------------------+ | 1 | +--------------------------------+ + - sql: SET @ls = 'LineString(0 0, 0 4, 4 4, 0 1)'; SELECT ST_ISCLOSED(GEOMFROMTEXT(@ls)); + result: +--------------------------------+ | ST_ISCLOSED(GEOMFROMTEXT(@ls)) + | +--------------------------------+ | 0 | +--------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/st_isclosed/' + - name: ST_ISEMPTY + category_id: geometry_properties + category_label: Geometry Properties + tags: + - geometry_properties + aliases: [] + signature: + display: ST_ISEMPTY(g) + args: + - name: g + optional: false + type: any + summary: IsEmpty is a function defined by the OpenGIS specification, but is not + fully + description: 'IsEmpty is a function defined by the OpenGIS specification, but + is not fully implemented by MariaDB or MySQL. Since MariaDB and MySQL do not + support GIS EMPTY values such as POINT EMPTY, as implemented it simply returns + 1 if the geometry value g is invalid, 0 if it is valid, and NULL if the argument + is NULL. ST_IsEmpty() and IsEmpty() are synonyms. URL: https://mariadb.com/kb/en/st_isempty/' + examples: [] + - name: ST_InteriorRingN + category_id: polygon_properties + category_label: Polygon Properties + tags: + - polygon_properties + aliases: [] + signature: + display: ST_InteriorRingN(poly,N) + args: + - name: poly + optional: false + type: any + - name: N + optional: false + type: any + summary: Returns the N-th interior ring for the Polygon value poly as a LineString. + description: Returns the N-th interior ring for the Polygon value poly as a LineString. + Rings are numbered beginning with 1. ST_InteriorRingN() and InteriorRingN() + are synonyms. + examples: + - sql: SET @poly = 'Polygon((0 0,0 3,3 3,3 0,0 0),(1 1,1 2,2 2,2 1,1 1))'; + result: SELECT AsText(InteriorRingN(GeomFromText(@poly),1)); +----------------------------------------------+ + | AsText(InteriorRingN(GeomFromText(@poly),1)) | +----------------------------------------------+ + | LINESTRING(1 1,1 2,2 2,2 1,1 1) | +----------------------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/st_interiorringn/' + - name: ST_IsRing + category_id: geometry_properties + category_label: Geometry Properties + tags: + - geometry_properties + aliases: [] + signature: + display: ST_IsRing(g) + args: + - name: g + optional: false + type: any + summary: Returns true if a given LINESTRING is a ring, that is, both ST_IsClosed + and + description: 'Returns true if a given LINESTRING is a ring, that is, both ST_IsClosed + and ST_IsSimple. A simple curve does not pass through the same point more than + once. However, see MDEV-7510. St_IsRing() and IsRing() are synonyms. URL: https://mariadb.com/kb/en/st_isring/' + examples: [] + - name: ST_IsSimple + category_id: geometry_properties + category_label: Geometry Properties + tags: + - geometry_properties + aliases: [] + signature: + display: ST_IsSimple(g) + args: + - name: g + optional: false + type: any + summary: Returns true if the given Geometry has no anomalous geometric points, + false if + description: Returns true if the given Geometry has no anomalous geometric points, + false if it does, or NULL if given a NULL value. ST_IsSimple() and IsSimple() + are synonyms. + examples: + - sql: A POINT is always simple. + result: SET @g = 'Point(1 2)'; + - sql: SELECT ST_ISSIMPLE(GEOMFROMTEXT(@g)); + result: +-------------------------------+ | ST_ISSIMPLE(GEOMFROMTEXT(@g)) | + +-------------------------------+ | 1 | +-------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/st_issimple/' + - name: ST_LENGTH + category_id: geometry_relations + category_label: Geometry Relations + tags: + - geometry_relations + aliases: [] + signature: + display: ST_LENGTH(ls) + args: + - name: ls + optional: false + type: any + summary: Returns as a double-precision number the length of the LineString value + ls in + description: Returns as a double-precision number the length of the LineString + value ls in its associated spatial reference. + examples: + - sql: SET @ls = 'LineString(1 1,2 2,3 3)'; + result: SELECT ST_LENGTH(ST_GeomFromText(@ls)); +---------------------------------+ + | ST_LENGTH(ST_GeomFromText(@ls)) | +---------------------------------+ | 2.82842712474619 + | +---------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/st_length/' + - name: ST_LineFromText + category_id: wkt + category_label: WKT + tags: + - wkt + aliases: [] + signature: + display: ST_LineFromText(wkt[,srid]) + args: + - name: wkt[ + optional: false + type: any + - name: srid] + optional: false + type: any + summary: Constructs a LINESTRING value using its WKT representation and SRID. + description: Constructs a LINESTRING value using its WKT representation and SRID. + ST_LineFromText(), ST_LineStringFromText(), ST_LineFromText() and ST_LineStringFromText() + are all synonyms. + examples: + - sql: "CREATE TABLE gis_line (g LINESTRING);\nSHOW FIELDS FROM gis_line;\nINSERT\ + \ INTO gis_line VALUES\n (LineFromText('LINESTRING(0 0,0 10,10 0)')),\n \ + \ (LineStringFromText('LINESTRING(10 10,20 10,20 20,10 20,10 10)')),\n (LineStringFromWKB(AsWKB(LineString(Point(10,\ + \ 10), Point(40, 10)))));" + result: 'URL: https://mariadb.com/kb/en/st_linefromtext/' + - name: ST_LineFromWKB + category_id: wkb + category_label: WKB + tags: + - wkb + aliases: [] + signature: + display: ST_LineFromWKB(wkb[,srid]) + args: + - name: wkb[ + optional: false + type: any + - name: srid] + optional: false + type: any + summary: Constructs a LINESTRING value using its WKB representation and SRID. + description: Constructs a LINESTRING value using its WKB representation and SRID. + ST_LineFromWKB(), LineFromWKB(), ST_LineStringFromWKB(), and LineStringFromWKB() + are synonyms. + examples: + - sql: SET @g = ST_AsBinary(ST_LineFromText('LineString(0 4,4 6)')); + result: SELECT ST_AsText(ST_LineFromWKB(@g)) AS l; +---------------------+ | + l | +---------------------+ | LINESTRING(0 4,4 6) | +---------------------+ + - sql: 'URL: https://mariadb.com/kb/en/st_linefromwkb/' + - name: ST_NUMGEOMETRIES + category_id: geometry_properties + category_label: Geometry Properties + tags: + - geometry_properties + aliases: [] + signature: + display: ST_NUMGEOMETRIES(gc) + args: + - name: gc + optional: false + type: any + summary: Returns the number of geometries in the GeometryCollection gc. + description: 'Returns the number of geometries in the GeometryCollection gc. ST_NumGeometries() + and NumGeometries() are synonyms. Example ------- SET @gc = ''GeometryCollection(Point(1 + 1),LineString(2 2, 3 3))''; SELECT NUMGEOMETRIES(GeomFromText(@gc)); +----------------------------------+ + | NUMGEOMETRIES(GeomFromText(@gc)) | +----------------------------------+ | 2 + | +----------------------------------+ URL: https://mariadb.com/kb/en/st_numgeometries/' + examples: [] + - name: ST_NUMPOINTS + category_id: linestring_properties + category_label: LineString Properties + tags: + - linestring_properties + aliases: [] + signature: + display: ST_NUMPOINTS(ls) + args: + - name: ls + optional: false + type: any + summary: Returns the number of Point objects in the LineString value ls. + description: Returns the number of Point objects in the LineString value ls. ST_NumPoints() + and NumPoints() are synonyms. + examples: + - sql: SET @ls = 'LineString(1 1,2 2,3 3)'; + result: SELECT NumPoints(GeomFromText(@ls)); +------------------------------+ + | NumPoints(GeomFromText(@ls)) | +------------------------------+ | 3 + | +------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/st_numpoints/' + - name: ST_NumInteriorRings + category_id: polygon_properties + category_label: Polygon Properties + tags: + - polygon_properties + aliases: [] + signature: + display: ST_NumInteriorRings(poly) + args: + - name: poly + optional: false + type: any + summary: Returns an integer containing the number of interior rings in the Polygon + description: Returns an integer containing the number of interior rings in the + Polygon value poly. Note that according the the OpenGIS standard, a POLYGON + should have exactly one ExteriorRing and all other rings should lie within that + ExteriorRing and thus be the InteriorRings. Practically, however, some systems, + including MariaDB's, permit polygons to have several 'ExteriorRings'. In the + case of there being multiple, non-overlapping exterior rings ST_NumInteriorRings() + will return 1. ST_NumInteriorRings() and NumInteriorRings() are synonyms. + examples: + - sql: SET @poly = 'Polygon((0 0,0 3,3 3,3 0,0 0),(1 1,1 2,2 2,2 1,1 1))'; + result: SELECT NumInteriorRings(GeomFromText(@poly)); +---------------------------------------+ + | NumInteriorRings(GeomFromText(@poly)) | +---------------------------------------+ + | 1 | +---------------------------------------+ + - sql: 'Non-overlapping ''polygon'':' + result: SELECT ST_NumInteriorRings(ST_PolyFromText('POLYGON((0 0,10 0,10 10,0 + 10,0 0), + - sql: +------------------+ + result: '| NumInteriorRings | +------------------+ | 1 | +------------------+' + - sql: 'URL: https://mariadb.com/kb/en/st_numinteriorrings/' + - name: ST_OVERLAPS + category_id: geometry_relations + category_label: Geometry Relations + tags: + - geometry_relations + aliases: [] + signature: + display: ST_OVERLAPS(g1,g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + summary: Returns 1 or 0 to indicate whether geometry g1 spatially overlaps geometry + g2. + description: 'Returns 1 or 0 to indicate whether geometry g1 spatially overlaps + geometry g2. The term spatially overlaps is used if two geometries intersect + and their intersection results in a geometry of the same dimension but not equal + to either of the given geometries. ST_OVERLAPS() uses object shapes, while OVERLAPS(), + based on the original MySQL implementation, uses object bounding rectangles. + URL: https://mariadb.com/kb/en/st-overlaps/' + examples: [] + - name: ST_POINTN + category_id: linestring_properties + category_label: LineString Properties + tags: + - linestring_properties + aliases: [] + signature: + display: ST_POINTN(ls,N) + args: + - name: ls + optional: false + type: any + - name: N + optional: false + type: any + summary: Returns the N-th Point in the LineString value ls. + description: Returns the N-th Point in the LineString value ls. Points are numbered + beginning with 1. ST_PointN() and PointN() are synonyms. + examples: + - sql: SET @ls = 'LineString(1 1,2 2,3 3)'; + result: SELECT AsText(PointN(GeomFromText(@ls),2)); +-------------------------------------+ + | AsText(PointN(GeomFromText(@ls),2)) | +-------------------------------------+ + | POINT(2 2) | +-------------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/st_pointn/' + - name: ST_POINTONSURFACE + category_id: geometry_constructors + category_label: Geometry Constructors + tags: + - geometry_constructors + aliases: [] + signature: + display: ST_POINTONSURFACE + args: [] + summary: Given a geometry, returns a POINT guaranteed to intersect a surface. + description: 'Given a geometry, returns a POINT guaranteed to intersect a surface. + However, see MDEV-7514. ST_PointOnSurface() and PointOnSurface() are synonyms. + URL: https://mariadb.com/kb/en/st_pointonsurface/' + examples: [] + - name: ST_PointFromText + category_id: wkt + category_label: WKT + tags: + - wkt + aliases: [] + signature: + display: ST_PointFromText(wkt[,srid]) + args: + - name: wkt[ + optional: false + type: any + - name: srid] + optional: false + type: any + summary: Constructs a POINT value using its WKT representation and SRID. + description: Constructs a POINT value using its WKT representation and SRID. ST_PointFromText() + and PointFromText() are synonyms. + examples: + - sql: "CREATE TABLE gis_point (g POINT);\nSHOW FIELDS FROM gis_point;\nINSERT\ + \ INTO gis_point VALUES\n (PointFromText('POINT(10 10)')),\n (PointFromText('POINT(20\ + \ 10)')),\n (PointFromText('POINT(20 20)')),\n (PointFromWKB(AsWKB(PointFromText('POINT(10\ + \ 20)'))));" + result: 'URL: https://mariadb.com/kb/en/st_pointfromtext/' + - name: ST_PointFromWKB + category_id: wkb + category_label: WKB + tags: + - wkb + aliases: [] + signature: + display: ST_PointFromWKB(wkb[,srid]) + args: + - name: wkb[ + optional: false + type: any + - name: srid] + optional: false + type: any + summary: Constructs a POINT value using its WKB representation and SRID. + description: Constructs a POINT value using its WKB representation and SRID. ST_PointFromWKB() + and PointFromWKB() are synonyms. + examples: + - sql: SET @g = ST_AsBinary(ST_PointFromText('POINT(0 4)')); + result: SELECT ST_AsText(ST_PointFromWKB(@g)) AS p; +------------+ | p | + +------------+ | POINT(0 4) | +------------+ + - sql: 'URL: https://mariadb.com/kb/en/st_pointfromwkb/' + - name: ST_PolyFromText + category_id: wkt + category_label: WKT + tags: + - wkt + aliases: [] + signature: + display: ST_PolyFromText(wkt[,srid]) + args: + - name: wkt[ + optional: false + type: any + - name: srid] + optional: false + type: any + summary: Constructs a POLYGON value using its WKT representation and SRID. + description: Constructs a POLYGON value using its WKT representation and SRID. + ST_PolyFromText(), ST_PolygonFromText(), PolyFromText() and ST_PolygonFromText() + are all synonyms. + examples: + - sql: "CREATE TABLE gis_polygon (g POLYGON);\nINSERT INTO gis_polygon VALUES\n\ + \ (PolygonFromText('POLYGON((10 10,20 10,20 20,10 20,10 10))')),\n (PolyFromText('POLYGON((0\ + \ 0,50 0,50 50,0 50,0 0), (10 10,20 10,20 20,10\n20,10 10))'));" + result: 'URL: https://mariadb.com/kb/en/st_polyfromtext/' + - name: ST_PolyFromWKB + category_id: wkb + category_label: WKB + tags: + - wkb + aliases: [] + signature: + display: ST_PolyFromWKB(wkb[,srid]) + args: + - name: wkb[ + optional: false + type: any + - name: srid] + optional: false + type: any + summary: Constructs a POLYGON value using its WKB representation and SRID. + description: Constructs a POLYGON value using its WKB representation and SRID. + ST_PolyFromWKB(), ST_PolygonFromWKB(), PolyFromWKB() and PolygonFromWKB() are + synonyms. + examples: + - sql: SET @g = ST_AsBinary(ST_PolyFromText('POLYGON((1 1,1 5,4 9,6 9,9 3,7 2,1 + 1))')); + result: SELECT ST_AsText(ST_PolyFromWKB(@g)) AS p; +----------------------------------------+ + | p | +----------------------------------------+ + | POLYGON((1 1,1 5,4 9,6 9,9 3,7 2,1 1)) | +----------------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/st_polyfromwkb/' + - name: ST_RELATE + category_id: geometry_properties + category_label: Geometry Properties + tags: + - geometry_properties + aliases: [] + signature: + display: ST_RELATE + args: [] + summary: Returns true if Geometry g1 is spatially related to Geometryg2 by testing + for + description: 'Returns true if Geometry g1 is spatially related to Geometryg2 by + testing for intersections between the interior, boundary and exterior of the + two geometries as specified by the values in intersection matrix pattern i. + URL: https://mariadb.com/kb/en/st_relate/' + examples: [] + - name: ST_SRID + category_id: geometry_properties + category_label: Geometry Properties + tags: + - geometry_properties + aliases: [] + signature: + display: ST_SRID(g) + args: + - name: g + optional: false + type: any + summary: Returns an integer indicating the Spatial Reference System ID for the + geometry + description: Returns an integer indicating the Spatial Reference System ID for + the geometry value g. In MariaDB, the SRID value is just an integer associated + with the geometry value. All calculations are done assuming Euclidean (planar) + geometry. ST_SRID() and SRID() are synonyms. + examples: + - sql: SELECT SRID(GeomFromText('LineString(1 1,2 2)',101)); + result: +-----------------------------------------------+ | SRID(GeomFromText('LineString(1 + 1,2 2)',101)) | +-----------------------------------------------+ | 101 + | +-----------------------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/st_srid/' + - name: ST_STARTPOINT + category_id: linestring_properties + category_label: LineString Properties + tags: + - linestring_properties + aliases: [] + signature: + display: ST_STARTPOINT(ls) + args: + - name: ls + optional: false + type: any + summary: Returns the Point that is the start point of the LineString value ls. + description: Returns the Point that is the start point of the LineString value + ls. ST_StartPoint() and StartPoint() are synonyms. + examples: + - sql: SET @ls = 'LineString(1 1,2 2,3 3)'; + result: SELECT AsText(StartPoint(GeomFromText(@ls))); +---------------------------------------+ + | AsText(StartPoint(GeomFromText(@ls))) | +---------------------------------------+ + | POINT(1 1) | +---------------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/st_startpoint/' + - name: ST_SYMDIFFERENCE + category_id: geometry_constructors + category_label: Geometry Constructors + tags: + - geometry_constructors + aliases: [] + signature: + display: ST_SYMDIFFERENCE(g1,g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + summary: Returns a geometry that represents the portions of geometry g1 and geometry + g2 + description: Returns a geometry that represents the portions of geometry g1 and + geometry g2 that don't intersect. + examples: + - sql: SET @g1 = ST_GEOMFROMTEXT('LINESTRING(10 20, 10 40)'); + result: SET @g2 = ST_GEOMFROMTEXT('LINESTRING(10 15, 10 25)'); + - sql: SELECT ASTEXT(ST_SYMDIFFERENCE(@g1,@g2)); + result: +----------------------------------------------+ | ASTEXT(ST_SYMDIFFERENCE(@g1,@g2)) | + +----------------------------------------------+ | MULTILINESTRING((10 15,10 + 20),(10 25,10 40)) | +----------------------------------------------+ + - sql: SET @g2 = ST_GeomFromText('LINESTRING(10 20, 10 41)'); + result: SELECT ASTEXT(ST_SYMDIFFERENCE(@g1,@g2)); +-----------------------------------+ + | ASTEXT(ST_SYMDIFFERENCE(@g1,@g2)) | +-----------------------------------+ + | LINESTRING(10 40,10 41) | +-----------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/st_symdifference/' + - name: ST_TOUCHES + category_id: geometry_relations + category_label: Geometry Relations + tags: + - geometry_relations + aliases: [] + signature: + display: ST_TOUCHES(g1,g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + summary: Returns 1 or 0 to indicate whether geometry g1 spatially touches geometry + g2. + description: Returns 1 or 0 to indicate whether geometry g1 spatially touches + geometry g2. Two geometries spatially touch if the interiors of the geometries + do not intersect, but the boundary of one of the geometries intersects either + the boundary or the interior of the other. ST_TOUCHES() uses object shapes, + while TOUCHES(), based on the original MySQL implementation, uses object bounding + rectangles. + examples: + - sql: SET @g1 = ST_GEOMFROMTEXT('POINT(2 0)'); + result: SET @g2 = ST_GEOMFROMTEXT('LINESTRING(2 0, 0 2)'); + - sql: SELECT ST_TOUCHES(@g1,@g2); + result: +---------------------+ | ST_TOUCHES(@g1,@g2) | +---------------------+ + | 1 | +---------------------+ + - sql: SET @g1 = ST_GEOMFROMTEXT('POINT(2 1)'); + result: SELECT ST_TOUCHES(@g1,@g2); +---------------------+ | ST_TOUCHES(@g1,@g2) + | +---------------------+ | 0 | +---------------------+ + - sql: 'URL: https://mariadb.com/kb/en/st-touches/' + - name: ST_UNION + category_id: geometry_constructors + category_label: Geometry Constructors + tags: + - geometry_constructors + aliases: [] + signature: + display: ST_UNION(g1,g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + summary: Returns a geometry that is the union of the geometry g1 and geometry + g2. + description: Returns a geometry that is the union of the geometry g1 and geometry + g2. + examples: + - sql: SET @g1 = GEOMFROMTEXT('POINT (0 2)'); + result: SET @g2 = GEOMFROMTEXT('POINT (2 0)'); + - sql: SELECT ASTEXT(ST_UNION(@g1,@g2)); + result: +---------------------------+ | ASTEXT(ST_UNION(@g1,@g2)) | +---------------------------+ + | MULTIPOINT(2 0,0 2) | +---------------------------+ + - sql: SET @g1 = GEOMFROMTEXT('POLYGON((0 0,0 3,3 3,3 0,0 0))'); + result: SET @g2 = GEOMFROMTEXT('POLYGON((2 2,4 2,4 4,2 4,2 2))'); + - sql: SELECT ASTEXT(ST_UNION(@g1,@g2)); + result: +------------------------------------------------+ | ASTEXT(ST_UNION(@g1,@g2)) | + +------------------------------------------------+ | POLYGON((0 0,0 3,2 3,2 + 4,4 4,4 2,3 2,3 0,0 0)) | +------------------------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/st_union/' + - name: ST_WITHIN + category_id: geometry_relations + category_label: Geometry Relations + tags: + - geometry_relations + aliases: [] + signature: + display: ST_WITHIN(g1,g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + summary: Returns 1 or 0 to indicate whether geometry g1 is spatially within geometry + g2. + description: Returns 1 or 0 to indicate whether geometry g1 is spatially within + geometry g2. This tests the opposite relationship as ST_CONTAINS(). ST_WITHIN() + uses object shapes, while WITHIN(), based on the original MySQL implementation, + uses object bounding rectangles. + examples: + - sql: SET @g1 = ST_GEOMFROMTEXT('POINT(174 149)'); + result: SET @g2 = ST_GEOMFROMTEXT('POLYGON((175 150, 20 40, 50 60, 125 100, + 175 + - sql: SELECT ST_WITHIN(@g1,@g2); + result: +--------------------+ | ST_WITHIN(@g1,@g2) | +--------------------+ + | 1 | +--------------------+ + - sql: SET @g1 = ST_GEOMFROMTEXT('POINT(176 151)'); + result: SELECT ST_WITHIN(@g1,@g2); +--------------------+ | ST_WITHIN(@g1,@g2) + | +--------------------+ | 0 | +--------------------+ + - sql: 'URL: https://mariadb.com/kb/en/st-within/' + - name: ST_X + category_id: point_properties + category_label: Point Properties + tags: + - point_properties + aliases: [] + signature: + display: ST_X(p) + args: + - name: p + optional: false + type: any + summary: Returns the X-coordinate value for the point p as a double-precision + number. + description: Returns the X-coordinate value for the point p as a double-precision + number. ST_X() and X() are synonyms. + examples: + - sql: SET @pt = 'Point(56.7 53.34)'; + result: SELECT X(GeomFromText(@pt)); +----------------------+ | X(GeomFromText(@pt)) + | +----------------------+ | 56.7 | +----------------------+ + - sql: 'URL: https://mariadb.com/kb/en/st_x/' + - name: ST_Y + category_id: point_properties + category_label: Point Properties + tags: + - point_properties + aliases: [] + signature: + display: ST_Y(p) + args: + - name: p + optional: false + type: any + summary: Returns the Y-coordinate value for the point p as a double-precision + number. + description: Returns the Y-coordinate value for the point p as a double-precision + number. ST_Y() and Y() are synonyms. + examples: + - sql: SET @pt = 'Point(56.7 53.34)'; + result: SELECT Y(GeomFromText(@pt)); +----------------------+ | Y(GeomFromText(@pt)) + | +----------------------+ | 53.34 | +----------------------+ + - sql: 'URL: https://mariadb.com/kb/en/st_y/' + - name: SUBDATE + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: SUBDATE(date,INTERVAL expr unit) + args: + - name: date + optional: false + type: any + - name: INTERVAL expr unit + optional: false + type: any + summary: When invoked with the INTERVAL form of the second argument, SUBDATE() + is a + description: When invoked with the INTERVAL form of the second argument, SUBDATE() + is a synonym for DATE_SUB(). See Date and Time Units for a complete list of + permitted units. The second form allows the use of an integer value for days. + In such cases, it is interpreted as the number of days to be subtracted from + the date or datetime expression expr. + examples: + - sql: SELECT DATE_SUB('2008-01-02', INTERVAL 31 DAY); + result: +-----------------------------------------+ | DATE_SUB('2008-01-02', + INTERVAL 31 DAY) | +-----------------------------------------+ | 2007-12-02 | + +-----------------------------------------+ + - sql: SELECT SUBDATE('2008-01-02', INTERVAL 31 DAY); + result: +----------------------------------------+ | SUBDATE('2008-01-02', INTERVAL + 31 DAY) | +----------------------------------------+ | 2007-12-02 | + +----------------------------------------+ + - sql: SELECT SUBDATE('2008-01-02 12:00:00', 31); + result: +------------------------------------+ | SUBDATE('2008-01-02 12:00:00', + 31) | +------------------------------------+ | 2007-12-02 12:00:00 | + +------------------------------------+ + - sql: "CREATE TABLE t1 (d DATETIME);\nINSERT INTO t1 VALUES\n (\"2007-01-30\ + \ 21:31:07\"),\n (\"1983-10-15 06:42:51\"),\n (\"2011-04-21 12:34:56\"),\n\ + \ (\"2011-10-30 06:31:41\"),\n (\"2011-01-30 14:03:25\"),\n (\"2004-10-07\ + \ 11:19:34\");" + result: SELECT d, SUBDATE(d, 10) from t1; +---------------------+---------------------+ + | d | SUBDATE(d, 10) | +---------------------+---------------------+ + | 2007-01-30 21:31:07 | 2007-01-20 21:31:07 | | 1983-10-15 06:42:51 | 1983-10-05 + 06:42:51 | | 2011-04-21 12:34:56 | 2011-04-11 12:34:56 | | 2011-10-30 06:31:41 + | 2011-10-20 06:31:41 | | 2011-01-30 14:03:25 | 2011-01-20 14:03:25 | + - name: SUBSTR + category_id: string + category_label: String Functions + tags: + - string + aliases: + - SUBSTRING + signature: + display: SUBSTR + args: [] + summary: 'URL: https://mariadb.' + description: 'URL: https://mariadb.com/kb/en/substr/' + examples: [] + - name: SUBSTRING + category_id: string + category_label: String Functions + tags: + - string + aliases: + - SUBSTR + signature: + display: SUBSTRING(str,pos) + args: + - name: str + optional: false + type: any + - name: pos + optional: false + type: any + summary: The forms without a len argument return a substring from string str starting + description: The forms without a len argument return a substring from string str + starting at position pos. The forms with a len argument return a substring len + characters long from string str, starting at position pos. The forms that use + FROM are standard SQL syntax. It is also possible to use a negative value for + pos. In this case, the beginning of the substring is pos characters from the + end of the string, rather than the beginning. A negative value may be used for + pos in any of the forms of this function. By default, the position of the first + character in the string from which the substring is to be extracted is reckoned + as 1. For Oracle-compatibility, from MariaDB 10.3.3, when sql_mode is set to + 'oracle', position zero is treated as position 1 (although the first character + is still reckoned as 1). If any argument is NULL, returns NULL. + examples: + - sql: SELECT SUBSTRING('Knowledgebase',5); + result: +------------------------------+ | SUBSTRING('Knowledgebase',5) | +------------------------------+ + | ledgebase | +------------------------------+ + - sql: SELECT SUBSTRING('MariaDB' FROM 6); + result: +-----------------------------+ | SUBSTRING('MariaDB' FROM 6) | +-----------------------------+ + | DB | +-----------------------------+ + - sql: SELECT SUBSTRING('Knowledgebase',3,7); + result: +--------------------------------+ | SUBSTRING('Knowledgebase',3,7) + | +--------------------------------+ | owledge | +--------------------------------+ + - sql: SELECT SUBSTRING('Knowledgebase', -4); + result: +--------------------------------+ | SUBSTRING('Knowledgebase', -4) + | +--------------------------------+ | base | +--------------------------------+ + - name: SUBSTRING_INDEX + category_id: string + category_label: String Functions + tags: + - string + aliases: [] + signature: + display: SUBSTRING_INDEX(str,delim,count) + args: + - name: str + optional: false + type: any + - name: delim + optional: false + type: any + - name: count + optional: false + type: any + summary: Returns the substring from string str before count occurrences of the + description: Returns the substring from string str before count occurrences of + the delimiter delim. If count is positive, everything to the left of the final + delimiter (counting from the left) is returned. If count is negative, everything + to the right of the final delimiter (counting from the right) is returned. SUBSTRING_INDEX() + performs a case-sensitive match when searching for delim. If any argument is + NULL, returns NULL. For example SUBSTRING_INDEX('www.mariadb.org', '.', 2) means + "Return all of the characters up to the 2nd occurrence of ." + examples: + - sql: SELECT SUBSTRING_INDEX('www.mariadb.org', '.', 2); + result: +--------------------------------------------+ | SUBSTRING_INDEX('www.mariadb.org', + '.', 2) | +--------------------------------------------+ | www.mariadb | + +--------------------------------------------+ + - sql: SELECT SUBSTRING_INDEX('www.mariadb.org', '.', -2); + result: +---------------------------------------------+ | SUBSTRING_INDEX('www.mariadb.org', + '.', -2) | +---------------------------------------------+ | mariadb.org | + +---------------------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/substring_index/' + - name: SUBTIME + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: SUBTIME(expr1,expr2) + args: + - name: expr1 + optional: false + type: any + - name: expr2 + optional: false + type: any + summary: SUBTIME() returns expr1 - expr2 expressed as a value in the same format + as + description: SUBTIME() returns expr1 - expr2 expressed as a value in the same + format as expr1. expr1 is a time or datetime expression, and expr2 is a time + expression. + examples: + - sql: SELECT SUBTIME('2007-12-31 23:59:59.999999','1 1:1:1.000002'); + result: +--------------------------------------------------------+ | SUBTIME('2007-12-31 + 23:59:59.999999','1 1:1:1.000002') | +--------------------------------------------------------+ + | 2007-12-30 22:58:58.999997 | +--------------------------------------------------------+ + - sql: SELECT SUBTIME('01:00:00.999999', '02:00:00.999998'); + result: +-----------------------------------------------+ | SUBTIME('01:00:00.999999', + '02:00:00.999998') | +-----------------------------------------------+ | -00:59:59.999999 | + +-----------------------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/subtime/' + - name: SUM + category_id: group_by + category_label: Functions and Modifiers for Use with GROUP BY + tags: + - group_by + aliases: [] + signature: + display: SUM([DISTINCT] expr) + args: + - name: '[DISTINCT] expr' + optional: false + type: any + summary: Returns the sum of expr. + description: Returns the sum of expr. If the return set has no rows, SUM() returns + NULL. The DISTINCT keyword can be used to sum only the distinct values of expr. + SUM() can be used as a window function, although not with the DISTINCT specifier. + examples: + - sql: CREATE TABLE sales (sales_value INT); INSERT INTO sales VALUES(10),(20),(20),(40); + result: SELECT SUM(sales_value) FROM sales; +------------------+ | SUM(sales_value) + | +------------------+ | 90 | +------------------+ + - sql: SELECT SUM(DISTINCT(sales_value)) FROM sales; + result: +----------------------------+ | SUM(DISTINCT(sales_value)) | +----------------------------+ + | 70 | +----------------------------+ + - sql: 'Commonly, SUM is used with a GROUP BY clause:' + result: CREATE TABLE sales (name CHAR(10), month CHAR(10), units INT); + - sql: "INSERT INTO sales VALUES\n ('Chun', 'Jan', 75), ('Chun', 'Feb', 73),\n\ + \ ('Esben', 'Jan', 43), ('Esben', 'Feb', 31),\n ('Kaolin', 'Jan', 56), ('Kaolin',\ + \ 'Feb', 88),\n ('Tatiana', 'Jan', 87), ('Tatiana', 'Feb', 83);" + result: SELECT name, SUM(units) FROM sales GROUP BY name; +---------+------------+ + | name | SUM(units) | +---------+------------+ | Chun | 148 | + | Esben | 74 | | Kaolin | 144 | | Tatiana | 170 | + +---------+------------+ + - sql: 'The GROUP BY clause is required when using an aggregate function along + with regular column data, otherwise the result will be a mismatch, as in the + following common type of mistake:' + result: '...' + - name: SYSDATE + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: + - NOW + signature: + display: SYSDATE([precision]) + args: + - name: precision + optional: true + type: any + summary: Returns the current date and time as a value in 'YYYY-MM-DD HH:MM:SS' + or + description: Returns the current date and time as a value in 'YYYY-MM-DD HH:MM:SS' + or YYYYMMDDHHMMSS.uuuuuu format, depending on whether the function is used in + a string or numeric context. The optional precision determines the microsecond + precision. See Microseconds in MariaDB. SYSDATE() returns the time at which + it executes. This differs from the behavior for NOW(), which returns a constant + time that indicates the time at which the statement began to execute. (Within + a stored routine or trigger, NOW() returns the time at which the routine or + triggering statement began to execute.) In addition, changing the timestamp + system variable with a SET timestamp statement affects the value returned by + NOW() but not by SYSDATE(). This means that timestamp settings in the binary + log have no effect on invocations of SYSDATE(). Because SYSDATE() can return + different values even within the same statement, and is not affected by SET + TIMESTAMP, it is non-deterministic and therefore unsafe for replication if statement-based + binary logging is used. If that is a problem, you can use row-based logging, + or start the server with the mysqld option --sysdate-is-now to cause SYSDATE() + to be an alias for NOW(). The non-deterministic nature of SYSDATE() also means + that indexes cannot be used for evaluating expressions that refer to it, and + that statements using the SYSDATE() function are unsafe for statement-based + replication. + examples: + - sql: 'Difference between NOW() and SYSDATE():' + result: SELECT NOW(), SLEEP(2), NOW(); +---------------------+----------+---------------------+ + | NOW() | SLEEP(2) | NOW() | +---------------------+----------+---------------------+ + | 2010-03-27 13:23:40 | 0 | 2010-03-27 13:23:40 | +---------------------+----------+---------------------+ + - sql: SELECT SYSDATE(), SLEEP(2), SYSDATE(); + result: +---------------------+----------+---------------------+ | SYSDATE() | + SLEEP(2) | SYSDATE() | +---------------------+----------+---------------------+ + | 2010-03-27 13:23:52 | 0 | 2010-03-27 13:23:54 | +---------------------+----------+---------------------+ + - sql: 'With precision:' + result: SELECT SYSDATE(4); +--------------------------+ + - name: SYSTEM_USER + category_id: information + category_label: Information Functions + tags: + - information + aliases: [] + signature: + display: SYSTEM_USER + args: [] + summary: SYSTEM_USER() is a synonym for USER(). + description: 'SYSTEM_USER() is a synonym for USER(). URL: https://mariadb.com/kb/en/system_user/' + examples: [] + - name: SYS_GUID + category_id: miscellaneous + category_label: Miscellaneous Functions + tags: + - miscellaneous + aliases: [] + signature: + display: SYS_GUID + args: [] + summary: Returns a 16-byte globally unique identifier (GUID), similar to the UUID + description: 'Returns a 16-byte globally unique identifier (GUID), similar to + the UUID function, but without the - character. Example ------- SELECT SYS_GUID(); + +----------------------------------+ | SYS_GUID() | +----------------------------------+ + | 2C574E45BA2811EBB265F859713E4BE4 | +----------------------------------+ URL: + https://mariadb.com/kb/en/sys_guid/' + examples: [] + - name: TAN + category_id: numeric + category_label: Numeric Functions + tags: + - numeric + aliases: [] + signature: + display: TAN(X) + args: + - name: X + optional: false + type: any + summary: Returns the tangent of X, where X is given in radians. + description: Returns the tangent of X, where X is given in radians. + examples: + - sql: SELECT TAN(0.7853981633974483); + result: +-------------------------+ | TAN(0.7853981633974483) | +-------------------------+ + | 0.9999999999999999 | +-------------------------+ + - sql: SELECT TAN(PI()); + result: +-----------------------+ | TAN(PI()) | +-----------------------+ + | -1.22460635382238e-16 | +-----------------------+ + - sql: SELECT TAN(PI()+1); + result: +-----------------+ | TAN(PI()+1) | +-----------------+ | 1.5574077246549 + | +-----------------+ + - sql: SELECT TAN(RADIANS(PI())); + result: +--------------------+ | TAN(RADIANS(PI())) | +--------------------+ + | 0.0548861508080033 | +--------------------+ + - sql: 'URL: https://mariadb.com/kb/en/tan/' + - name: TEXT + category_id: data_types + category_label: Data Types + tags: + - data_types + aliases: [] + signature: + display: TEXT(M) + args: + - name: M + optional: false + type: any + summary: A TEXT column with a maximum length of 65,535 (216 - 1) characters. + description: A TEXT column with a maximum length of 65,535 (216 - 1) characters. + The effective maximum length is less if the value contains multi-byte characters. + Each TEXT value is stored using a two-byte length prefix that indicates the + number of bytes in the value. If you need a bigger storage, consider using MEDIUMTEXT + instead. An optional length M can be given for this type. If this is done, MariaDB + creates the column as the smallest TEXT type large enough to hold values M characters + long. Before MariaDB 10.2, all MariaDB collations were of type PADSPACE, meaning + that TEXT (as well as VARCHAR and CHAR values) are compared without regard for + trailing spaces. This does not apply to the LIKE pattern-matching operator, + which takes into account trailing spaces. Before MariaDB 10.2.1, BLOB and TEXT + columns could not be assigned a DEFAULT value. This restriction was lifted in + MariaDB 10.2.1. + examples: + - sql: 'Trailing spaces:' + result: CREATE TABLE strtest (d TEXT(10)); + - sql: SELECT d='Maria',d='Maria ' FROM strtest; + result: +-----------+--------------+ | d='Maria' | d='Maria ' | +-----------+--------------+ + | 1 | 1 | +-----------+--------------+ + - sql: SELECT d LIKE 'Maria',d LIKE 'Maria ' FROM strtest; + result: +----------------+-------------------+ | d LIKE 'Maria' | d LIKE 'Maria ' + | +----------------+-------------------+ | 0 | 1 + | +----------------+-------------------+ + - sql: Indexing -------- + result: TEXT columns can only be indexed over a specified length. This means + that they + - sql: unique index be created on them. + result: MariaDB starting with 10.4 + - sql: "Starting with MariaDB 10.4, a unique index can be created on a TEXT column.\n\ + \ ..." + - name: TIME + category_id: data_types + category_label: Data Types + tags: + - data_types + aliases: [] + signature: + display: TIME() + args: + - name: + optional: false + type: any + summary: A time. + description: "A time. The range is '-838:59:59.999999' to '838:59:59.999999'.\ + \ Microsecond\nprecision can be from 0-6; if not specified 0 is used. Microseconds\ + \ have been\navailable since MariaDB 5.3.\n\nMariaDB displays TIME values in\ + \ 'HH:MM:SS.ssssss' format, but allows\nassignment of times in looser formats,\ + \ including 'D HH:MM:SS', 'HH:MM:SS',\n'HH:MM', 'D HH:MM', 'D HH', 'SS', or\ + \ 'HHMMSS', as well as permitting dropping\nof any leading zeros when a delimiter\ + \ is provided, for example '3:9:10'. For\ndetails, see date and time literals.\n\ + \nMariaDB 10.1.2 introduced the --mysql56-temporal-format option, on by default,\n\ + which allows MariaDB to store TIMEs using the same low-level format MySQL 5.6\n\ + uses.\n\nInternal Format\n---------------\n\nIn MariaDB 10.1.2 a new temporal\ + \ format was introduced from MySQL 5.6 that\nalters how the TIME, DATETIME and\ + \ TIMESTAMP columns operate at lower levels.\nThese changes allow these temporal\ + \ data types to have fractional parts and\nnegative values. You can disable\ + \ this feature using the\nmysql56_temporal_format system variable.\n\nTables\ + \ that include TIMESTAMP values that were created on an older version of\nMariaDB\ + \ or that were created while the mysql56_temporal_format system variable\nwas\ + \ disabled continue to store data using the older data type format.\n\nIn order\ + \ to update table columns from the older format to the newer format,\nexecute\ + \ an ALTER TABLE... MODIFY COLUMN statement that changes the column to\nthe\ + \ *same* data type. This change may be needed if you want to export the\ntable's\ + \ tablespace and import it onto a server that has\nmysql56_temporal_format=ON\ + \ set (see MDEV-15225).\n\nFor instance, if you have a TIME column in your table:\n\ + \nSHOW VARIABLES LIKE 'mysql56_temporal_format';\n\n+-------------------------+-------+\n\ + | Variable_name | Value |\n+-------------------------+-------+\n|\ + \ mysql56_temporal_format | ON |\n+-------------------------+-------+\n\n\ + ALTER TABLE example_table MODIFY ts_col TIME;\n\nWhen MariaDB executes the ALTER\ + \ TABLE statement, it converts the data from the\nolder temporal format to the\ + \ newer one.\n\nIn the event that you have several tables and columns using\ + \ temporal data\ntypes that you want to switch over to the new format, make\ + \ sure the system\n ..." + examples: [] + - name: TIMEDIFF + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: TIMEDIFF(expr1,expr2) + args: + - name: expr1 + optional: false + type: any + - name: expr2 + optional: false + type: any + summary: TIMEDIFF() returns expr1 - expr2 expressed as a time value. + description: TIMEDIFF() returns expr1 - expr2 expressed as a time value. expr1 + and expr2 are time or date-and-time expressions, but both must be of the same + type. + examples: + - sql: SELECT TIMEDIFF('2000:01:01 00:00:00', '2000:01:01 00:00:00.000001'); + result: +---------------------------------------------------------------+ | + TIMEDIFF('2000:01:01 00:00:00', '2000:01:01 00:00:00.000001') | +---------------------------------------------------------------+ + | -00:00:00.000001 | +---------------------------------------------------------------+ + - sql: SELECT TIMEDIFF('2008-12-31 23:59:59.000001', '2008-12-30 01:01:01.000002'); + result: +----------------------------------------------------------------------+ + | TIMEDIFF('2008-12-31 23:59:59.000001', '2008-12-30 01:01:01.000002') | +----------------------------------------------------------------------+ + | 46:58:57.999999 | +----------------------------------------------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/timediff/' + - name: TIMESTAMP + category_id: data_types + category_label: Data Types + tags: + - data_types + aliases: [] + signature: + display: TIMESTAMP(=3;\n+------+\n| i |\n+------+\n\ + | 1 |\n| 2 |\n| 3 |\n| 4 |\n| 5 |\n| 6 |\n+------+\n\nSELECT\ + \ i FROM seqs WHERE i <= 3 UNION ALL SELECT i FROM seqs WHERE i>=3;\n+------+\n\ + \ ..." + examples: [] + - name: UNIX_TIMESTAMP + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: UNIX_TIMESTAMP + args: [] + summary: If called with no argument, returns a Unix timestamp (seconds since + description: If called with no argument, returns a Unix timestamp (seconds since + '1970-01-01 00:00:00' UTC) as an unsigned integer. If UNIX_TIMESTAMP() is called + with a date argument, it returns the value of the argument as seconds since + '1970-01-01 00:00:00' UTC. date may be a DATE string, a DATETIME string, a TIMESTAMP, + or a number in the format YYMMDD or YYYYMMDD. The server interprets date as + a value in the current time zone and converts it to an internal value in UTC. + Clients can set their time zone as described in time zones. The inverse function + of UNIX_TIMESTAMP() is FROM_UNIXTIME() UNIX_TIMESTAMP() supports microseconds. + Timestamps in MariaDB have a maximum value of 2147483647, equivalent to 2038-01-19 + 05:14:07. This is due to the underlying 32-bit limitation. Using the function + on a date beyond this will result in NULL being returned. Use DATETIME as a + storage type if you require dates beyond this. Error Handling -------------- + Returns NULL for wrong arguments to UNIX_TIMESTAMP(). In MySQL and MariaDB before + 5.3 wrong arguments to UNIX_TIMESTAMP() returned 0. Compatibility ------------- + As you can see in the examples above, UNIX_TIMESTAMP(constant-date-string) returns + a timestamp with 6 decimals while MariaDB 5.2 and before returns it without + decimals. This can cause a problem if you are using UNIX_TIMESTAMP() as a partitioning + function. You can fix this by using FLOOR(UNIX_TIMESTAMP(..)) or changing the + date string to a date number, like 20080101000000. + examples: + - sql: SELECT UNIX_TIMESTAMP(); + result: +------------------+ | UNIX_TIMESTAMP() | +------------------+ | 1269711082 + | +------------------+ + - sql: SELECT UNIX_TIMESTAMP('2007-11-30 10:30:19'); + result: +---------------------------------------+ | UNIX_TIMESTAMP('2007-11-30 + 10:30:19') | +---------------------------------------+ | 1196436619.000000 + | +---------------------------------------+ + - name: UPDATEXML + category_id: string + category_label: String Functions + tags: + - string + aliases: [] + signature: + display: UPDATEXML(xml_target, xpath_expr, new_xml) + args: + - name: xml_target + optional: false + type: any + - name: xpath_expr + optional: false + type: any + - name: new_xml + optional: false + type: any + summary: This function replaces a single portion of a given fragment of XML markup + description: This function replaces a single portion of a given fragment of XML + markup xml_target with a new XML fragment new_xml, and then returns the changed + XML. The portion of xml_target that is replaced matches an XPath expression + xpath_expr supplied by the user. If no expression matching xpath_expr is found, + or if multiple matches are found, the function returns the original xml_target + XML fragment. All three arguments should be strings. + examples: + - sql: "SELECT\n UpdateXML('ccc', '/a', 'fff') AS\ + \ val1,\n UpdateXML('ccc', '/b', 'fff') AS val2,\n\ + \ UpdateXML('ccc', '//b', 'fff') AS val3,\n \ + \ UpdateXML('ccc', '/a/d', 'fff') AS val4,\n \ + \ UpdateXML('ccc', '/a/d', 'fff') AS val5\n\ + \ \\G\n*************************** 1. row ***************************\nval1:\ + \ fff\nval2: ccc\nval3: fff\n\ + val4: cccfff\nval5: ccc\n\ + 1 row in set (0.00 sec)" + result: 'URL: https://mariadb.com/kb/en/updatexml/' + - name: UPPER + category_id: string + category_label: String Functions + tags: + - string + aliases: [] + signature: + display: UPPER(str) + args: + - name: str + optional: false + type: any + summary: Returns the string str with all characters changed to uppercase according + to + description: 'Returns the string str with all characters changed to uppercase + according to the current character set mapping. The default is latin1 (cp1252 + West European). UCASE is a synonym. SELECT UPPER(surname), givenname FROM users + ORDER BY surname; +----------------+------------+ | UPPER(surname) | givenname | + +----------------+------------+ | ABEL | Jacinto | | CASTRO | + Robert | | COSTA | Phestos | | MOSCHELLA | Hippolytos | + +----------------+------------+ UPPER() is ineffective when applied to binary + strings (BINARY, VARBINARY, BLOB). The description of LOWER() shows how to perform + lettercase conversion of binary strings. Prior to MariaDB 11.3, the query optimizer + did not handle queries of the format UCASE(varchar_col)=.... An optimizer_switch + option, sargable_casefold=ON, was added in MariaDB 11.3.0 to handle this case. + (MDEV-31496) URL: https://mariadb.com/kb/en/upper/' + examples: [] + - name: USER + category_id: information + category_label: Information Functions + tags: + - information + aliases: [] + signature: + display: USER + args: [] + summary: Returns the current MariaDB user name and host name, given when authenticating + description: Returns the current MariaDB user name and host name, given when authenticating + to MariaDB, as a string in the utf8 character set. Note that the value of USER() + may differ from the value of CURRENT_USER(), which is the user used to authenticate + the current client. CURRENT_ROLE() returns the current active role. SYSTEM_USER() + and SESSION_USER are synonyms for USER(). Statements using the USER() function + or one of its synonyms are not safe for statement level replication. + examples: + - sql: shell> mysql --user="anonymous" + result: SELECT USER(),CURRENT_USER(); +---------------------+----------------+ + | USER() | CURRENT_USER() | +---------------------+----------------+ + | anonymous@localhost | @localhost | +---------------------+----------------+ + - sql: To select only the IP address, use SUBSTRING_INDEX(), + result: SELECT SUBSTRING_INDEX(USER(), '@', -1); +----------------------------------+ + | SUBSTRING_INDEX(USER(), '@', -1) | +----------------------------------+ + | 192.168.0.101 | +----------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/user/' + - name: UTC_DATE + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: UTC_DATE + args: [] + summary: Returns the current UTC date as a value in 'YYYY-MM-DD' or YYYYMMDD format, + description: Returns the current UTC date as a value in 'YYYY-MM-DD' or YYYYMMDD + format, depending on whether the function is used in a string or numeric context. + examples: + - sql: SELECT UTC_DATE(), UTC_DATE() + 0; + result: +------------+----------------+ | UTC_DATE() | UTC_DATE() + 0 | +------------+----------------+ + | 2010-03-27 | 20100327 | +------------+----------------+ + - sql: 'URL: https://mariadb.com/kb/en/utc_date/' + - name: UTC_TIME + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: UTC_TIME([precision]) + args: + - name: precision + optional: true + type: any + summary: Returns the current UTC time as a value in 'HH:MM:SS' or HHMMSS. + description: Returns the current UTC time as a value in 'HH:MM:SS' or HHMMSS.uuuuuu + format, depending on whether the function is used in a string or numeric context. + The optional precision determines the microsecond precision. See Microseconds + in MariaDB. + examples: + - sql: SELECT UTC_TIME(), UTC_TIME() + 0; + result: +------------+----------------+ | UTC_TIME() | UTC_TIME() + 0 | +------------+----------------+ + | 17:32:34 | 173234.000000 | +------------+----------------+ + - sql: 'With precision:' + result: SELECT UTC_TIME(5); +----------------+ | UTC_TIME(5) | +----------------+ + | 07:52:50.78369 | +----------------+ + - sql: 'URL: https://mariadb.com/kb/en/utc_time/' + - name: UTC_TIMESTAMP + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: UTC_TIMESTAMP([precision]) + args: + - name: precision + optional: true + type: any + summary: Returns the current UTC date and time as a value in 'YYYY-MM-DD HH:MM:SS' + or + description: Returns the current UTC date and time as a value in 'YYYY-MM-DD HH:MM:SS' + or YYYYMMDDHHMMSS.uuuuuu format, depending on whether the function is used in + a string or numeric context. The optional precision determines the microsecond + precision. See Microseconds in MariaDB. + examples: + - sql: SELECT UTC_TIMESTAMP(), UTC_TIMESTAMP() + 0; + result: +---------------------+-----------------------+ | UTC_TIMESTAMP() | + UTC_TIMESTAMP() + 0 | +---------------------+-----------------------+ | + 2010-03-27 17:33:16 | 20100327173316.000000 | +---------------------+-----------------------+ + - sql: 'With precision:' + result: SELECT UTC_TIMESTAMP(4); +--------------------------+ | UTC_TIMESTAMP(4) | + +--------------------------+ | 2018-07-10 07:51:09.1019 | +--------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/utc_timestamp/' + - name: UUID + category_id: miscellaneous + category_label: Miscellaneous Functions + tags: + - miscellaneous + aliases: [] + signature: + display: UUID + args: [] + summary: Returns a Universally Unique Identifier (UUID). + description: "Returns a Universally Unique Identifier (UUID).\n\nA UUID is designed\ + \ as a number that is globally unique in space and time. Two\ncalls to UUID()\ + \ are expected to generate two different values, even if these\ncalls are performed\ + \ on two separate computers that are not connected to each\nother.\n\nUUID()\ + \ results are intended to be unique, but cannot always be relied upon to\nbe\ + \ unpredictable and unguessable.\n\nA UUID is a 128-bit number represented by\ + \ a utf8 string of five hexadecimal\nnumbers in aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\ + \ format:\n\n* The first three numbers are generated from a timestamp.\n* The\ + \ fourth number preserves temporal uniqueness in case the timestamp value\n\ + \ loses monotonicity (for example, due to daylight saving time).\n* The fifth\ + \ number is an IEEE 802 node number that provides spatial uniqueness.\n A random\ + \ number is substituted if the latter is not available (for example,\n because\ + \ the host computer has no Ethernet card, or we do not know how to find\n the\ + \ hardware address of an interface on your operating system). In this case,\n\ + \ spatial uniqueness cannot be guaranteed. Nevertheless, a collision should\n\ + \ have very low probability.\n\nCurrently, the MAC address of an interface is\ + \ taken into account only on\nFreeBSD and Linux. On other operating systems,\ + \ MariaDB uses a randomly\ngenerated 48-bit number.\n\nStatements using the\ + \ UUID() function are not safe for statement-based\nreplication.\n\nThe function\ + \ generates a UUIDv1 and the results are generated according to the\n\"DCE 1.1:Remote\ + \ Procedure Call\" (Appendix A) CAE (Common Applications\nEnvironment) Specifications\ + \ published by The Open Group in October 1997\n(Document Number C706)." + examples: + - sql: SELECT UUID(); + result: +--------------------------------------+ | UUID() | + +--------------------------------------+ | cd41294a-afb0-11df-bc9b-00241dd75637 + | +--------------------------------------+ + - sql: 'URL: https://mariadb.com/kb/en/uuid/' + - name: UUID_SHORT + category_id: miscellaneous + category_label: Miscellaneous Functions + tags: + - miscellaneous + aliases: [] + signature: + display: UUID_SHORT + args: [] + summary: Returns a "short" universally unique identifier as a 64-bit unsigned + integer + description: "Returns a \"short\" universally unique identifier as a 64-bit unsigned\ + \ integer\n(rather than a string-form 128-bit identifier as returned by the\ + \ UUID()\nfunction).\n\nThe value of UUID_SHORT() is guaranteed to be unique\ + \ if the following\nconditions hold:\n\n* The server_id of the current host\ + \ is unique among your set of master and\n slave servers\n* server_id is between\ + \ 0 and 255\n* You don't set back your system time for your server between mysqld\ + \ restarts\n* You do not invoke UUID_SHORT() on average more than 16\n million\ + \ times per second between mysqld restarts\n\nThe UUID_SHORT() return value\ + \ is constructed this way:\n\n(server_id & 255) << 56\n+ (server_startup_time_in_seconds\ + \ << 24)\n+ incremented_variable++;\n\nStatements using the UUID_SHORT() function\ + \ are not safe for statement-based\nreplication." + examples: + - sql: SELECT UUID_SHORT(); + result: +-------------------+ | UUID_SHORT() | +-------------------+ | + 21517162376069120 | +-------------------+ + - sql: create table t1 (a bigint unsigned default(uuid_short()) primary key); + insert into t1 values(),(); select * from t1; + result: +-------------------+ | a | +-------------------+ | + 98113699159474176 | | 98113699159474177 | +-------------------+ + - sql: 'URL: https://mariadb.com/kb/en/uuid_short/' + - name: VARBINARY + category_id: data_types + category_label: Data Types + tags: + - data_types + aliases: [] + signature: + display: VARBINARY(M) + args: + - name: M + optional: false + type: any + summary: The VARBINARY type is similar to the VARCHAR type, but stores binary + byte + description: The VARBINARY type is similar to the VARCHAR type, but stores binary + byte strings rather than non-binary character strings. M represents the maximum + column length in bytes. It contains no character set, and comparison and sorting + are based on the numeric value of the bytes. If the maximum length is exceeded, + and SQL strict mode is not enabled , the extra characters will be dropped with + a warning. If strict mode is enabled, an error will occur. Unlike BINARY values, + VARBINARYs are not right-padded when inserting. Oracle Mode ----------- In Oracle + mode from MariaDB 10.3, RAW is a synonym for VARBINARY. + examples: + - sql: 'Inserting too many characters, first with strict mode off, then with it + on:' + result: CREATE TABLE varbins (a VARBINARY(10)); + - sql: INSERT INTO varbins VALUES('12345678901'); Query OK, 1 row affected, 1 + warning (0.04 sec) + result: SELECT * FROM varbins; +------------+ | a | +------------+ + | 1234567890 | +------------+ + - sql: SET sql_mode='STRICT_ALL_TABLES'; + result: INSERT INTO varbins VALUES('12345678901'); + - sql: 'Sorting is performed with the byte value:' + result: TRUNCATE varbins; + - sql: INSERT INTO varbins VALUES('A'),('B'),('a'),('b'); + result: SELECT * FROM varbins ORDER BY a; +------+ | a | +------+ + - name: VARCHAR + category_id: data_types + category_label: Data Types + tags: + - data_types + aliases: [] + signature: + display: VARCHAR(M) + args: + - name: M + optional: false + type: any + summary: A variable-length string. + description: 'A variable-length string. M represents the maximum column length + in characters. The range of M is 0 to 65,532. The effective maximum length of + a VARCHAR is subject to the maximum row size and the character set used. For + example, utf8 characters can require up to three bytes per character, so a VARCHAR + column that uses the utf8 character set can be declared to be a maximum of 21,844 + characters. Note: For the ColumnStore engine, M represents the maximum column + length in bytes. MariaDB stores VARCHAR values as a one-byte or two-byte length + prefix plus data. The length prefix indicates the number of bytes in the value. + A VARCHAR column uses one length byte if values require no more than 255 bytes, + two length bytes if values may require more than 255 bytes. MariaDB follows + the standard SQL specification, and does not remove trailing spaces from VARCHAR + values. VARCHAR(0) columns can contain 2 values: an empty string or NULL. Such + columns cannot be part of an index. The CONNECT storage engine does not support + VARCHAR(0). VARCHAR is shorthand for CHARACTER VARYING. NATIONAL VARCHAR is + the standard SQL way to define that a VARCHAR column should use some predefined + character set. MariaDB uses utf8 as this predefined character set, as does MySQL + 4.1 and up. NVARCHAR is shorthand for NATIONAL VARCHAR. Before MariaDB 10.2, + all MariaDB collations were of type PADSPACE, meaning that VARCHAR (as well + as CHAR and TEXT values) are compared without regard for trailing spaces. This + does not apply to the LIKE pattern-matching operator, which takes into account + trailing spaces. From MariaDB 10.2, a number of NO PAD collations are available. + If a unique index consists of a column where trailing pad characters are stripped + or ignored, inserts into that column where values differ only by the number + of trailing pad characters will result in a duplicate-key error.' + examples: + - sql: 'The following are equivalent:' + result: VARCHAR(30) CHARACTER SET utf8 + - sql: NVARCHAR(30) NCHAR VARCHAR(30) NATIONAL CHARACTER VARYING(30) NATIONAL + CHAR VARYING(30) + result: 'Trailing spaces:' + - name: VARIANCE + category_id: group_by + category_label: Functions and Modifiers for Use with GROUP BY + tags: + - group_by + aliases: [] + signature: + display: VARIANCE(expr) + args: + - name: expr + optional: false + type: any + summary: Returns the population standard variance of expr. + description: Returns the population standard variance of expr. This is an extension + to standard SQL. The standard SQL function VAR_POP() can be used instead. Variance + is calculated by * working out the mean for the set * for each number, subtracting + the mean and squaring the result * calculate the average of the resulting differences + It is an aggregate function, and so can be used with the GROUP BY clause. VARIANCE() + can be used as a window function. VARIANCE() returns NULL if there were no matching + rows. + examples: + - sql: CREATE TABLE v(i tinyint); + result: INSERT INTO v VALUES(101),(99); + - sql: SELECT VARIANCE(i) FROM v; + result: +-------------+ | VARIANCE(i) | +-------------+ | 1.0000 | +-------------+ + - sql: INSERT INTO v VALUES(120),(80); + result: SELECT VARIANCE(i) FROM v; +-------------+ | VARIANCE(i) | +-------------+ + | 200.5000 | +-------------+ + - sql: 'As an aggregate function:' + result: CREATE OR REPLACE TABLE stats (category VARCHAR(2), x INT); + - sql: "INSERT INTO stats VALUES\n ('a',1),('a',2),('a',3),\n ('b',11),('b',12),('b',20),('b',30),('b',60);" + result: SELECT category, STDDEV_POP(x), STDDEV_SAMP(x), VAR_POP(x) + - sql: +----------+---------------+----------------+------------+ + result: '| category | STDDEV_POP(x) | STDDEV_SAMP(x) | VAR_POP(x) |' + - name: VAR_POP + category_id: group_by + category_label: Functions and Modifiers for Use with GROUP BY + tags: + - group_by + aliases: [] + signature: + display: VAR_POP(expr) + args: + - name: expr + optional: false + type: any + summary: Returns the population standard variance of expr. + description: Returns the population standard variance of expr. It considers rows + as the whole population, not as a sample, so it has the number of rows as the + denominator. You can also use VARIANCE(), which is equivalent but is not standard + SQL. Variance is calculated by * working out the mean for the set * for each + number, subtracting the mean and squaring the result * calculate the average + of the resulting differences It is an aggregate function, and so can be used + with the GROUP BY clause. VAR_POP() can be used as a window function. VAR_POP() + returns NULL if there were no matching rows. + examples: + - sql: CREATE TABLE v(i tinyint); + result: INSERT INTO v VALUES(101),(99); + - sql: SELECT VAR_POP(i) FROM v; + result: +------------+ | VAR_POP(i) | +------------+ | 1.0000 | +------------+ + - sql: INSERT INTO v VALUES(120),(80); + result: SELECT VAR_POP(i) FROM v; +------------+ | VAR_POP(i) | +------------+ + | 200.5000 | +------------+ + - sql: 'As an aggregate function:' + result: CREATE OR REPLACE TABLE stats (category VARCHAR(2), x INT); + - sql: "INSERT INTO stats VALUES\n ('a',1),('a',2),('a',3),\n ('b',11),('b',12),('b',20),('b',30),('b',60);" + result: SELECT category, STDDEV_POP(x), STDDEV_SAMP(x), VAR_POP(x) + - sql: '...' + - name: VAR_SAMP + category_id: group_by + category_label: Functions and Modifiers for Use with GROUP BY + tags: + - group_by + aliases: [] + signature: + display: VAR_SAMP(expr) + args: + - name: expr + optional: false + type: any + summary: Returns the sample variance of expr. + description: Returns the sample variance of expr. That is, the denominator is + the number of rows minus one. It is an aggregate function, and so can be used + with the GROUP BY clause. VAR_SAMP() can be used as a window function. VAR_SAMP() + returns NULL if there were no matching rows. + examples: + - sql: 'As an aggregate function:' + result: CREATE OR REPLACE TABLE stats (category VARCHAR(2), x INT); + - sql: "INSERT INTO stats VALUES\n ('a',1),('a',2),('a',3),\n ('b',11),('b',12),('b',20),('b',30),('b',60);" + result: SELECT category, STDDEV_POP(x), STDDEV_SAMP(x), VAR_POP(x) + - sql: +----------+---------------+----------------+------------+ + result: '| category | STDDEV_POP(x) | STDDEV_SAMP(x) | VAR_POP(x) | +----------+---------------+----------------+------------+ + | a | 0.8165 | 1.0000 | 0.6667 | | b | 18.0400 + | 20.1693 | 325.4400 | +----------+---------------+----------------+------------+' + - sql: 'As a window function:' + result: CREATE OR REPLACE TABLE student_test (name CHAR(10), test CHAR(10), + score + - sql: "INSERT INTO student_test VALUES\n ('Chun', 'SQL', 75), ('Chun', 'Tuning',\ + \ 73),\n ('Esben', 'SQL', 43), ('Esben', 'Tuning', 31),\n ('Kaolin', 'SQL',\ + \ 56), ('Kaolin', 'Tuning', 88),\n ('Tatiana', 'SQL', 87);" + result: SELECT name, test, score, VAR_SAMP(score) + - sql: +---------+--------+-------+------------------+ + result: '| name | test | score | variance_results | +---------+--------+-------+------------------+ + | Chun | SQL | 75 | 382.9167 | | Chun | Tuning | 73 + | 873.0000 | | Esben | SQL | 43 | 382.9167 | | Esben | + Tuning | 31 | 873.0000 | | Kaolin | SQL | 56 | 382.9167 + |' + - name: VERSION + category_id: information + category_label: Information Functions + tags: + - information + aliases: [] + signature: + display: VERSION + args: [] + summary: Returns a string that indicates the MariaDB server version. + description: Returns a string that indicates the MariaDB server version. The string + uses the utf8 character set. + examples: + - sql: SELECT VERSION(); + result: +----------------+ | VERSION() | +----------------+ | 10.4.7-MariaDB + | +----------------+ + - sql: 'The VERSION() string may have one or more of the following suffixes:' + result: +---------------------------+------------------------------------------------+ + | Suffix | Description | + +---------------------------+------------------------------------------------+ + | -embedded | The server is an embedded server | + | | (libmariadbd). | + +---------------------------+------------------------------------------------+ + | -log | General logging, slow logging or binary | + | | (replication) logging is enabled. | + +---------------------------+------------------------------------------------+ + | -debug | The server is compiled for debugging. | + +---------------------------+------------------------------------------------+ + | -valgrind | The server is compiled to be instrumented | + | | with valgrind. | + +---------------------------+------------------------------------------------+ + - sql: Changing the Version String --------------------------- + result: Some old legacy code may break because they are parsing the VERSION + string and + - sql: MDEV-7780. + result: One can fool these applications by setting the version string from the + command + - sql: 'URL: https://mariadb.com/kb/en/version/' + - name: WEEK + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: WEEK(date[,mode]) + args: + - name: date[ + optional: false + type: any + - name: mode] + optional: false + type: any + summary: This function returns the week number for date. + description: This function returns the week number for date. The two-argument + form of WEEK() allows you to specify whether the week starts on Sunday or Monday + and whether the return value should be in the range from 0 to 53 or from 1 to + 53. If the mode argument is omitted, the value of the default_week_format system + variable is used. Modes ----- +-------+---------------------+--------+------------------------------------+ + | Mode | 1st day of week | Range | Week 1 is the 1st week with | + +-------+---------------------+--------+------------------------------------+ + | 0 | Sunday | 0-53 | a Sunday in this year | + +-------+---------------------+--------+------------------------------------+ + | 1 | Monday | 0-53 | more than 3 days this year | + +-------+---------------------+--------+------------------------------------+ + | 2 | Sunday | 1-53 | a Sunday in this year | + +-------+---------------------+--------+------------------------------------+ + | 3 | Monday | 1-53 | more than 3 days this year | + +-------+---------------------+--------+------------------------------------+ + | 4 | Sunday | 0-53 | more than 3 days this year | + +-------+---------------------+--------+------------------------------------+ + | 5 | Monday | 0-53 | a Monday in this year | + +-------+---------------------+--------+------------------------------------+ + | 6 | Sunday | 1-53 | more than 3 days this year | + +-------+---------------------+--------+------------------------------------+ + | 7 | Monday | 1-53 | a Monday in this year | + +-------+---------------------+--------+------------------------------------+ + With the mode value of 3, which means 'more than 3 days this year', weeks are + numbered according to ISO 8601:1988. + examples: + - sql: SELECT WEEK('2008-02-20'); + result: +--------------------+ | WEEK('2008-02-20') | +--------------------+ + | 7 | +--------------------+ + - sql: SELECT WEEK('2008-02-20',0); + result: +----------------------+ | WEEK('2008-02-20',0) | +----------------------+ + | 7 | +----------------------+ + - sql: "SELECT WEEK('2008-02-20',1);\n ..." + - name: WEEKDAY + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: WEEKDAY(date) + args: + - name: date + optional: false + type: any + summary: Returns the weekday index for date (0 = Monday, 1 = Tuesday, . + description: Returns the weekday index for date (0 = Monday, 1 = Tuesday, ... + 6 = Sunday). This contrasts with DAYOFWEEK() which follows the ODBC standard + (1 = Sunday, 2 = Monday, ..., 7 = Saturday). + examples: + - sql: SELECT WEEKDAY('2008-02-03 22:23:00'); + result: +--------------------------------+ | WEEKDAY('2008-02-03 22:23:00') + | +--------------------------------+ | 6 | +--------------------------------+ + - sql: SELECT WEEKDAY('2007-11-06'); + result: +-----------------------+ | WEEKDAY('2007-11-06') | +-----------------------+ + | 1 | +-----------------------+ + - sql: "CREATE TABLE t1 (d DATETIME);\nINSERT INTO t1 VALUES\n (\"2007-01-30\ + \ 21:31:07\"),\n (\"1983-10-15 06:42:51\"),\n (\"2011-04-21 12:34:56\"),\n\ + \ (\"2011-10-30 06:31:41\"),\n (\"2011-01-30 14:03:25\"),\n (\"2004-10-07\ + \ 11:19:34\");" + result: SELECT d FROM t1 where WEEKDAY(d) = 6; +---------------------+ | d | + +---------------------+ | 2011-10-30 06:31:41 | | 2011-01-30 14:03:25 | +---------------------+ + - sql: 'URL: https://mariadb.com/kb/en/weekday/' + - name: WEEKOFYEAR + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: WEEKOFYEAR(date) + args: + - name: date + optional: false + type: any + summary: Returns the calendar week of the date as a number in the range from 1 + to 53. + description: Returns the calendar week of the date as a number in the range from + 1 to 53. WEEKOFYEAR() is a compatibility function that is equivalent to WEEK(date,3). + examples: + - sql: SELECT WEEKOFYEAR('2008-02-20'); + result: +--------------------------+ | WEEKOFYEAR('2008-02-20') | +--------------------------+ + | 8 | +--------------------------+ + - sql: "CREATE TABLE t1 (d DATETIME);\nINSERT INTO t1 VALUES\n (\"2007-01-30\ + \ 21:31:07\"),\n (\"1983-10-15 06:42:51\"),\n (\"2011-04-21 12:34:56\"),\n\ + \ (\"2011-10-30 06:31:41\"),\n (\"2011-01-30 14:03:25\"),\n (\"2004-10-07\ + \ 11:19:34\");" + result: select * from t1; +---------------------+ | d | +---------------------+ + | 2007-01-30 21:31:07 | | 1983-10-15 06:42:51 | | 2011-04-21 12:34:56 | | + 2011-10-30 06:31:41 | | 2011-01-30 14:03:25 | | 2004-10-07 11:19:34 | +---------------------+ + - sql: SELECT d, WEEKOFYEAR(d), WEEK(d,3) from t1; + result: +---------------------+---------------+-----------+ | d | + WEEKOFYEAR(d) | WEEK(d,3) | +---------------------+---------------+-----------+ + | 2007-01-30 21:31:07 | 5 | 5 | | 1983-10-15 06:42:51 + | 41 | 41 | | 2011-04-21 12:34:56 | 16 | 16 + | | 2011-10-30 06:31:41 | 43 | 43 | | 2011-01-30 14:03:25 + | 4 | 4 | | 2004-10-07 11:19:34 | 41 | 41 + | +---------------------+---------------+-----------+ + - sql: 'URL: https://mariadb.com/kb/en/weekofyear/' + - name: WEIGHT_STRING + category_id: string + category_label: String Functions + tags: + - string + aliases: [] + signature: + display: WEIGHT_STRING(str [AS {CHAR|BINARY}(N) + args: + - name: str [AS {CHAR|BINARY}(N + optional: false + type: any + summary: Returns a binary string representing the string's sorting and comparison + description: Returns a binary string representing the string's sorting and comparison + value. A string with a lower result means that for sorting purposes the string + appears before a string with a higher result. WEIGHT_STRING() is particularly + useful when adding new collations, for testing purposes. If str is a non-binary + string (CHAR, VARCHAR or TEXT), WEIGHT_STRING returns the string's collation + weight. If str is a binary string (BINARY, VARBINARY or BLOB), the return value + is simply the input value, since the weight for each byte in a binary string + is the byte value. WEIGHT_STRING() returns NULL if given a NULL input. The optional + AS clause permits casting the input string to a binary or non-binary string, + as well as to a particular length. AS BINARY(N) measures the length in bytes + rather than characters, and right pads with 0x00 bytes to the desired length. + AS CHAR(N) measures the length in characters, and right pads with spaces to + the desired length. N has a minimum value of 1, and if it is less than the length + of the input string, the string is truncated without warning. The optional LEVEL + clause specifies that the return value should contain weights for specific collation + levels. The levels specifier can either be a single integer, a comma-separated + list of integers, or a range of integers separated by a dash (whitespace is + ignored). Integers can range from 1 to a maximum of 6, dependent on the collation, + and need to be listed in ascending order. If the LEVEL clause is no provided, + a default of 1 to the maximum for the collation is assumed. If the LEVEL is + specified without using a range, an optional modifier is permitted. ASC, the + default, returns the weights without any modification. DESC returns bitwise-inverted + weights. REVERSE returns the weights in reverse order. + examples: + - sql: "The examples below use the HEX() function to represent non-printable results\n\ + in hexadecimal format.\n ..." + - name: WITHIN + category_id: geometry_relations + category_label: Geometry Relations + tags: + - geometry_relations + aliases: [] + signature: + display: WITHIN(g1,g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + summary: Returns 1 or 0 to indicate whether g1 is spatially within g2. + description: Returns 1 or 0 to indicate whether g1 is spatially within g2. This + tests the opposite relationship as Contains(). WITHIN() is based on the original + MySQL implementation, and uses object bounding rectangles, while ST_WITHIN() + uses object shapes. + examples: + - sql: SET @g1 = GEOMFROMTEXT('POINT(174 149)'); SET @g2 = GEOMFROMTEXT('POINT(176 + 151)'); SET @g3 = GEOMFROMTEXT('POLYGON((175 150, 20 40, 50 60, 125 100, 175 + 150))'); + result: SELECT within(@g1,@g3); +-----------------+ | within(@g1,@g3) | +-----------------+ + | 1 | +-----------------+ + - sql: SELECT within(@g2,@g3); + result: +-----------------+ | within(@g2,@g3) | +-----------------+ | 0 + | +-----------------+ + - sql: 'URL: https://mariadb.com/kb/en/within/' + - name: WSREP_LAST_SEEN_GTID + category_id: galera + category_label: Galera Functions + tags: + - galera + aliases: [] + signature: + display: WSREP_LAST_SEEN_GTID + args: [] + summary: Returns the Global Transaction ID of the most recent write transaction + description: 'Returns the Global Transaction ID of the most recent write transaction + observed by the client. The result can be useful to determine the transaction + to provide to WSREP_SYNC_WAIT_UPTO_GTID for waiting and unblocking purposes. + URL: https://mariadb.com/kb/en/wsrep_last_seen_gtid/' + examples: [] + - name: WSREP_LAST_WRITTEN_GTID + category_id: galera + category_label: Galera Functions + tags: + - galera + aliases: [] + signature: + display: WSREP_LAST_WRITTEN_GTID + args: [] + summary: Returns the Global Transaction ID of the most recent write transaction + description: 'Returns the Global Transaction ID of the most recent write transaction + performed by the client. URL: https://mariadb.com/kb/en/wsrep_last_written_gtid/' + examples: [] + - name: WSREP_SYNC_WAIT_UPTO_GTID + category_id: galera + category_label: Galera Functions + tags: + - galera + aliases: [] + signature: + display: WSREP_SYNC_WAIT_UPTO_GTID(gtid[,timeout]) + args: + - name: gtid[ + optional: false + type: any + - name: timeout] + optional: false + type: any + summary: Blocks the client until the transaction specified by the given Global + description: 'Blocks the client until the transaction specified by the given Global + Transaction ID is applied and committed by the node. The optional timeout argument + can be used to specify a block timeout in seconds. If not provided, the timeout + will be indefinite. Returns the node that applied and committed the Global Transaction + ID, ER_LOCAL_WAIT_TIMEOUT if the function is timed out before this, or ER_WRONG_ARGUMENTS + if the function is given an invalid GTID. The result from WSREP_LAST_SEEN_GTID + can be useful to determine the transaction to provide to WSREP_SYNC_WAIT_UPTO_GTID + for waiting and unblocking purposes. URL: https://mariadb.com/kb/en/wsrep_sync_wait_upto_gtid/' + examples: [] + - name: YEAR + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: YEAR(date) + args: + - name: date + optional: false + type: any + summary: Returns the year for the given date, in the range 1000 to 9999, or 0 + for the + description: Returns the year for the given date, in the range 1000 to 9999, or + 0 for the "zero" date. + examples: + - sql: "CREATE TABLE t1 (d DATETIME);\nINSERT INTO t1 VALUES\n (\"2007-01-30\ + \ 21:31:07\"),\n (\"1983-10-15 06:42:51\"),\n (\"2011-04-21 12:34:56\"),\n\ + \ (\"2011-10-30 06:31:41\"),\n (\"2011-01-30 14:03:25\"),\n (\"2004-10-07\ + \ 11:19:34\");" + result: SELECT * FROM t1; +---------------------+ | d | +---------------------+ + | 2007-01-30 21:31:07 | | 1983-10-15 06:42:51 | | 2011-04-21 12:34:56 | | + 2011-10-30 06:31:41 | | 2011-01-30 14:03:25 | | 2004-10-07 11:19:34 | +---------------------+ + - sql: SELECT * FROM t1 WHERE YEAR(d) = 2011; + result: +---------------------+ | d | +---------------------+ + | 2011-04-21 12:34:56 | | 2011-10-30 06:31:41 | | 2011-01-30 14:03:25 | +---------------------+ + - sql: SELECT YEAR('1987-01-01'); + result: +--------------------+ | YEAR('1987-01-01') | +--------------------+ + | 1987 | +--------------------+ + - sql: 'URL: https://mariadb.com/kb/en/year/' + - name: YEARWEEK + category_id: date_time + category_label: Date and Time Functions + tags: + - date_time + aliases: [] + signature: + display: YEARWEEK(date) + args: + - name: date + optional: false + type: any + summary: Returns year and week for a date. + description: Returns year and week for a date. The mode argument works exactly + like the mode argument to WEEK(). The year in the result may be different from + the year in the date argument for the first and the last week of the year. + examples: + - sql: SELECT YEARWEEK('1987-01-01'); + result: +------------------------+ | YEARWEEK('1987-01-01') | +------------------------+ + | 198652 | +------------------------+ + - sql: "CREATE TABLE t1 (d DATETIME);\nINSERT INTO t1 VALUES\n (\"2007-01-30\ + \ 21:31:07\"),\n (\"1983-10-15 06:42:51\"),\n (\"2011-04-21 12:34:56\"),\n\ + \ (\"2011-10-30 06:31:41\"),\n (\"2011-01-30 14:03:25\"),\n (\"2004-10-07\ + \ 11:19:34\");" + result: SELECT * FROM t1; +---------------------+ | d | +---------------------+ + | 2007-01-30 21:31:07 | | 1983-10-15 06:42:51 | | 2011-04-21 12:34:56 | | + 2011-10-30 06:31:41 | | 2011-01-30 14:03:25 | | 2004-10-07 11:19:34 | +---------------------+ + - sql: SELECT YEARWEEK(d) FROM t1 WHERE YEAR(d) = 2011; + result: +-------------+ | YEARWEEK(d) | +-------------+ | 201116 | | 201144 + | | 201105 | +-------------+ + - sql: 'URL: https://mariadb.com/kb/en/yearweek/' +versions: + '5': + functions_remove: + - AES_DECRYPT + - AES_ENCRYPT + - BLOB + - CAST + - CONNECTION_ID + - CONV + - CONVERT + - CUME_DIST + - DATETIME + - DECIMAL + - DECODE + - DENSE_RANK + - EQUALS + - EXCEPT + - FIRST_VALUE + - IFNULL + - INET6_ATON + - INTERSECT + - IS_IPV4_COMPAT + - IS_IPV4_MAPPED + - JSON_ARRAY + - JSON_ARRAYAGG + - JSON_ARRAY_APPEND + - JSON_ARRAY_INSERT + - JSON_ARRAY_INTERSECT + - JSON_COMPACT + - JSON_CONTAINS + - JSON_CONTAINS_PATH + - JSON_DEPTH + - JSON_DETAILED + - JSON_EQUALS + - JSON_EXISTS + - JSON_EXTRACT + - JSON_INSERT + - JSON_KEYS + - JSON_LENGTH + - JSON_LOOSE + - JSON_MERGE + - JSON_MERGE_PATCH + - JSON_MERGE_PRESERVE + - JSON_NORMALIZE + - JSON_OBJECT + - JSON_OBJECTAGG + - JSON_OBJECT_FILTER_KEYS + - JSON_OBJECT_TO_ARRAY + - JSON_OVERLAPS + - JSON_QUERY + - JSON_QUOTE + - JSON_REMOVE + - JSON_REPLACE + - JSON_SCHEMA_VALID + - JSON_SEARCH + - JSON_SET + - JSON_TABLE + - JSON_TYPE + - JSON_UNQUOTE + - JSON_VALID + - JSON_VALUE + - LAG + - LEAD + - LENGTH + - LIMIT + - LPAD + - LTRIM + - MASTER_GTID_WAIT + - NOW + - NTH_VALUE + - NTILE + - OCTET_LENGTH + - PASSWORD + - PERCENTILE_CONT + - PERCENTILE_DISC + - PERCENT_RANK + - RANK + - ROW_NUMBER + - RPAD + - RTRIM + - SUBSTRING + - TIMESTAMP + - TRIM + - UNCOMPRESSED_LENGTH + - UNION + - VARBINARY + - VARCHAR + '10': + functions_remove: + - AES_DECRYPT + - AES_ENCRYPT + - CONV + - TIMESTAMP + '11': {} + '12': {} diff --git a/structures/engines/mysql/context.py b/structures/engines/mysql/context.py index 716d170..65a2aee 100644 --- a/structures/engines/mysql/context.py +++ b/structures/engines/mysql/context.py @@ -1,4 +1,5 @@ import re +import ssl from typing import Any, Optional import pymysql @@ -9,11 +10,26 @@ from structures.connection import Connection from structures.engines.context import QUERY_LOGS, AbstractContext -from structures.engines.database import SQLColumn, SQLDatabase, SQLForeignKey, SQLIndex, SQLTable +from structures.engines.database import ( + SQLColumn, + SQLDatabase, + SQLForeignKey, + SQLIndex, + SQLTable, +) from structures.engines.datatype import SQLDataType from structures.engines.mysql import MAP_COLUMN_FIELDS -from structures.engines.mysql.database import MySQLColumn, MySQLDatabase, MySQLForeignKey, MySQLIndex, MySQLRecord, MySQLTable, MySQLTrigger, MySQLView +from structures.engines.mysql.database import ( + MySQLColumn, + MySQLDatabase, + MySQLForeignKey, + MySQLIndex, + MySQLRecord, + MySQLTable, + MySQLTrigger, + MySQLView, +) from structures.engines.mysql.datatype import MySQLDataType from structures.engines.mysql.indextype import MySQLIndexType @@ -26,7 +42,8 @@ class MySQLContext(AbstractContext): DATATYPE = MySQLDataType INDEXTYPE = MySQLIndexType - IDENTIFIER_QUOTE = "`" + IDENTIFIER_QUOTE_CHAR = "`" + DEFAULT_STATEMENT_SEPARATOR = ";" def __init__(self, connection: Connection): super().__init__(connection) @@ -37,8 +54,8 @@ def __init__(self, connection: Connection): # self.database = session.configuration.database self.port = getattr(connection.configuration, "port", 3306) - def _on_connect(self, *args, **kwargs): - super()._on_connect(*args, **kwargs) + def after_connect(self, *args, **kwargs): + super().after_connect(*args, **kwargs) self.execute(""" SELECT COLLATION_NAME, CHARACTER_SET_NAME FROM information_schema.COLLATIONS WHERE CHARACTER_SET_NAME IS NOT NULL @@ -50,97 +67,126 @@ def _on_connect(self, *args, **kwargs): self.execute("""SHOW ENGINES;""") self.ENGINES = [dict(row).get("Engine") for row in self.fetchall()] - try: - self.execute(""" - SELECT WORD FROM information_schema.KEYWORDS - WHERE RESERVED = 1 - ORDER BY WORD; - """) - self.KEYWORDS = tuple(row["WORD"] for row in self.fetchall()) - except Exception: - self.KEYWORDS = () - - try: - self.execute(""" - SELECT FUNCTION FROM information_schema.SQL_FUNCTIONS - ORDER BY FUNCTION; - """) - builtin_functions = tuple(row["FUNCTION"] for row in self.fetchall()) - except Exception: - builtin_functions = () + server_version = self.get_server_version() + self.KEYWORDS, builtin_functions = self.get_engine_vocabulary( + "mysql", server_version + ) self.execute(""" SELECT DISTINCT ROUTINE_NAME FROM information_schema.ROUTINES WHERE ROUTINE_TYPE = 'FUNCTION' ORDER BY ROUTINE_NAME; """) - user_functions = tuple(row["ROUTINE_NAME"] for row in self.fetchall()) + user_functions = tuple(row["ROUTINE_NAME"].upper() for row in self.fetchall()) - self.FUNCTIONS = builtin_functions + user_functions + self.FUNCTIONS = tuple(dict.fromkeys(builtin_functions + user_functions)) def _parse_type(self, column_type: str): types = MySQLDataType.get_all() - type_set = [x.lower() for type in types if type.has_set for x in ([type.name] + type.alias)] - type_length = [x.lower() for type in types if type.has_length for x in ([type.name] + type.alias)] - - if match := re.search(fr"^({'|'.join(type_set)})\((.*)\)$", column_type): - return dict( - name=match.group(1).upper(), - set=[value.strip("'") for value in match.group(2).split(",")] - ) - if match := re.search(fr"^({'|'.join(type_length)})\((.*)\)$", column_type): + type_set = [ + x.lower() + for type in types + if type.has_set + for x in ([type.name] + type.alias) + ] + type_length = [ + x.lower() + for type in types + if type.has_length + for x in ([type.name] + type.alias) + ] + + if match := re.search(rf"^({'|'.join(type_set)})\((.*)\)$", column_type): return dict( name=match.group(1).upper(), - length=int(match.group(2)) + set=[value.strip("'") for value in match.group(2).split(",")], ) + if match := re.search(rf"^({'|'.join(type_length)})\((.*)\)$", column_type): + return dict(name=match.group(1).upper(), length=int(match.group(2))) - if match := re.search(r"(\w+)\s*\((\d+)(?:,\s*(\d+))?\)(\s*unsigned)?(\s*zerofill)?", column_type): + if match := re.search( + r"(\w+)\s*\((\d+)(?:,\s*(\d+))?\)(\s*unsigned)?(\s*zerofill)?", column_type + ): return dict( name=match.group(1).upper(), precision=int(match.group(2)), scale=int(match.group(3)) if match.group(3) else None, is_unsigned=bool(match.group(4)), - is_zerofill=bool(match.group(5)) + is_zerofill=bool(match.group(5)), ) return dict() def connect(self, **connect_kwargs) -> None: if self._connection is None: + self.before_connect() + + use_tls_enabled = bool( + getattr(self.connection.configuration, "use_tls_enabled", False) + ) + base_kwargs = dict( host=self.host, user=self.user, password=self.password, port=self.port, - cursorclass=pymysql.cursors.DictCursor + cursorclass=pymysql.cursors.DictCursor, + **connect_kwargs, + ) + if use_tls_enabled: + base_kwargs["ssl"] = { + "cert_reqs": ssl.CERT_NONE, + "check_hostname": False, + } + logger.debug( + "MySQL connect target host=%s port=%s user=%s use_tls_enabled=%s", + base_kwargs.get("host"), + base_kwargs.get("port"), + base_kwargs.get("user"), + use_tls_enabled, ) try: - # SSH tunnel support via connection configuration - if hasattr(self.connection, 'ssh_tunnel') and self.connection.ssh_tunnel: - ssh_config = self.connection.ssh_tunnel - self._ssh_tunnel = SSHTunnel( - ssh_config.hostname, int(ssh_config.port), - ssh_username=ssh_config.username, - ssh_password=ssh_config.password, - remote_port=self.port, - local_bind_address=(self.host, int(getattr(ssh_config, 'local_port', 0))) - ) - self._ssh_tunnel.start() - base_kwargs.update( - host=self.host, - port=self._ssh_tunnel.local_port, - ) - - self._connection = pymysql.connect(**{ + self._connection = pymysql.connect(**base_kwargs) + self._cursor = self._connection.cursor() + except pymysql.err.OperationalError as e: + should_retry_tls = bool(e.args and e.args[0] == 1045) + if not should_retry_tls or "ssl" in base_kwargs: + logger.error(f"Failed to connect to MySQL: {e}") + raise + + logger.warning( + "MySQL connection failed without TLS (%s). Retrying with TLS.", + e, + ) + logger.debug( + "Retrying MySQL connection with TLS preferred after auth failure" + ) + tls_kwargs = { **base_kwargs, - **connect_kwargs - }) + "ssl": { + "cert_reqs": ssl.CERT_NONE, + "check_hostname": False, + }, + } + self._connection = pymysql.connect(**tls_kwargs) self._cursor = self._connection.cursor() - self._on_connect() + + if hasattr(self.connection, "configuration"): + self.connection.configuration = ( + self.connection.configuration._replace(use_tls_enabled=True) + ) + logger.info( + "MySQL connection succeeded after enabling TLS automatically." + ) except Exception as e: logger.error(f"Failed to connect to MySQL: {e}") raise + else: + self.after_connect() + + def set_database(self, database: SQLDatabase) -> None: + self.execute(f"USE {database.quoted_name}") def get_server_version(self) -> str: self.execute("SELECT VERSION() as version") @@ -174,41 +220,59 @@ def get_databases(self) -> list[SQLDatabase]: """) results = [] for i, row in enumerate(self.fetchall()): - results.append(MySQLDatabase( - id=i, - name=row["database_name"], - default_collation=row["default_collation"], - total_bytes=float(row["total_bytes"]), - context=self, - get_tables_handler=self.get_tables, - get_views_handler=self.get_views, - get_triggers_handler=self.get_triggers, - )) + results.append( + MySQLDatabase( + id=i, + name=row["database_name"], + default_collation=row["default_collation"], + total_bytes=float(row["total_bytes"]), + context=self, + get_tables_handler=self.get_tables, + get_views_handler=self.get_views, + get_triggers_handler=self.get_triggers, + ) + ) return results def get_views(self, database: SQLDatabase): results: list[MySQLView] = [] - self.execute(f"SELECT TABLE_NAME, VIEW_DEFINITION FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_SCHEMA = '{database.name}' ORDER BY TABLE_NAME") + self.execute( + f"SELECT TABLE_NAME, VIEW_DEFINITION FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_SCHEMA = '{database.name}' ORDER BY TABLE_NAME" + ) for i, result in enumerate(self.fetchall()): - results.append(MySQLView( - id=i, - name=result["TABLE_NAME"], - database=database, - sql=result["VIEW_DEFINITION"] - )) + results.append( + MySQLView( + id=i, + name=result["TABLE_NAME"], + database=database, + statement=result["VIEW_DEFINITION"] or "", + ) + ) return results + def get_definers(self) -> list[str]: + self.execute(""" + SELECT DISTINCT CONCAT(User, '@', Host) as definer + FROM mysql.user + ORDER BY definer + """) + return [row["definer"] for row in self.fetchall()] + def get_triggers(self, database: SQLDatabase) -> list[MySQLTrigger]: results: list[MySQLTrigger] = [] - self.execute(f"SELECT TRIGGER_NAME, ACTION_STATEMENT FROM INFORMATION_SCHEMA.TRIGGERS WHERE TRIGGER_SCHEMA = '{database.name}' ORDER BY TRIGGER_NAME") + self.execute( + f"SELECT TRIGGER_NAME, ACTION_STATEMENT FROM INFORMATION_SCHEMA.TRIGGERS WHERE TRIGGER_SCHEMA = '{database.name}' ORDER BY TRIGGER_NAME" + ) for i, result in enumerate(self.fetchall()): - results.append(MySQLTrigger( - id=i, - name=result["TRIGGER_NAME"], - database=database, - sql=result["ACTION_STATEMENT"] - )) + results.append( + MySQLTrigger( + id=i, + name=result["TRIGGER_NAME"], + database=database, + statement=result["ACTION_STATEMENT"], + ) + ) return results @@ -240,6 +304,7 @@ def get_tables(self, database: SQLDatabase) -> list[SQLTable]: total_rows=row["TABLE_ROWS"], get_columns_handler=self.get_columns, get_indexes_handler=self.get_indexes, + get_checks_handler=self.get_checks, get_foreign_keys_handler=self.get_foreign_keys, get_records_handler=self.get_records, ) @@ -347,6 +412,42 @@ def get_indexes(self, table: SQLTable) -> list[SQLIndex]: return results + def get_checks(self, table: MySQLTable) -> list[MySQLCheck]: + from structures.engines.mysql.database import MySQLCheck + + if table is None or table.is_new: + return [] + + query = f""" + SELECT + cc.CONSTRAINT_NAME, + cc.CHECK_CLAUSE + FROM information_schema.CHECK_CONSTRAINTS cc + JOIN information_schema.TABLE_CONSTRAINTS tc + ON cc.CONSTRAINT_SCHEMA = tc.CONSTRAINT_SCHEMA + AND cc.CONSTRAINT_NAME = tc.CONSTRAINT_NAME + WHERE tc.TABLE_SCHEMA = '{table.database.name}' + AND tc.TABLE_NAME = '{table.name}' + AND tc.CONSTRAINT_TYPE = 'CHECK' + ORDER BY cc.CONSTRAINT_NAME + """ + + self.execute(query) + rows = self.fetchall() + + results = [] + for i, row in enumerate(rows): + results.append( + MySQLCheck( + id=i, + name=row["CONSTRAINT_NAME"], + table=table, + expression=row["CHECK_CLAUSE"], + ) + ) + + return results + def get_foreign_keys(self, table: SQLTable) -> list[SQLForeignKey]: if table is None or table.is_new: return [] @@ -370,20 +471,31 @@ def get_foreign_keys(self, table: SQLTable) -> list[SQLForeignKey]: """) foreign_keys = [] for i, row in enumerate(self.cursor.fetchall()): - foreign_keys.append(MySQLForeignKey( - id=i, - name=row["CONSTRAINT_NAME"], - columns=row["COLUMNS_NAMES"].split(","), - table=table, - reference_table=row["REFERENCED_TABLE_NAME"], - reference_columns=row["REFERENCED_COLUMNS"].split(","), - on_update=row["UPDATE_RULE"], - on_delete=row["DELETE_RULE"], - )) + foreign_keys.append( + MySQLForeignKey( + id=i, + name=row["CONSTRAINT_NAME"], + columns=row["COLUMNS_NAMES"].split(","), + table=table, + reference_table=row["REFERENCED_TABLE_NAME"], + reference_columns=row["REFERENCED_COLUMNS"].split(","), + on_update=row["UPDATE_RULE"], + on_delete=row["DELETE_RULE"], + ) + ) return foreign_keys - def get_records(self, table: SQLTable, /, *, filters: Optional[str] = None, limit: int = 1000, offset: int = 0, orders: Optional[str] = None) -> list[MySQLRecord]: + def get_records( + self, + table: SQLTable, + /, + *, + filters: Optional[str] = None, + limit: int = 1000, + offset: int = 0, + orders: Optional[str] = None, + ) -> list[MySQLRecord]: QUERY_LOGS.append(f"/* get_records for table={table.name} */") if table is None or table.is_new: return [] @@ -398,13 +510,13 @@ def get_records(self, table: SQLTable, /, *, filters: Optional[str] = None, limi results = [] for i, record in enumerate(self.cursor.fetchall(), start=offset): - results.append( - MySQLRecord(id=i, table=table, values=dict(record)) - ) + results.append(MySQLRecord(id=i, table=table, values=dict(record))) logger.debug(f"get records for table={table.name}") return results - def build_empty_table(self, database: SQLDatabase, /, name: Optional[str] = None, **default_values) -> MySQLTable: + def build_empty_table( + self, database: SQLDatabase, /, name: Optional[str] = None, **default_values + ) -> MySQLTable: id = MySQLContext.get_temporary_id(database.tables) if name is None: @@ -419,26 +531,38 @@ def build_empty_table(self, database: SQLDatabase, /, name: Optional[str] = None database=database, get_indexes_handler=self.get_indexes, get_columns_handler=self.get_columns, + get_checks_handler=self.get_checks, get_foreign_keys_handler=self.get_foreign_keys, get_records_handler=self.get_records, **default_values, ).copy() - def build_empty_column(self, table: SQLTable, datatype: SQLDataType, /, name: Optional[str] = None, **default_values) -> MySQLColumn: + def build_empty_column( + self, + table: SQLTable, + datatype: SQLDataType, + /, + name: Optional[str] = None, + **default_values, + ) -> MySQLColumn: id = MySQLContext.get_temporary_id(table.columns) if name is None: name = _(f"Column{str(id * -1):03}") return MySQLColumn( - id=id, - name=name, - table=table, - datatype=datatype, - **default_values + id=id, name=name, table=table, datatype=datatype, **default_values ) - def build_empty_index(self, table: MySQLTable, indextype: MySQLIndexType, columns: list[str], /, name: Optional[str] = None, **default_values) -> MySQLIndex: + def build_empty_index( + self, + table: MySQLTable, + indextype: MySQLIndexType, + columns: list[str], + /, + name: Optional[str] = None, + **default_values, + ) -> MySQLIndex: id = MySQLContext.get_temporary_id(table.indexes) if name is None: @@ -452,31 +576,62 @@ def build_empty_index(self, table: MySQLTable, indextype: MySQLIndexType, column table=table, ) - def build_empty_foreign_key(self, table: MySQLTable, columns: list[str], /, name: Optional[str] = None, **default_values) -> MySQLForeignKey: + def build_empty_check( + self, + table: MySQLTable, + /, + name: Optional[str] = None, + expression: Optional[str] = None, + **default_values, + ) -> MySQLCheck: + from structures.engines.mysql.database import MySQLCheck + + id = MySQLContext.get_temporary_id(table.checks) + + if name is None: + name = f"check_{abs(id)}" + + return MySQLCheck( + id=id, name=name, table=table, expression=expression or "", **default_values + ) + + def build_empty_foreign_key( + self, + table: MySQLTable, + columns: list[str], + /, + name: Optional[str] = None, + **default_values, + ) -> MySQLForeignKey: id = MySQLContext.get_temporary_id(table.foreign_keys) if name is None: name = _(f"ForeignKey{str(id * -1):03}") + reference_table = default_values.get("reference_table", "") + reference_columns = default_values.get("reference_columns", []) + return MySQLForeignKey( id=id, name=name, table=table, columns=columns, - reference_table="", - reference_columns=[], - on_update="", - on_delete="" + reference_table=reference_table, + reference_columns=reference_columns, + on_update=default_values.get("on_update", ""), + on_delete=default_values.get("on_delete", ""), ) - def build_empty_record(self, table: MySQLTable, /, *, values: dict[str, Any]) -> MySQLRecord: + def build_empty_record( + self, table: MySQLTable, /, *, values: dict[str, Any] + ) -> MySQLRecord: return MySQLRecord( - id=MySQLContext.get_temporary_id(table.records), - table=table, - values=values + id=MySQLContext.get_temporary_id(table.records), table=table, values=values ) - def build_empty_view(self, database: SQLDatabase, /, name: Optional[str] = None, **default_values) -> MySQLView: + def build_empty_view( + self, database: SQLDatabase, /, name: Optional[str] = None, **default_values + ) -> MySQLView: id = MySQLContext.get_temporary_id(database.views) if name is None: @@ -486,18 +641,45 @@ def build_empty_view(self, database: SQLDatabase, /, name: Optional[str] = None, id=id, name=name, database=database, + statement=default_values.get("statement", ""), + ) + + def build_empty_function( + self, database: SQLDatabase, /, name: Optional[str] = None, **default_values + ) -> "MySQLFunction": + from structures.engines.mysql.database import MySQLFunction + + id = MySQLContext.get_temporary_id(database.functions) + + if name is None: + name = f"function_{id}" + + return MySQLFunction( + id=id, + name=name, + database=database, + parameters=default_values.get("parameters", ""), + returns=default_values.get("returns", "INT"), + deterministic=default_values.get("deterministic", False), sql=default_values.get("sql", ""), ) - def build_empty_trigger(self, database: SQLDatabase, /, name: Optional[str] = None, **default_values) -> MySQLTrigger: + def build_empty_procedure( + self, database: SQLDatabase, /, name: Optional[str] = None, **default_values + ): + raise NotImplementedError("MySQL Procedure not implemented yet") + + def build_empty_trigger( + self, database: SQLDatabase, /, name: Optional[str] = None, **default_values + ) -> MySQLTrigger: id = MySQLContext.get_temporary_id(database.triggers) if name is None: - name = _(f"Trigger{str(id * -1):03}") + name = f"trigger_{id}" return MySQLTrigger( id=id, name=name, database=database, - sql=default_values.get("sql", ""), + statement=default_values.get("statement", ""), ) diff --git a/structures/engines/mysql/database.py b/structures/engines/mysql/database.py index 27668c6..e84de88 100644 --- a/structures/engines/mysql/database.py +++ b/structures/engines/mysql/database.py @@ -6,6 +6,7 @@ from structures.helpers import merge_original_current from structures.engines.context import QUERY_LOGS from structures.engines.database import ( + SQLCheck, SQLColumn, SQLDatabase, SQLForeignKey, @@ -36,7 +37,7 @@ def raw_create(self) -> str: columns_and_indexes = columns + indexes return f""" - CREATE TABLE {self.database.sql_safe_name}.{self.sql_safe_name} ( + CREATE TABLE {self.fully_qualified_name} ( {', '.join(columns_and_indexes)} ) COLLATE='{self.collation_name}' @@ -44,8 +45,8 @@ def raw_create(self) -> str: """ def alter_auto_increment(self, auto_increment: int): - sql = f"ALTER TABLE `{self.database.name}`.`{self.name}` AUTO_INCREMENT {auto_increment};" - self.database.context.execute(sql) + statement = f"ALTER TABLE `{self.database.name}`.`{self.name}` AUTO_INCREMENT {auto_increment};" + self.database.context.execute(statement) return True @@ -56,14 +57,14 @@ def alter_collation(self, convert: bool = True): return self.database.context.execute(f"""ALTER TABLE `{self.database.name}`.`{self.name}` {charset} COLLATE {self.collation_name};""") def alter_engine(self, engine: str): - sql = f"ALTER TABLE `{self.database.name}`.`{self.name}` ENGINE {engine};" - self.database.context.execute(sql) + statement = f"ALTER TABLE `{self.database.name}`.`{self.name}` ENGINE {engine};" + self.database.context.execute(statement) return True def rename(self, table: Self, new_name: str) -> bool: - sql = f"ALTER TABLE `{self.database.name}`.`{table.name}` RENAME TO `{new_name}`;" - self.database.context.execute(sql) + statement = f"ALTER TABLE `{self.database.name}`.`{table.name}` RENAME TO `{new_name}`;" + self.database.context.execute(statement) return True @@ -152,6 +153,21 @@ def drop(self) -> bool: return self.database.context.execute(f"DROP TABLE `{self.database.name}`.`{self.name}`") +@dataclasses.dataclass(eq=False) +class MySQLCheck(SQLCheck): + def create(self) -> bool: + statement = f"ALTER TABLE {self.table.fully_qualified_name} ADD CONSTRAINT {self.quoted_name} CHECK ({self.expression})" + return self.table.database.context.execute(statement) + + def drop(self) -> bool: + statement = f"ALTER TABLE {self.table.fully_qualified_name} DROP CONSTRAINT {self.quoted_name}" + return self.table.database.context.execute(statement) + + def alter(self) -> bool: + self.drop() + return self.create() + + @dataclasses.dataclass(eq=False) class MySQLColumn(SQLColumn): set: Optional[list[str]] = None @@ -161,15 +177,15 @@ class MySQLColumn(SQLColumn): after: Optional[str] = None def add(self) -> bool: - sql = f"ALTER TABLE `{self.table.database.name}`.`{self.table.name}` ADD COLUMN {MySQLColumnBuilder(self)}" + statement = f"ALTER TABLE `{self.table.database.name}`.`{self.table.name}` ADD COLUMN {MySQLColumnBuilder(self)}" if hasattr(self, "after") and self.after: - sql += f" AFTER `{self.after}`" + statement += f" AFTER `{self.after}`" - return self.table.database.context.execute(sql) + return self.table.database.context.execute(statement) def modify(self, current: Self): - sql = f"ALTER TABLE `{self.table.database.name}`.`{self.table.name}` MODIFY COLUMN {MySQLColumnBuilder(current)}" - self.table.database.context.execute(sql) + statement = f"ALTER TABLE `{self.table.database.name}`.`{self.table.name}` MODIFY COLUMN {MySQLColumnBuilder(current)}" + self.table.database.context.execute(statement) def rename(self, new_name: str) -> bool: return self.table.database.context.execute(f"ALTER TABLE `{self.table.database.name}`.`{self.table.name}` RENAME COLUMN `{self.name}` TO `{new_name}`") @@ -190,7 +206,7 @@ def drop(self) -> bool: if self.type == MySQLIndexType.PRIMARY: return self.table.database.context.execute(f"ALTER TABLE `{self.table.database.name}`.`{self.table.name}` DROP PRIMARY KEY") - return self.table.database.context.execute(f"DROP INDEX `{self.sql_safe_name}` ON `{self.table.database.sql_safe_name}`.`{self.table.sql_safe_name}`") + return self.table.database.context.execute(f"DROP INDEX {self.quoted_name} ON {self.table.fully_qualified_name}") def modify(self, new: Self): self.drop() @@ -202,7 +218,7 @@ def modify(self, new: Self): class MySQLForeignKey(SQLForeignKey): def create(self) -> bool: query = [ - f"ALTER TABLE {self.table.database.sql_safe_name}.{self.table.sql_safe_name} ADD CONSTRAINT {self.sql_safe_name}", + f"ALTER TABLE {self.table.fully_qualified_name} ADD CONSTRAINT {self.quoted_name}", f"FOREIGN KEY({', '.join(self.columns)})", f"REFERENCES `{self.reference_table}`({', '.join(self.reference_columns)})", ] @@ -217,8 +233,8 @@ def create(self) -> bool: def drop(self) -> bool: return self.table.database.context.execute(f""" - ALTER TABLE {self.table.database.sql_safe_name}.{self.table.sql_safe_name} - DROP FOREIGN KEY {self.sql_safe_name} + ALTER TABLE {self.table.fully_qualified_name} + DROP FOREIGN KEY {self.quoted_name} """) def modify(self, new: Self): @@ -247,14 +263,14 @@ def raw_insert_record(self) -> str: if not columns_values: raise AssertionError("No columns values") - return f"""INSERT INTO {self.table.database.sql_safe_name}.{self.table.sql_safe_name} ({', '.join(columns_values.keys())}) VALUES ({', '.join(columns_values.values())})""" + return f"""INSERT INTO {self.table.fully_qualified_name} ({', '.join(columns_values.keys())}) VALUES ({', '.join(columns_values.values())})""" def raw_update_record(self) -> Optional[str]: identifier_columns = self._get_identifier_columns() identifier_conditions = " AND ".join([f"""`{identifier_name}` = {identifier_value}""" for identifier_name, identifier_value in identifier_columns.items()]) - sql_select = f"SELECT * FROM {self.table.database.sql_safe_name}.{self.table.sql_safe_name} WHERE {identifier_conditions}" + sql_select = f"SELECT * FROM {self.table.fully_qualified_name} WHERE {identifier_conditions}" self.table.database.context.execute(sql_select) if not (existing_record := self.table.database.context.fetchone()): @@ -313,21 +329,24 @@ def delete(self) -> bool: class MySQLView(SQLView): def create(self) -> bool: - return self.database.context.execute(f"CREATE VIEW `{self.name}` AS {self.sql}") + self.database.context.set_database(self.database) + return self.database.context.execute(f"CREATE VIEW {self.fully_qualified_name} AS {self.statement}") def drop(self) -> bool: - return self.database.context.execute(f"DROP VIEW IF EXISTS `{self.name}`") + self.database.context.set_database(self.database) + return self.database.context.execute(f"DROP VIEW IF EXISTS {self.fully_qualified_name}") def alter(self) -> bool: - return self.database.context.execute(f"CREATE OR REPLACE VIEW `{self.name}` AS {self.sql}") + self.database.context.set_database(self.database) + return self.database.context.execute(f"CREATE OR REPLACE VIEW {self.fully_qualified_name} AS {self.statement}") class MySQLTrigger(SQLTrigger): def create(self) -> bool: - return self.database.context.execute(f"CREATE TRIGGER `{self.name}` {self.sql}") + return self.database.context.execute(f"CREATE TRIGGER {self.fully_qualified_name} {self.statement}") def drop(self) -> bool: - return self.database.context.execute(f"DROP TRIGGER IF EXISTS `{self.name}`") + return self.database.context.execute(f"DROP TRIGGER IF EXISTS {self.fully_qualified_name}") def alter(self) -> bool: self.drop() @@ -344,7 +363,7 @@ class MySQLFunction(SQLFunction): def create(self) -> bool: deterministic = "DETERMINISTIC" if self.deterministic else "NOT DETERMINISTIC" query = f""" - CREATE FUNCTION `{self.name}`({self.parameters}) + CREATE FUNCTION {self.fully_qualified_name}({self.parameters}) RETURNS {self.returns} {deterministic} BEGIN @@ -354,7 +373,7 @@ def create(self) -> bool: return self.database.context.execute(query) def drop(self) -> bool: - return self.database.context.execute(f"DROP FUNCTION IF EXISTS `{self.name}`") + return self.database.context.execute(f"DROP FUNCTION IF EXISTS {self.fully_qualified_name}") def alter(self) -> bool: self.drop() diff --git a/structures/engines/mysql/specification.yaml b/structures/engines/mysql/specification.yaml new file mode 100644 index 0000000..945219b --- /dev/null +++ b/structures/engines/mysql/specification.yaml @@ -0,0 +1,8956 @@ +schema_version: 1 +engine: mysql +documentation: + purpose: MySQL vocabulary and version deltas. + fields: + - schema_version: Specification schema version. + - engine: Engine name. + - common.keywords: Engine common keywords. + - common.functions: Engine common function specs. + - versions..keywords_remove: Keywords to remove for that major version. + - versions..functions_remove: Function names to remove for that major version. + notes: + - Runtime selection uses exact major match, else highest configured major <= server major. +example: + schema_version: 1 + engine: mysql + common: + keywords: + - STRAIGHT_JOIN + functions: + - name: ABS + summary: Returns absolute value. + versions: + '9': + functions_remove: + - OLD_FUNCTION +common: + keywords: [] + functions: + - name: ABS + category_id: numeric_functions + category_label: Numeric Functions + signature: + display: ABS(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the absolute value of X, or NULL if X is NULL. + description: 'Returns the absolute value of X, or NULL if X is NULL. The result + type is derived from the argument type. An implication of this is that ABS(-9223372036854775808) + produces an error because the result cannot be stored in a signed BIGINT value. + URL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html' + examples: [] + - name: ACOS + category_id: numeric_functions + category_label: Numeric Functions + signature: + display: ACOS(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the arc cosine of X, that is, the value whose cosine is X. + description: 'Returns the arc cosine of X, that is, the value whose cosine is + X. Returns NULL if X is not in the range -1 to 1, or if X is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html' + examples: [] + - name: ADDDATE + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: ADDDATE(date,INTERVAL expr unit) + args: + - name: date + optional: false + type: any + - name: INTERVAL expr unit + optional: false + type: any + tags: [] + aliases: [] + summary: When invoked with the INTERVAL form of the second argument, ADDDATE() + description: "When invoked with the INTERVAL form of the second argument, ADDDATE()\n\ + is a synonym for DATE_ADD(). The related function SUBDATE() is a\nsynonym for\ + \ DATE_SUB(). For information on the INTERVAL unit argument,\nsee\nhttps://dev.mysql.com/doc/refman/8.3/en/expressions.html#temporal-inter\n\ + vals.\n\nmysql> SELECT DATE_ADD('2008-01-02', INTERVAL 31 DAY);\n ->\ + \ '2008-02-02'\nmysql> SELECT ADDDATE('2008-01-02', INTERVAL 31 DAY);\n \ + \ -> '2008-02-02'\n\nWhen invoked with the days form of the second argument,\ + \ MySQL treats it\nas an integer number of days to be added to expr.\n\nURL:\ + \ https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html" + examples: [] + - name: ADDTIME + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: ADDTIME(expr1,expr2) + args: + - name: expr1 + optional: false + type: any + - name: expr2 + optional: false + type: any + tags: [] + aliases: [] + summary: ADDTIME() adds expr2 to expr1 and returns the result. + description: 'ADDTIME() adds expr2 to expr1 and returns the result. expr1 is a + time or datetime expression, and expr2 is a time expression. Returns NULL if + expr1or expr2 is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: AES_DECRYPT + category_id: encryption_functions + category_label: Encryption Functions + signature: + display: AES_DECRYPT(crypt_str,key_str[,init_vector][,kdf_name][,salt][,info + | iterations]) + args: + - name: crypt_str + optional: false + type: any + - name: key_str[ + optional: false + type: any + - name: init_vector][ + optional: false + type: any + - name: kdf_name][ + optional: false + type: any + - name: salt][ + optional: false + type: any + - name: info | iterations] + optional: false + type: any + tags: [] + aliases: [] + summary: This function decrypts data using the official AES (Advanced Encryption + description: 'This function decrypts data using the official AES (Advanced Encryption + Standard) algorithm. For more information, see the description of AES_ENCRYPT(). + Statements that use AES_DECRYPT() are unsafe for statement-based replication. + URL: https://dev.mysql.com/doc/refman/8.3/en/encryption-functions.html' + examples: [] + - name: AES_ENCRYPT + category_id: encryption_functions + category_label: Encryption Functions + signature: + display: AES_ENCRYPT(str,key_str[,init_vector][,kdf_name][,salt][,info | iterations]) + args: + - name: str + optional: false + type: any + - name: key_str[ + optional: false + type: any + - name: init_vector][ + optional: false + type: any + - name: kdf_name][ + optional: false + type: any + - name: salt][ + optional: false + type: any + - name: info | iterations] + optional: false + type: any + tags: [] + aliases: [] + summary: AES_ENCRYPT() and AES_DECRYPT() implement encryption and decryption of + description: "AES_ENCRYPT() and AES_DECRYPT() implement encryption and decryption\ + \ of\ndata using the official AES (Advanced Encryption Standard) algorithm,\n\ + previously known as \"Rijndael.\" The AES standard permits various key\nlengths.\ + \ By default these functions implement AES with a 128-bit key\nlength. Key lengths\ + \ of 196 or 256 bits can be used, as described later.\nThe key length is a trade\ + \ off between performance and security.\n\nAES_ENCRYPT() encrypts the string\ + \ str using the key string key_str, and\nreturns a binary string containing\ + \ the encrypted output. AES_DECRYPT()\ndecrypts the encrypted string crypt_str\ + \ using the key string key_str,\nand returns the original (binary) string in\ + \ hexadecimal format. (To\nobtain the string as plaintext, cast the result to\ + \ CHAR. Alternatively,\nstart the mysql client with --skip-binary-as-hex to\ + \ cause all binary\nvalues to be displayed as text.) If either function argument\ + \ is NULL,\nthe function returns NULL. If AES_DECRYPT() detects invalid data\ + \ or\nincorrect padding, it returns NULL. However, it is possible for\nAES_DECRYPT()\ + \ to return a non-NULL value (possibly garbage) if the\ninput data or the key\ + \ is invalid.\n\nThese functions support the use of a key derivation function\ + \ (KDF) to\ncreate a cryptographically strong secret key from the information\n\ + passed in key_str. The derived key is used to encrypt and decrypt the\ndata,\ + \ and it remains in the MySQL Server instance and is not accessible\nto users.\ + \ Using a KDF is highly recommended, as it provides better\nsecurity than specifying\ + \ your own premade key or deriving it by a\nsimpler method as you use the function.\ + \ The functions support HKDF\n(available from OpenSSL 1.1.0), for which you\ + \ can specify an optional\nsalt and context-specific information to include\ + \ in the keying\nmaterial, and PBKDF2 (available from OpenSSL 1.0.2), for which\ + \ you can\nspecify an optional salt and set the number of iterations used to\n\ + produce the key.\n\nAES_ENCRYPT() and AES_DECRYPT() permit control of the block\ + \ encryption\nmode. The block_encryption_mode system variable controls the mode\ + \ for\nblock-based encryption algorithms. Its default value is aes-128-ecb,\n\ + which signifies encryption using a key length of 128 bits and ECB mode.\nFor\ + \ a description of the permitted values of this variable, see\nhttps://dev.mysql.com/doc/refman/8.3/en/server-system-variables.html.\n\ + The optional init_vector argument is used to provide an initialization\nvector\ + \ for block encryption modes that require it.\n\nStatements that use AES_ENCRYPT()\ + \ or AES_DECRYPT() are unsafe for\nstatement-based replication.\n\nIf AES_ENCRYPT()\ + \ is invoked from within the mysql client, binary\nstrings display using hexadecimal\ + \ notation, depending on the value of\nthe --binary-as-hex. For more information\ + \ about that option, see\nhttps://dev.mysql.com/doc/refman/8.3/en/mysql.html.\n\ + \nThe arguments for the AES_ENCRYPT() and AES_DECRYPT() functions are as\n ..." + examples: [] + - name: ANY_VALUE + category_id: miscellaneous_functions + category_label: Miscellaneous Functions + signature: + display: ANY_VALUE(arg) + args: + - name: arg + optional: false + type: any + tags: [] + aliases: [] + summary: This function is useful for GROUP BY queries when the + description: "This function is useful for GROUP BY queries when the\nONLY_FULL_GROUP_BY\ + \ SQL mode is enabled, for cases when MySQL rejects a\nquery that you know is\ + \ valid for reasons that MySQL cannot determine.\nThe function return value\ + \ and type are the same as the return value and\ntype of its argument, but the\ + \ function result is not checked for the\nONLY_FULL_GROUP_BY SQL mode.\n\nFor\ + \ example, if name is a nonindexed column, the following query fails\nwith ONLY_FULL_GROUP_BY\ + \ enabled:\n\nmysql> SELECT name, address, MAX(age) FROM t GROUP BY name;\n\ + ERROR 1055 (42000): Expression #2 of SELECT list is not in GROUP\nBY clause\ + \ and contains nonaggregated column 'mydb.t.address' which\nis not functionally\ + \ dependent on columns in GROUP BY clause; this\nis incompatible with sql_mode=only_full_group_by\n\ + \nThe failure occurs because address is a nonaggregated column that is\nneither\ + \ named among GROUP BY columns nor functionally dependent on\nthem. As a result,\ + \ the address value for rows within each name group is\nnondeterministic. There\ + \ are multiple ways to cause MySQL to accept the\nquery:\n\no Alter the table\ + \ to make name a primary key or a unique NOT NULL\n column. This enables MySQL\ + \ to determine that address is functionally\n dependent on name; that is, address\ + \ is uniquely determined by name.\n (This technique is inapplicable if NULL\ + \ must be permitted as a valid\n name value.)\n\no Use ANY_VALUE() to refer\ + \ to address:\n\nSELECT name, ANY_VALUE(address), MAX(age) FROM t GROUP BY name;\n\ + \n In this case, MySQL ignores the nondeterminism of address values\n within\ + \ each name group and accepts the query. This may be useful if\n you simply\ + \ do not care which value of a nonaggregated column is\n chosen for each group.\ + \ ANY_VALUE() is not an aggregate function,\n unlike functions such as SUM()\ + \ or COUNT(). It simply acts to suppress\n the test for nondeterminism.\n\n\ + o Disable ONLY_FULL_GROUP_BY. This is equivalent to using ANY_VALUE()\n with\ + \ ONLY_FULL_GROUP_BY enabled, as described in the previous item.\n\nANY_VALUE()\ + \ is also useful if functional dependence exists between\ncolumns but MySQL\ + \ cannot determine it. The following query is valid\nbecause age is functionally\ + \ dependent on the grouping column age-1, but\nMySQL cannot tell that and rejects\ + \ the query with ONLY_FULL_GROUP_BY\nenabled:\n\nSELECT age FROM t GROUP BY\ + \ age-1;\n\n ..." + examples: [] + - name: ASCII + category_id: string_functions + category_label: String Functions + signature: + display: ASCII(str) + args: + - name: str + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the numeric value of the leftmost character of the string str. + description: 'Returns the numeric value of the leftmost character of the string + str. Returns 0 if str is the empty string. Returns NULL if str is NULL. ASCII() + works for 8-bit characters. URL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html' + examples: [] + - name: ASIN + category_id: numeric_functions + category_label: Numeric Functions + signature: + display: ASIN(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the arc sine of X, that is, the value whose sine is X. + description: 'Returns the arc sine of X, that is, the value whose sine is X. Returns + NULL if X is not in the range -1 to 1, or if X is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html' + examples: [] + - name: ASYMMETRIC_DECRYPT + category_id: enterprise_encryption_functions + category_label: Enterprise Encryption Functions + signature: + display: ASYMMETRIC_DECRYPT(algorithm, data_str, priv_key_str) + args: + - name: algorithm + optional: false + type: any + - name: data_str + optional: false + type: any + - name: priv_key_str + optional: false + type: any + tags: [] + aliases: [] + summary: Decrypts an encrypted string using the given algorithm and key string, + description: 'Decrypts an encrypted string using the given algorithm and key string, + and returns the resulting plaintext as a binary string. If decryption fails, + the result is NULL. For the legacy version of this function in use before MySQL + 8.0.29, see https://dev.mysql.com/doc/refman/8.3/en/enterprise-encryption-functions + -legacy.html. By default, the component_enterprise_encryption function assumes + that encrypted text uses the RSAES-OAEP padding scheme. The function supports + decryption for content encrypted by the legacy openssl_udf shared library functions + if the system variable enterprise_encryption.rsa_support_legacy_padding is set + to ON (the default is OFF). When ON is set, the function also supports the RSAES-PKCS1-v1_5 + padding scheme, as used by the legacy openssl_udf shared library functions. + When OFF is set, content encrypted by the legacy functions cannot be decrypted, + and the function returns null output for such content. algorithm is the encryption + algorithm used to create the key. The supported algorithm value is ''RSA''. + data_str is the encrypted string to decrypt, which was encrypted with asymmetric_encrypt(). + priv_key_str is a valid PEM encoded RSA private key. For successful decryption, + the key string must correspond to the public key string used with asymmetric_encrypt() + to produce the encrypted string. The asymmetric_encrypt() component function + only supports encryption using a public key, so decryption takes place with + the corresponding private key. For a usage example, see the description of asymmetric_encrypt(). + URL: https://dev.mysql.com/doc/refman/8.3/en/enterprise-encryption-functions.html' + examples: [] + - name: ASYMMETRIC_DERIVE + category_id: enterprise_encryption_functions + category_label: Enterprise Encryption Functions + signature: + display: ASYMMETRIC_DERIVE(pub_key_str, priv_key_str) + args: + - name: pub_key_str + optional: false + type: any + - name: priv_key_str + optional: false + type: any + tags: [] + aliases: [] + summary: Derives a symmetric key using the private key of one party and the + description: 'Derives a symmetric key using the private key of one party and the + public key of another, and returns the resulting key as a binary string. If + key derivation fails, the result is NULL. pub_key_str and priv_key_str are valid + PEM encoded key strings that were created using the DH algorithm. Suppose that + you have two pairs of public and private keys: SET @dhp = create_dh_parameters(1024); + SET @priv1 = create_asymmetric_priv_key(''DH'', @dhp); SET @pub1 = create_asymmetric_pub_key(''DH'', + @priv1); SET @priv2 = create_asymmetric_priv_key(''DH'', @dhp); SET @pub2 = + create_asymmetric_pub_key(''DH'', @priv2); Suppose further that you use the + private key from one pair and the public key from the other pair to create a + symmetric key string. Then this symmetric key identity relationship holds: asymmetric_derive(@pub1, + @priv2) = asymmetric_derive(@pub2, @priv1) This example requires DH private/public + keys as inputs, created using a shared symmetric secret. Create the secret by + passing the key length to create_dh_parameters(), then pass the secret as the + "key length" to create_asymmetric_priv_key(). URL: https://dev.mysql.com/doc/refman/8.3/en/enterprise-encryption-functions-legacy.html' + examples: [] + - name: ASYMMETRIC_ENCRYPT + category_id: enterprise_encryption_functions + category_label: Enterprise Encryption Functions + signature: + display: ASYMMETRIC_ENCRYPT(algorithm, data_str, pub_key_str) + args: + - name: algorithm + optional: false + type: any + - name: data_str + optional: false + type: any + - name: pub_key_str + optional: false + type: any + tags: [] + aliases: [] + summary: Encrypts a string using the given algorithm and key string, and returns + description: 'Encrypts a string using the given algorithm and key string, and + returns the resulting ciphertext as a binary string. If encryption fails, the + result is NULL. For the legacy version of this function in use before MySQL + 8.0.29, see https://dev.mysql.com/doc/refman/8.3/en/enterprise-encryption-functions + -legacy.html. algorithm is the encryption algorithm used to create the key. + The supported algorithm value is ''RSA''. data_str is the string to encrypt. + The length of this string cannot be greater than the key string length in bytes, + minus 42 (to account for the padding). pub_key_str is a valid PEM encoded RSA + public key. The asymmetric_encrypt() component function only supports encryption + using a public key. To recover the original unencrypted string, pass the encrypted + string to asymmetric_decrypt(), along with the other part of the key pair used + for encryption, as in the following example: URL: https://dev.mysql.com/doc/refman/8.3/en/enterprise-encryption-functions.html' + examples: [] + - name: ASYMMETRIC_SIGN + category_id: enterprise_encryption_functions + category_label: Enterprise Encryption Functions + signature: + display: ASYMMETRIC_SIGN(algorithm, text, priv_key_str, digest_type) + args: + - name: algorithm + optional: false + type: any + - name: text + optional: false + type: any + - name: priv_key_str + optional: false + type: any + - name: digest_type + optional: false + type: any + tags: [] + aliases: [] + summary: Signs a digest string or data string using a private key, and returns + description: 'Signs a digest string or data string using a private key, and returns + the signature as a binary string. If signing fails, the result is NULL. For + the legacy version of this function in use before MySQL 8.0.29, see https://dev.mysql.com/doc/refman/8.3/en/enterprise-encryption-functions + -legacy.html. algorithm is the encryption algorithm used to create the key. + The supported algorithm value is ''RSA''. text is a data string or digest string. + The function accepts digests but does not require them, as it is also capable + of handling data strings of an arbitrary length. A digest string can be generated + by calling create_digest(). priv_key_str is the private key string to use for + signing the digest string. It must be a valid PEM encoded RSA private key. digest_type + is the algorithm to be used to sign the data. The supported digest_type values + are ''SHA224'', ''SHA256'', ''SHA384'', and ''SHA512'' when OpenSSL 1.0.1 is + in use. If OpenSSL 1.1.1 is in use, the additional digest_type values ''SHA3-224'', + ''SHA3-256'', ''SHA3-384'', and ''SHA3-512'' are available. For a usage example, + see the description of asymmetric_verify(). URL: https://dev.mysql.com/doc/refman/8.3/en/enterprise-encryption-functions.html' + examples: [] + - name: ASYMMETRIC_VERIFY + category_id: enterprise_encryption_functions + category_label: Enterprise Encryption Functions + signature: + display: ASYMMETRIC_VERIFY(algorithm, text, sig_str, pub_key_str, digest_type) + args: + - name: algorithm + optional: false + type: any + - name: text + optional: false + type: any + - name: sig_str + optional: false + type: any + - name: pub_key_str + optional: false + type: any + - name: digest_type + optional: false + type: any + tags: [] + aliases: [] + summary: Verifies whether the signature string matches the digest string, and + description: 'Verifies whether the signature string matches the digest string, + and returns 1 or 0 to indicate whether verification succeeded or failed. If + verification fails, the result is NULL. For the legacy version of this function + in use before MySQL 8.0.29, see https://dev.mysql.com/doc/refman/8.3/en/enterprise-encryption-functions + -legacy.html. By default, the component_enterprise_encryption function assumes + that signatures use the RSASSA-PSS signature scheme. The function supports verification + for signatures produced by the legacy openssl_udf shared library functions if + the system variable enterprise_encryption.rsa_support_legacy_padding is set + to ON (the default is OFF). When ON is set, the function also supports the RSASSA-PKCS1-v1_5 + signature scheme, as used by the legacy openssl_udf shared library functions. + When OFF is set, signatures produced by the legacy functions cannot be verified, + and the function returns null output for such content. algorithm is the encryption + algorithm used to create the key. The supported algorithm value is ''RSA''. + text is a data string or digest string. The component function accepts digests + but does not require them, as it is also capable of handling data strings of + an arbitrary length. A digest string can be generated by calling create_digest(). + sig_str is the signature string to be verified. A signature string can be generated + by calling asymmetric_sign(). pub_key_str is the public key string of the signer. + It corresponds to the private key passed to asymmetric_sign() to generate the + signature string. It must be a valid PEM encoded RSA public key. digest_type + is the algorithm that was used to sign the data. The supported digest_type values + are ''SHA224'', ''SHA256'', ''SHA384'', and ''SHA512'' when OpenSSL 1.0.1 is + in use. If OpenSSL 1.1.1 is in use, the additional digest_type values ''SHA3-224'', + ''SHA3-256'', ''SHA3-384'', and ''SHA3-512'' are available. URL: https://dev.mysql.com/doc/refman/8.3/en/enterprise-encryption-functions.html' + examples: [] + - name: ATAN + category_id: numeric_functions + category_label: Numeric Functions + signature: + display: ATAN(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the arc tangent of X, that is, the value whose tangent is X. + description: 'Returns the arc tangent of X, that is, the value whose tangent is + X. Returns NULL if X is NULL URL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html' + examples: [] + - name: ATAN2 + category_id: numeric_functions + category_label: Numeric Functions + signature: + display: ATAN2(Y,X) + args: + - name: Y + optional: false + type: any + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the arc tangent of the two variables X and Y. + description: 'Returns the arc tangent of the two variables X and Y. It is similar + to calculating the arc tangent of Y / X, except that the signs of both arguments + are used to determine the quadrant of the result. Returns NULL if X or Y is + NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html' + examples: [] + - name: AVG + category_id: aggregate_functions_and_modifiers + category_label: Aggregate Functions and Modifiers + signature: + display: AVG([DISTINCT] expr) + args: + - name: '[DISTINCT] expr' + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the average value of expr. + description: 'Returns the average value of expr. The DISTINCT option can be used + to return the average of the distinct values of expr. If there are no matching + rows, AVG() returns NULL. The function also returns NULL if expr is NULL. This + function executes as a window function if over_clause is present. over_clause + is as described in https://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html; + it cannot be used with DISTINCT. URL: https://dev.mysql.com/doc/refman/8.3/en/aggregate-functions.html' + examples: [] + - name: BENCHMARK + category_id: information_functions + category_label: Information Functions + signature: + display: BENCHMARK(count,expr) + args: + - name: count + optional: false + type: any + - name: expr + optional: false + type: any + tags: [] + aliases: [] + summary: The BENCHMARK() function executes the expression expr repeatedly count + description: 'The BENCHMARK() function executes the expression expr repeatedly + count times. It may be used to time how quickly MySQL processes the expression. + The result value is 0, or NULL for inappropriate arguments such as a NULL or + negative repeat count. The intended use is from within the mysql client, which + reports query execution times: URL: https://dev.mysql.com/doc/refman/8.3/en/information-functions.html' + examples: [] + - name: BIGINT + category_id: data_types + category_label: Data Types + signature: + display: BIGINT(M) + args: + - name: M + optional: false + type: any + tags: [] + aliases: [] + summary: A large integer. + description: 'A large integer. The signed range is -9223372036854775808 to 9223372036854775807. + The unsigned range is 0 to 18446744073709551615. SERIAL is an alias for BIGINT + UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE. URL: https://dev.mysql.com/doc/refman/8.3/en/numeric-type-syntax.html' + examples: [] + - name: BIN + category_id: string_functions + category_label: String Functions + signature: + display: BIN(N) + args: + - name: N + optional: false + type: any + tags: [] + aliases: [] + summary: Returns a string representation of the binary value of N, where N is + a + description: 'Returns a string representation of the binary value of N, where + N is a longlong (BIGINT) number. This is equivalent to CONV(N,10,2). Returns + NULL if N is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html' + examples: [] + - name: BINARY + category_id: data_types + category_label: Data Types + signature: + display: BINARY(M) + args: + - name: M + optional: false + type: any + tags: [] + aliases: [] + summary: The BINARY type is similar to the CHAR type, but stores binary byte + description: 'The BINARY type is similar to the CHAR type, but stores binary byte + strings rather than nonbinary character strings. An optional length M represents + the column length in bytes. If omitted, M defaults to 1. URL: https://dev.mysql.com/doc/refman/8.3/en/string-type-syntax.html' + examples: [] + - name: BIN_TO_UUID + category_id: miscellaneous_functions + category_label: Miscellaneous Functions + signature: + display: BIN_TO_UUID(binary_uuid) + args: + - name: binary_uuid + optional: false + type: any + tags: [] + aliases: [] + summary: BIN_TO_UUID() is the inverse of UUID_TO_BIN(). + description: "BIN_TO_UUID() is the inverse of UUID_TO_BIN(). It converts a binary\n\ + UUID to a string UUID and returns the result. The binary value should\nbe a\ + \ UUID as a VARBINARY(16) value. The return value is a string of\nfive hexadecimal\ + \ numbers separated by dashes. (For details about this\nformat, see the UUID()\ + \ function description.) If the UUID argument is\nNULL, the return value is\ + \ NULL. If any argument is invalid, an error\noccurs.\n\nBIN_TO_UUID() takes\ + \ one or two arguments:\n\no The one-argument form takes a binary UUID value.\ + \ The UUID value is\n assumed not to have its time-low and time-high parts\ + \ swapped. The\n string result is in the same order as the binary argument.\n\ + \no The two-argument form takes a binary UUID value and a swap-flag\n value:\n\ + \n o If swap_flag is 0, the two-argument form is equivalent to the\n one-argument\ + \ form. The string result is in the same order as the\n binary argument.\n\ + \n o If swap_flag is 1, the UUID value is assumed to have its time-low\n \ + \ and time-high parts swapped. These parts are swapped back to their\n original\ + \ position in the result value.\n\nFor usage examples and information about\ + \ time-part swapping, see the\nUUID_TO_BIN() function description.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/miscellaneous-functions.html" + examples: [] + - name: BIT + category_id: data_types + category_label: Data Types + signature: + display: BIT(M) + args: + - name: M + optional: false + type: any + tags: [] + aliases: [] + summary: A bit-value type. + description: 'A bit-value type. M indicates the number of bits per value, from + 1 to 64. The default is 1 if M is omitted. URL: https://dev.mysql.com/doc/refman/8.3/en/numeric-type-syntax.html' + examples: [] + - name: BIT_AND + category_id: aggregate_functions_and_modifiers + category_label: Aggregate Functions and Modifiers + signature: + display: BIT_AND(expr) + args: + - name: expr + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the bitwise AND of all bits in expr. + description: "Returns the bitwise AND of all bits in expr.\n\nThe result type\ + \ depends on whether the function argument values are\nevaluated as binary strings\ + \ or numbers:\n\no Binary-string evaluation occurs when the argument values\ + \ have a\n binary string type, and the argument is not a hexadecimal literal,\n\ + \ bit literal, or NULL literal. Numeric evaluation occurs otherwise,\n with\ + \ argument value conversion to unsigned 64-bit integers as\n necessary.\n\n\ + o Binary-string evaluation produces a binary string of the same length\n as\ + \ the argument values. If argument values have unequal lengths, an\n ER_INVALID_BITWISE_OPERANDS_SIZE\n\ + \ (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n \ + \ .html#error_er_invalid_bitwise_operands_size) error occurs. If the\n argument\ + \ size exceeds 511 bytes, an\n ER_INVALID_BITWISE_AGGREGATE_OPERANDS_SIZE\n\ + \ (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n \ + \ .html#error_er_invalid_bitwise_aggregate_operands_size) error occurs.\n Numeric\ + \ evaluation produces an unsigned 64-bit integer.\n\nIf there are no matching\ + \ rows, BIT_AND() returns a neutral value (all\nbits set to 1) having the same\ + \ length as the argument values.\n\nNULL values do not affect the result unless\ + \ all values are NULL. In\nthat case, the result is a neutral value having the\ + \ same length as the\nargument values.\n\nFor more information discussion about\ + \ argument evaluation and result\ntypes, see the introductory discussion in\n\ + https://dev.mysql.com/doc/refman/8.3/en/bit-functions.html.\n\nIf BIT_AND()\ + \ is invoked from within the mysql client, binary string\nresults display using\ + \ hexadecimal notation, depending on the value of\nthe --binary-as-hex. For\ + \ more information about that option, see\nhttps://dev.mysql.com/doc/refman/8.3/en/mysql.html.\n\ + \nThis function executes as a window function if over_clause is present.\nover_clause\ + \ is as described in\nhttps://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html.\n\ + \nURL: https://dev.mysql.com/doc/refman/8.3/en/aggregate-functions.html" + examples: [] + - name: BIT_LENGTH + category_id: string_functions + category_label: String Functions + signature: + display: BIT_LENGTH(str) + args: + - name: str + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the length of the string str in bits. + description: 'Returns the length of the string str in bits. Returns NULL if str + is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html' + examples: [] + - name: BIT_OR + category_id: aggregate_functions_and_modifiers + category_label: Aggregate Functions and Modifiers + signature: + display: BIT_OR(expr) + args: + - name: expr + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the bitwise OR of all bits in expr. + description: "Returns the bitwise OR of all bits in expr.\n\nThe result type depends\ + \ on whether the function argument values are\nevaluated as binary strings or\ + \ numbers:\n\no Binary-string evaluation occurs when the argument values have\ + \ a\n binary string type, and the argument is not a hexadecimal literal,\n\ + \ bit literal, or NULL literal. Numeric evaluation occurs otherwise,\n with\ + \ argument value conversion to unsigned 64-bit integers as\n necessary.\n\n\ + o Binary-string evaluation produces a binary string of the same length\n as\ + \ the argument values. If argument values have unequal lengths, an\n ER_INVALID_BITWISE_OPERANDS_SIZE\n\ + \ (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n \ + \ .html#error_er_invalid_bitwise_operands_size) error occurs. If the\n argument\ + \ size exceeds 511 bytes, an\n ER_INVALID_BITWISE_AGGREGATE_OPERANDS_SIZE\n\ + \ (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n \ + \ .html#error_er_invalid_bitwise_aggregate_operands_size) error occurs.\n Numeric\ + \ evaluation produces an unsigned 64-bit integer.\n\nIf there are no matching\ + \ rows, BIT_OR() returns a neutral value (all\nbits set to 0) having the same\ + \ length as the argument values.\n\nNULL values do not affect the result unless\ + \ all values are NULL. In\nthat case, the result is a neutral value having the\ + \ same length as the\nargument values.\n\nFor more information discussion about\ + \ argument evaluation and result\ntypes, see the introductory discussion in\n\ + https://dev.mysql.com/doc/refman/8.3/en/bit-functions.html.\n\nIf BIT_OR() is\ + \ invoked from within the mysql client, binary string\nresults display using\ + \ hexadecimal notation, depending on the value of\nthe --binary-as-hex. For\ + \ more information about that option, see\nhttps://dev.mysql.com/doc/refman/8.3/en/mysql.html.\n\ + \nThis function executes as a window function if over_clause is present.\nover_clause\ + \ is as described in\nhttps://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html.\n\ + \nURL: https://dev.mysql.com/doc/refman/8.3/en/aggregate-functions.html" + examples: [] + - name: BIT_XOR + category_id: aggregate_functions_and_modifiers + category_label: Aggregate Functions and Modifiers + signature: + display: BIT_XOR(expr) + args: + - name: expr + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the bitwise XOR of all bits in expr. + description: "Returns the bitwise XOR of all bits in expr.\n\nThe result type\ + \ depends on whether the function argument values are\nevaluated as binary strings\ + \ or numbers:\n\no Binary-string evaluation occurs when the argument values\ + \ have a\n binary string type, and the argument is not a hexadecimal literal,\n\ + \ bit literal, or NULL literal. Numeric evaluation occurs otherwise,\n with\ + \ argument value conversion to unsigned 64-bit integers as\n necessary.\n\n\ + o Binary-string evaluation produces a binary string of the same length\n as\ + \ the argument values. If argument values have unequal lengths, an\n ER_INVALID_BITWISE_OPERANDS_SIZE\n\ + \ (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n \ + \ .html#error_er_invalid_bitwise_operands_size) error occurs. If the\n argument\ + \ size exceeds 511 bytes, an\n ER_INVALID_BITWISE_AGGREGATE_OPERANDS_SIZE\n\ + \ (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n \ + \ .html#error_er_invalid_bitwise_aggregate_operands_size) error occurs.\n Numeric\ + \ evaluation produces an unsigned 64-bit integer.\n\nIf there are no matching\ + \ rows, BIT_XOR() returns a neutral value (all\nbits set to 0) having the same\ + \ length as the argument values.\n\nNULL values do not affect the result unless\ + \ all values are NULL. In\nthat case, the result is a neutral value having the\ + \ same length as the\nargument values.\n\nFor more information discussion about\ + \ argument evaluation and result\ntypes, see the introductory discussion in\n\ + https://dev.mysql.com/doc/refman/8.3/en/bit-functions.html.\n\nIf BIT_XOR()\ + \ is invoked from within the mysql client, binary string\nresults display using\ + \ hexadecimal notation, depending on the value of\nthe --binary-as-hex. For\ + \ more information about that option, see\nhttps://dev.mysql.com/doc/refman/8.3/en/mysql.html.\n\ + \nThis function executes as a window function if over_clause is present.\nover_clause\ + \ is as described in\nhttps://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html.\n\ + \nURL: https://dev.mysql.com/doc/refman/8.3/en/aggregate-functions.html" + examples: [] + - name: BLOB + category_id: data_types + category_label: Data Types + signature: + display: BLOB(M) + args: + - name: M + optional: false + type: any + tags: [] + aliases: [] + summary: "A BLOB column with a maximum length of 65,535 (216 \u2212 1) bytes." + description: "A BLOB column with a maximum length of 65,535 (216 \u2212 1) bytes.\ + \ Each\nBLOB value is stored using a 2-byte length prefix that indicates the\n\ + number of bytes in the value.\n\nAn optional length M can be given for this\ + \ type. If this is done, MySQL\ncreates the column as the smallest BLOB type\ + \ large enough to hold\nvalues M bytes long.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-type-syntax.html" + examples: [] + - name: CAST + category_id: cast_functions_and_operators + category_label: Cast Functions and Operators + signature: + display: CAST(expr AS type [ARRAY]) + args: + - name: expr AS type [ARRAY] + optional: false + type: any + tags: [] + aliases: [] + summary: CAST(timestamp_value AT TIME ZONE timezone_specifier AS + description: "CAST(timestamp_value AT TIME ZONE timezone_specifier AS\nDATETIME[(precision)])\n\ + \ntimezone_specifier: [INTERVAL] '+00:00' | 'UTC'\n\nWith CAST(expr AS type\ + \ syntax, the CAST() function takes an expression\nof any type and produces\ + \ a result value of the specified type. This\noperation may also be expressed\ + \ as CONVERT(expr, type), which is\nequivalent. If expr is NULL, CAST() returns\ + \ NULL.\n\nThese type values are permitted:\n\no BINARY[(N)]\n\n Produces a\ + \ string with the VARBINARY data type, except that when the\n expression expr\ + \ is empty (zero length), the result type is BINARY(0).\n If the optional length\ + \ N is given, BINARY(N) causes the cast to use\n no more than N bytes of the\ + \ argument. Values shorter than N bytes are\n padded with 0x00 bytes to a length\ + \ of N. If the optional length N is\n not given, MySQL calculates the maximum\ + \ length from the expression.\n If the supplied or calculated length is greater\ + \ than an internal\n threshold, the result type is BLOB. If the length is still\ + \ too long,\n the result type is LONGBLOB.\n\n For a description of how casting\ + \ to BINARY affects comparisons, see\n https://dev.mysql.com/doc/refman/8.3/en/binary-varbinary.html.\n\ + \no CHAR[(N)] [charset_info]\n\n Produces a string with the VARCHAR data type,\ + \ unless the expression\n expr is empty (zero length), in which case the result\ + \ type is\n CHAR(0). If the optional length N is given, CHAR(N) causes the\ + \ cast\n to use no more than N characters of the argument. No padding occurs\n\ + \ for values shorter than N characters. If the optional length N is not\n \ + \ given, MySQL calculates the maximum length from the expression. If\n the\ + \ supplied or calculated length is greater than an internal\n threshold, the\ + \ result type is TEXT. If the length is still too long,\n the result type is\ + \ LONGTEXT.\n\n With no charset_info clause, CHAR produces a string with the\ + \ default\n character set. To specify the character set explicitly, these\n\ + \ charset_info values are permitted:\n\n o CHARACTER SET charset_name: Produces\ + \ a string with the given\n character set.\n\n o ASCII: Shorthand for CHARACTER\ + \ SET latin1.\n\n o UNICODE: Shorthand for CHARACTER SET ucs2.\n\n ..." + examples: [] + - name: CEIL + category_id: numeric_functions + category_label: Numeric Functions + signature: + display: CEIL(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: CEIL() is a synonym for CEILING(). + description: 'CEIL() is a synonym for CEILING(). URL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html' + examples: [] + - name: CEILING + category_id: numeric_functions + category_label: Numeric Functions + signature: + display: CEILING(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the smallest integer value not less than X. + description: 'Returns the smallest integer value not less than X. Returns NULL + if X is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html' + examples: [] + - name: CHAR + category_id: data_types + category_label: Data Types + signature: + display: CHAR(M) + args: + - name: M + optional: false + type: any + tags: [] + aliases: [] + summary: collation_name] + description: 'collation_name] A fixed-length string that is always right-padded + with spaces to the specified length when stored. M represents the column length + in characters. The range of M is 0 to 255. If M is omitted, the length is 1. + *Note*: Trailing spaces are removed when CHAR values are retrieved unless the + PAD_CHAR_TO_FULL_LENGTH SQL mode is enabled. URL: https://dev.mysql.com/doc/refman/8.3/en/string-type-syntax.html' + examples: [] + - name: CHARACTER_LENGTH + category_id: string_functions + category_label: String Functions + signature: + display: CHARACTER_LENGTH(str) + args: + - name: str + optional: false + type: any + tags: [] + aliases: [] + summary: CHARACTER_LENGTH() is a synonym for CHAR_LENGTH(). + description: 'CHARACTER_LENGTH() is a synonym for CHAR_LENGTH(). URL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html' + examples: [] + - name: CHARSET + category_id: information_functions + category_label: Information Functions + signature: + display: CHARSET(str) + args: + - name: str + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the character set of the string argument, or NULL if the + description: 'Returns the character set of the string argument, or NULL if the + argument is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/information-functions.html' + examples: [] + - name: CHAR_LENGTH + category_id: string_functions + category_label: String Functions + signature: + display: CHAR_LENGTH(str) + args: + - name: str + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the length of the string str, measured in code points. + description: "Returns the length of the string str, measured in code points. A\n\ + multibyte character counts as a single code point. This means that, for\na string\ + \ containing two 3-byte characters, LENGTH() returns 6, whereas\nCHAR_LENGTH()\ + \ returns 2, as shown here:\n\nmysql> SET @dolphin:='\u6D77\u8C5A';\nQuery OK,\ + \ 0 rows affected (0.01 sec)\n\nmysql> SELECT LENGTH(@dolphin), CHAR_LENGTH(@dolphin);\n\ + +------------------+-----------------------+\n| LENGTH(@dolphin) | CHAR_LENGTH(@dolphin)\ + \ |\n+------------------+-----------------------+\n| 6 | \ + \ 2 |\n+------------------+-----------------------+\n1 row in\ + \ set (0.00 sec)\n\nCHAR_LENGTH() returns NULL if str is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html" + examples: [] + - name: COALESCE + category_id: comparison_operators + category_label: Comparison Operators + signature: + display: COALESCE(value,...) + args: + - name: value + optional: false + type: any + - name: '...' + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the first non-NULL value in the list, or NULL if there are no + description: 'Returns the first non-NULL value in the list, or NULL if there are + no non-NULL values. The return type of COALESCE() is the aggregated type of + the argument types. URL: https://dev.mysql.com/doc/refman/8.3/en/comparison-operators.html' + examples: [] + - name: COERCIBILITY + category_id: information_functions + category_label: Information Functions + signature: + display: COERCIBILITY(str) + args: + - name: str + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the collation coercibility value of the string argument. + description: 'Returns the collation coercibility value of the string argument. + URL: https://dev.mysql.com/doc/refman/8.3/en/information-functions.html' + examples: [] + - name: COLLATION + category_id: information_functions + category_label: Information Functions + signature: + display: COLLATION(str) + args: + - name: str + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the collation of the string argument. + description: 'Returns the collation of the string argument. URL: https://dev.mysql.com/doc/refman/8.3/en/information-functions.html' + examples: [] + - name: COMPRESS + category_id: encryption_functions + category_label: Encryption Functions + signature: + display: COMPRESS(string_to_compress) + args: + - name: string_to_compress + optional: false + type: any + tags: [] + aliases: [] + summary: Compresses a string and returns the result as a binary string. + description: 'Compresses a string and returns the result as a binary string. This + function requires MySQL to have been compiled with a compression library such + as zlib. Otherwise, the return value is always NULL. The return value is also + NULL if string_to_compress is NULL. The compressed string can be uncompressed + with UNCOMPRESS(). URL: https://dev.mysql.com/doc/refman/8.3/en/encryption-functions.html' + examples: [] + - name: CONCAT + category_id: string_functions + category_label: String Functions + signature: + display: CONCAT(str1,str2,...) + args: + - name: str1 + optional: false + type: any + - name: str2 + optional: false + type: any + - name: '...' + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the string that results from concatenating the arguments. + description: 'Returns the string that results from concatenating the arguments. + May have one or more arguments. If all arguments are nonbinary strings, the + result is a nonbinary string. If the arguments include any binary strings, the + result is a binary string. A numeric argument is converted to its equivalent + nonbinary string form. CONCAT() returns NULL if any argument is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html' + examples: [] + - name: CONCAT_WS + category_id: string_functions + category_label: String Functions + signature: + display: CONCAT_WS(separator,str1,str2,...) + args: + - name: separator + optional: false + type: any + - name: str1 + optional: false + type: any + - name: str2 + optional: false + type: any + - name: '...' + optional: false + type: any + tags: [] + aliases: [] + summary: CONCAT_WS() stands for Concatenate With Separator and is a special form + description: 'CONCAT_WS() stands for Concatenate With Separator and is a special + form of CONCAT(). The first argument is the separator for the rest of the arguments. + The separator is added between the strings to be concatenated. The separator + can be a string, as can the rest of the arguments. If the separator is NULL, + the result is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html' + examples: [] + - name: CONNECTION_ID + category_id: information_functions + category_label: Information Functions + signature: + display: CONNECTION_ID + args: [] + tags: [] + aliases: [] + summary: Returns the connection ID (thread ID) for the connection. + description: 'Returns the connection ID (thread ID) for the connection. Every + connection has an ID that is unique among the set of currently connected clients. + The value returned by CONNECTION_ID() is the same type of value as displayed + in the ID column of the Information Schema PROCESSLIST table, the Id column + of SHOW PROCESSLIST output, and the PROCESSLIST_ID column of the Performance + Schema threads table. URL: https://dev.mysql.com/doc/refman/8.3/en/information-functions.html' + examples: [] + - name: CONV + category_id: numeric_functions + category_label: Numeric Functions + signature: + display: CONV(N,from_base,to_base) + args: + - name: N + optional: false + type: any + - name: from_base + optional: false + type: any + - name: to_base + optional: false + type: any + tags: [] + aliases: [] + summary: Converts numbers between different number bases. + description: 'Converts numbers between different number bases. Returns a string + representation of the number N, converted from base from_base to base to_base. + Returns NULL if any argument is NULL. The argument N is interpreted as an integer, + but may be specified as an integer or a string. The minimum base is 2 and the + maximum base is 36. If from_base is a negative number, N is regarded as a signed + number. Otherwise, N is treated as unsigned. CONV() works with 64-bit precision. + CONV() returns NULL if any of its arguments are NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html' + examples: [] + - name: CONVERT + category_id: cast_functions_and_operators + category_label: Cast Functions and Operators + signature: + display: CONVERT(expr USING transcoding_name) + args: + - name: expr USING transcoding_name + optional: false + type: any + tags: [] + aliases: [] + summary: CONVERT(expr,type) + description: 'CONVERT(expr,type) CONVERT(expr USING transcoding_name) is standard + SQL syntax. The non-USING form of CONVERT() is ODBC syntax. Regardless of the + syntax used, the function returns NULL if expr is NULL. CONVERT(expr USING transcoding_name) + converts data between different character sets. In MySQL, transcoding names + are the same as the corresponding character set names. For example, this statement + converts the string ''abc'' in the default character set to the corresponding + string in the utf8mb4 character set: SELECT CONVERT(''abc'' USING utf8mb4); + CONVERT(expr, type) syntax (without USING) takes an expression and a type value + specifying a result type, and produces a result value of the specified type. + This operation may also be expressed as CAST(expr AS type), which is equivalent. + For more information, see the description of CAST(). URL: https://dev.mysql.com/doc/refman/8.3/en/cast-functions.html' + examples: [] + - name: CONVERT_TZ + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: CONVERT_TZ(dt,from_tz,to_tz) + args: + - name: dt + optional: false + type: any + - name: from_tz + optional: false + type: any + - name: to_tz + optional: false + type: any + tags: [] + aliases: [] + summary: CONVERT_TZ() converts a datetime value dt from the time zone given by + description: 'CONVERT_TZ() converts a datetime value dt from the time zone given + by from_tz to the time zone given by to_tz and returns the resulting value. + Time zones are specified as described in https://dev.mysql.com/doc/refman/8.3/en/time-zone-support.html. + This function returns NULL if any of the arguments are invalid, or if any of + them are NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: COS + category_id: numeric_functions + category_label: Numeric Functions + signature: + display: COS(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the cosine of X, where X is given in radians. + description: 'Returns the cosine of X, where X is given in radians. Returns NULL + if X is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html' + examples: [] + - name: COT + category_id: numeric_functions + category_label: Numeric Functions + signature: + display: COT(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the cotangent of X. + description: 'Returns the cotangent of X. Returns NULL if X is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html' + examples: [] + - name: COUNT + category_id: aggregate_functions_and_modifiers + category_label: Aggregate Functions and Modifiers + signature: + display: COUNT(expr) + args: + - name: expr + optional: false + type: any + tags: [] + aliases: [] + summary: Returns a count of the number of non-NULL values of expr in the rows + description: 'Returns a count of the number of non-NULL values of expr in the + rows retrieved by a SELECT statement. The result is a BIGINT value. If there + are no matching rows, COUNT() returns 0. COUNT(NULL) returns 0. This function + executes as a window function if over_clause is present. over_clause is as described + in https://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html. URL: + https://dev.mysql.com/doc/refman/8.3/en/aggregate-functions.html' + examples: [] + - name: CRC32 + category_id: numeric_functions + category_label: Numeric Functions + signature: + display: CRC32(expr) + args: + - name: expr + optional: false + type: any + tags: [] + aliases: [] + summary: Computes a cyclic redundancy check value and returns a 32-bit unsigned + description: 'Computes a cyclic redundancy check value and returns a 32-bit unsigned + value. The result is NULL if the argument is NULL. The argument is expected + to be a string and (if possible) is treated as one if it is not. URL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html' + examples: [] + - name: CREATE_ASYMMETRIC_PRIV_KEY + category_id: enterprise_encryption_functions + category_label: Enterprise Encryption Functions + signature: + display: CREATE_ASYMMETRIC_PRIV_KEY(algorithm, key_length) + args: + - name: algorithm + optional: false + type: any + - name: key_length + optional: false + type: any + tags: [] + aliases: [] + summary: Creates a private key using the given algorithm and key length, and + description: 'Creates a private key using the given algorithm and key length, + and returns the key as a binary string in PEM format. The key is in PKCS #8 + format. If key generation fails, the result is NULL. For the legacy version + of this function in use before MySQL 8.0.29, see https://dev.mysql.com/doc/refman/8.3/en/enterprise-encryption-functions + -legacy.html. algorithm is the encryption algorithm used to create the key. + The supported algorithm value is ''RSA''. key_length is the key length in bits. + If you exceed the maximum allowed key length or specify less than the minimum, + key generation fails and the result is null output. The minimum allowed key + length in bits is 2048. The maximum allowed key length is the value of the enterprise_encryption.maximum_rsa_key_size + system variable, which defaults to 4096. It has a maximum setting of 16384, + which is the maximum key length allowed for the RSA algorithm. See https://dev.mysql.com/doc/refman/8.3/en/enterprise-encryption-configuri + ng.html. *Note*: Generating longer keys can consume significant CPU resources. + Limiting the key length using the enterprise_encryption.maximum_rsa_key_size + system variable lets you provide adequate security for your requirements while + balancing this with resource usage. This example creates a 2048-bit RSA private + key, then derives a public key from the private key: URL: https://dev.mysql.com/doc/refman/8.3/en/enterprise-encryption-functions.html' + examples: [] + - name: CREATE_ASYMMETRIC_PUB_KEY + category_id: enterprise_encryption_functions + category_label: Enterprise Encryption Functions + signature: + display: CREATE_ASYMMETRIC_PUB_KEY(algorithm, priv_key_str) + args: + - name: algorithm + optional: false + type: any + - name: priv_key_str + optional: false + type: any + tags: [] + aliases: [] + summary: Derives a public key from the given private key using the given + description: 'Derives a public key from the given private key using the given + algorithm, and returns the key as a binary string in PEM format. The key is + in PKCS #8 format. If key derivation fails, the result is NULL. For the legacy + version of this function in use before MySQL 8.0.29, see https://dev.mysql.com/doc/refman/8.3/en/enterprise-encryption-functions + -legacy.html. algorithm is the encryption algorithm used to create the key. + The supported algorithm value is ''RSA''. priv_key_str is a valid PEM encoded + RSA private key. For a usage example, see the description of create_asymmetric_priv_key(). + URL: https://dev.mysql.com/doc/refman/8.3/en/enterprise-encryption-functions.html' + examples: [] + - name: CREATE_DH_PARAMETERS + category_id: enterprise_encryption_functions + category_label: Enterprise Encryption Functions + signature: + display: CREATE_DH_PARAMETERS(key_len) + args: + - name: key_len + optional: false + type: any + tags: [] + aliases: [] + summary: Creates a shared secret for generating a DH private/public key pair and + description: 'Creates a shared secret for generating a DH private/public key pair + and returns a binary string that can be passed to create_asymmetric_priv_key(). + If secret generation fails, the result is NULL. key_len is the key length. The + minimum and maximum key lengths in bits are 1,024 and 10,000. These key-length + limits are constraints imposed by OpenSSL. Server administrators can impose + additional limits on maximum key length by setting the MYSQL_OPENSSL_UDF_RSA_BITS_THRESHOLD, + MYSQL_OPENSSL_UDF_DSA_BITS_THRESHOLD, and MYSQL_OPENSSL_UDF_DH_BITS_THRESHOLD + environment variables. See https://dev.mysql.com/doc/refman/8.3/en/enterprise-encryption-configuri + ng.html. For an example showing how to use the return value for generating symmetric + keys, see the description of asymmetric_derive(). URL: https://dev.mysql.com/doc/refman/8.3/en/enterprise-encryption-functions-legacy.html' + examples: [] + - name: CREATE_DIGEST + category_id: enterprise_encryption_functions + category_label: Enterprise Encryption Functions + signature: + display: CREATE_DIGEST(digest_type, str) + args: + - name: digest_type + optional: false + type: any + - name: str + optional: false + type: any + tags: [] + aliases: [] + summary: Creates a digest from the given string using the given digest type, and + description: 'Creates a digest from the given string using the given digest type, + and returns the digest as a binary string. If digest generation fails, the result + is NULL. For the legacy version of this function in use before MySQL 8.0.29, + see https://dev.mysql.com/doc/refman/8.3/en/enterprise-encryption-functions + -legacy.html. The resulting digest string is suitable for use with asymmetric_sign() + and asymmetric_verify(). The component versions of these functions accept digests + but do not require them, as they are capable of handling data of an arbitrary + length. digest_type is the digest algorithm to be used to generate the digest + string. The supported digest_type values are ''SHA224'', ''SHA256'', ''SHA384'', + and ''SHA512'' when OpenSSL 1.0.1 is in use. If OpenSSL 1.1.1 is in use, the + additional digest_type values ''SHA3-224'', ''SHA3-256'', ''SHA3-384'', and + ''SHA3-512'' are available. str is the non-null data string for which the digest + is to be generated. URL: https://dev.mysql.com/doc/refman/8.3/en/enterprise-encryption-functions.html' + examples: [] + - name: CUME_DIST + category_id: window_functions + category_label: Window Functions + signature: + display: CUME_DIST + args: [] + tags: [] + aliases: [] + summary: Returns the cumulative distribution of a value within a group of + description: 'Returns the cumulative distribution of a value within a group of + values; that is, the percentage of partition values less than or equal to the + value in the current row. This represents the number of rows preceding or peer + with the current row in the window ordering of the window partition divided + by the total number of rows in the window partition. Return values range from + 0 to 1. This function should be used with ORDER BY to sort partition rows into + the desired order. Without ORDER BY, all rows are peers and have value N/N = + 1, where N is the partition size. over_clause is as described in https://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html. + URL: https://dev.mysql.com/doc/refman/8.3/en/window-function-descriptions.html' + examples: [] + - name: CURDATE + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: CURDATE + args: [] + tags: [] + aliases: [] + summary: Returns the current date as a value in 'YYYY-MM-DD' or YYYYMMDD format, + description: 'Returns the current date as a value in ''YYYY-MM-DD'' or YYYYMMDD + format, depending on whether the function is used in string or numeric context. + URL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: CURRENT_DATE + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: CURRENT_DATE + args: [] + tags: [] + aliases: [] + summary: CURRENT_DATE and CURRENT_DATE() are synonyms for CURDATE(). + description: 'CURRENT_DATE and CURRENT_DATE() are synonyms for CURDATE(). URL: + https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: CURRENT_ROLE + category_id: information_functions + category_label: Information Functions + signature: + display: CURRENT_ROLE + args: [] + tags: [] + aliases: [] + summary: Returns a utf8mb3 string containing the current active roles for the + description: 'Returns a utf8mb3 string containing the current active roles for + the current session, separated by commas, or NONE if there are none. The value + reflects the setting of the sql_quote_show_create system variable. URL: https://dev.mysql.com/doc/refman/8.3/en/information-functions.html' + examples: [] + - name: CURRENT_TIME + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: CURRENT_TIME([fsp]) + args: + - name: '[fsp]' + optional: false + type: any + tags: [] + aliases: [] + summary: CURRENT_TIME and CURRENT_TIME() are synonyms for CURTIME(). + description: 'CURRENT_TIME and CURRENT_TIME() are synonyms for CURTIME(). URL: + https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: CURRENT_TIMESTAMP + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: CURRENT_TIMESTAMP([fsp]) + args: + - name: '[fsp]' + optional: false + type: any + tags: [] + aliases: [] + summary: CURRENT_TIMESTAMP and CURRENT_TIMESTAMP() are synonyms for NOW(). + description: 'CURRENT_TIMESTAMP and CURRENT_TIMESTAMP() are synonyms for NOW(). + URL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: CURRENT_USER + category_id: information_functions + category_label: Information Functions + signature: + display: CURRENT_USER + args: [] + tags: [] + aliases: [] + summary: Returns the user name and host name combination for the MySQL account + description: 'Returns the user name and host name combination for the MySQL account + that the server used to authenticate the current client. This account determines + your access privileges. The return value is a string in the utf8mb3 character + set. The value of CURRENT_USER() can differ from the value of USER(). URL: https://dev.mysql.com/doc/refman/8.3/en/information-functions.html' + examples: [] + - name: CURTIME + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: CURTIME([fsp]) + args: + - name: '[fsp]' + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the current time as a value in 'hh:mm:ss' or hhmmss format, + description: 'Returns the current time as a value in ''hh:mm:ss'' or hhmmss format, + depending on whether the function is used in string or numeric context. The + value is expressed in the session time zone. If the fsp argument is given to + specify a fractional seconds precision from 0 to 6, the return value includes + a fractional seconds part of that many digits. URL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: DATABASE + category_id: information_functions + category_label: Information Functions + signature: + display: DATABASE + args: [] + tags: [] + aliases: [] + summary: Returns the default (current) database name as a string in the utf8mb3 + description: 'Returns the default (current) database name as a string in the utf8mb3 + character set. If there is no default database, DATABASE() returns NULL. Within + a stored routine, the default database is the database that the routine is associated + with, which is not necessarily the same as the database that is the default + in the calling context. URL: https://dev.mysql.com/doc/refman/8.3/en/information-functions.html' + examples: [] + - name: DATEDIFF + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: DATEDIFF(expr1,expr2) + args: + - name: expr1 + optional: false + type: any + - name: expr2 + optional: false + type: any + tags: [] + aliases: [] + summary: "DATEDIFF() returns expr1 \u2212 expr2 expressed as a value in days from" + description: "DATEDIFF() returns expr1 \u2212 expr2 expressed as a value in days\ + \ from\none date to the other. expr1 and expr2 are date or date-and-time\nexpressions.\ + \ Only the date parts of the values are used in the\ncalculation.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html" + examples: [] + - name: DATETIME + category_id: data_types + category_label: Data Types + signature: + display: DATETIME(fsp) + args: + - name: fsp + optional: false + type: any + tags: [] + aliases: [] + summary: A date and time combination. + description: 'A date and time combination. The supported range is ''1000-01-01 + 00:00:00.000000'' to ''9999-12-31 23:59:59.499999''. MySQL displays DATETIME + values in ''YYYY-MM-DD hh:mm:ss[.fraction]'' format, but permits assignment + of values to DATETIME columns using either strings or numbers. An optional fsp + value in the range from 0 to 6 may be given to specify fractional seconds precision. + A value of 0 signifies that there is no fractional part. If omitted, the default + precision is 0. Automatic initialization and updating to the current date and + time for DATETIME columns can be specified using DEFAULT and ON UPDATE column + definition clauses, as described in https://dev.mysql.com/doc/refman/8.3/en/timestamp-initialization.html. + URL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-type-syntax.html' + examples: [] + - name: DATE_ADD + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: DATE_ADD(date,INTERVAL expr unit) + args: + - name: date + optional: false + type: any + - name: INTERVAL expr unit + optional: false + type: any + tags: [] + aliases: [] + summary: These functions perform date arithmetic. + description: 'These functions perform date arithmetic. The date argument specifies + the starting date or datetime value. expr is an expression specifying the interval + value to be added or subtracted from the starting date. expr is evaluated as + a string; it may start with a - for negative intervals. unit is a keyword indicating + the units in which the expression should be interpreted. URL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: DATE_FORMAT + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: DATE_FORMAT(date,format) + args: + - name: date + optional: false + type: any + - name: format + optional: false + type: any + tags: [] + aliases: [] + summary: Formats the date value according to the format string. + description: 'Formats the date value according to the format string. If either + argument is NULL, the function returns NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: DATE_SUB + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: DATE_SUB(date,INTERVAL expr unit) + args: + - name: date + optional: false + type: any + - name: INTERVAL expr unit + optional: false + type: any + tags: [] + aliases: [] + summary: See the description for DATE_ADD(). + description: 'See the description for DATE_ADD(). URL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: DAY + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: DAY(date) + args: + - name: date + optional: false + type: any + tags: [] + aliases: [] + summary: DAY() is a synonym for DAYOFMONTH(). + description: 'DAY() is a synonym for DAYOFMONTH(). URL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: DAYNAME + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: DAYNAME(date) + args: + - name: date + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the name of the weekday for date. + description: 'Returns the name of the weekday for date. The language used for + the name is controlled by the value of the lc_time_names system variable (see + https://dev.mysql.com/doc/refman/8.3/en/locale-support.html). Returns NULL if + date is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: DAYOFMONTH + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: DAYOFMONTH(date) + args: + - name: date + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the day of the month for date, in the range 1 to 31, or 0 for + description: 'Returns the day of the month for date, in the range 1 to 31, or + 0 for dates such as ''0000-00-00'' or ''2008-00-00'' that have a zero day part. + Returns NULL if date is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: DAYOFWEEK + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: DAYOFWEEK(date) + args: + - name: date + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the weekday index for date (1 = Sunday, 2 = Monday, ..., 7 = + description: 'Returns the weekday index for date (1 = Sunday, 2 = Monday, ..., + 7 = Saturday). These index values correspond to the ODBC standard. Returns NULL + if date is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: DAYOFYEAR + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: DAYOFYEAR(date) + args: + - name: date + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the day of the year for date, in the range 1 to 366. + description: 'Returns the day of the year for date, in the range 1 to 366. Returns + NULL if date is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: DEC + category_id: data_types + category_label: Data Types + signature: + display: DEC(M[,D]) + args: + - name: M[ + optional: false + type: any + - name: D] + optional: false + type: any + tags: [] + aliases: [] + summary: '[ZEROFILL], FIXED[(M[,D])] [UNSIGNED] [ZEROFILL]' + description: '[ZEROFILL], FIXED[(M[,D])] [UNSIGNED] [ZEROFILL] These types are + synonyms for DECIMAL. The FIXED synonym is available for compatibility with + other database systems. URL: https://dev.mysql.com/doc/refman/8.3/en/numeric-type-syntax.html' + examples: [] + - name: DECIMAL + category_id: data_types + category_label: Data Types + signature: + display: DECIMAL(M[,D]) + args: + - name: M[ + optional: false + type: any + - name: D] + optional: false + type: any + tags: [] + aliases: [] + summary: A packed "exact" fixed-point number. + description: 'A packed "exact" fixed-point number. M is the total number of digits + (the precision) and D is the number of digits after the decimal point (the scale). + The decimal point and (for negative numbers) the - sign are not counted in M. + If D is 0, values have no decimal point or fractional part. The maximum number + of digits (M) for DECIMAL is 65. The maximum number of supported decimals (D) + is 30. If D is omitted, the default is 0. If M is omitted, the default is 10. + (There is also a limit on how long the text of DECIMAL literals can be; see + https://dev.mysql.com/doc/refman/8.3/en/precision-math-expressions.html .) UNSIGNED, + if specified, disallows negative values. The UNSIGNED attribute is deprecated + for columns of type DECIMAL (and any synonyms); you should expect support for + it to be removed in a future version of MySQL. Consider using a simple CHECK + constraint instead for such columns. All basic calculations (+, -, *, /) with + DECIMAL columns are done with a precision of 65 digits. URL: https://dev.mysql.com/doc/refman/8.3/en/numeric-type-syntax.html' + examples: [] + - name: DEFAULT + category_id: miscellaneous_functions + category_label: Miscellaneous Functions + signature: + display: DEFAULT(col_name) + args: + - name: col_name + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the default value for a table column. + description: 'Returns the default value for a table column. An error results if + the column has no default value. The use of DEFAULT(col_name) to specify the + default value for a named column is permitted only for columns that have a literal + default value, not for columns that have an expression default value. URL: https://dev.mysql.com/doc/refman/8.3/en/miscellaneous-functions.html' + examples: [] + - name: DEGREES + category_id: numeric_functions + category_label: Numeric Functions + signature: + display: DEGREES(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the argument X, converted from radians to degrees. + description: 'Returns the argument X, converted from radians to degrees. Returns + NULL if X is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html' + examples: [] + - name: DENSE_RANK + category_id: window_functions + category_label: Window Functions + signature: + display: DENSE_RANK + args: [] + tags: [] + aliases: [] + summary: Returns the rank of the current row within its partition, without gaps. + description: 'Returns the rank of the current row within its partition, without + gaps. Peers are considered ties and receive the same rank. This function assigns + consecutive ranks to peer groups; the result is that groups of size greater + than one do not produce noncontiguous rank numbers. For an example, see the + RANK() function description. This function should be used with ORDER BY to sort + partition rows into the desired order. Without ORDER BY, all rows are peers. + over_clause is as described in https://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html. + URL: https://dev.mysql.com/doc/refman/8.3/en/window-function-descriptions.html' + examples: [] + - name: DOUBLE + category_id: data_types + category_label: Data Types + signature: + display: DOUBLE(M,D) + args: + - name: M + optional: false + type: any + - name: D + optional: false + type: any + tags: [] + aliases: [] + summary: A normal-size (double-precision) floating-point number. + description: 'A normal-size (double-precision) floating-point number. Permissible + values are -1.7976931348623157E+308 to -2.2250738585072014E-308, 0, and 2.2250738585072014E-308 + to 1.7976931348623157E+308. These are the theoretical limits, based on the IEEE + standard. The actual range might be slightly smaller depending on your hardware + or operating system. M is the total number of digits and D is the number of + digits following the decimal point. If M and D are omitted, values are stored + to the limits permitted by the hardware. A double-precision floating-point number + is accurate to approximately 15 decimal places. DOUBLE(M,D) is a nonstandard + MySQL extension; and is deprecated. You should expect support for this syntax + to be removed in a future version of MySQL. UNSIGNED, if specified, disallows + negative values. The UNSIGNED attribute is deprecated for columns of type DOUBLE + (and any synonyms) and you should expect support for it to be removed in a future + version of MySQL. Consider using a simple CHECK constraint instead for such + columns. URL: https://dev.mysql.com/doc/refman/8.3/en/numeric-type-syntax.html' + examples: [] + - name: ELT + category_id: string_functions + category_label: String Functions + signature: + display: ELT(N,str1,str2,str3,...) + args: + - name: N + optional: false + type: any + - name: str1 + optional: false + type: any + - name: str2 + optional: false + type: any + - name: str3 + optional: false + type: any + - name: '...' + optional: false + type: any + tags: [] + aliases: [] + summary: 'ELT() returns the Nth element of the list of strings: str1 if N = 1,' + description: 'ELT() returns the Nth element of the list of strings: str1 if N + = 1, str2 if N = 2, and so on. Returns NULL if N is less than 1, greater than + the number of arguments, or NULL. ELT() is the complement of FIELD(). URL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html' + examples: [] + - name: ENUM + category_id: data_types + category_label: Data Types + signature: + display: ENUM('value1','value2',...) + args: + - name: '''value1''' + optional: false + type: any + - name: '''value2''' + optional: false + type: any + - name: '...' + optional: false + type: any + tags: [] + aliases: [] + summary: collation_name] + description: 'collation_name] An enumeration. A string object that can have only + one value, chosen from the list of values ''value1'', ''value2'', ..., NULL + or the special '''' error value. ENUM values are represented internally as integers. + An ENUM column can have a maximum of 65,535 distinct elements. The maximum supported + length of an individual ENUM element is M <= 255 and (M x w) <= 1020, where + M is the element literal length and w is the number of bytes required for the + maximum-length character in the character set. URL: https://dev.mysql.com/doc/refman/8.3/en/string-type-syntax.html' + examples: [] + - name: EXP + category_id: numeric_functions + category_label: Numeric Functions + signature: + display: EXP(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the value of e (the base of natural logarithms) raised to the + description: 'Returns the value of e (the base of natural logarithms) raised to + the power of X. The inverse of this function is LOG() (using a single argument + only) or LN(). If X is NULL, this function returns NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html' + examples: [] + - name: EXPORT_SET + category_id: string_functions + category_label: String Functions + signature: + display: EXPORT_SET(bits,on,off[,separator[,number_of_bits]]) + args: + - name: bits + optional: false + type: any + - name: 'on' + optional: false + type: any + - name: off[ + optional: false + type: any + - name: separator[ + optional: false + type: any + - name: number_of_bits]] + optional: false + type: any + tags: [] + aliases: [] + summary: Returns a string such that for every bit set in the value bits, you get + description: "Returns a string such that for every bit set in the value bits,\ + \ you get\nan on string and for every bit not set in the value, you get an off\n\ + string. Bits in bits are examined from right to left (from low-order to\nhigh-order\ + \ bits). Strings are added to the result from left to right,\nseparated by the\ + \ separator string (the default being the comma\ncharacter ,). The number of\ + \ bits examined is given by number_of_bits,\nwhich has a default of 64 if not\ + \ specified. number_of_bits is silently\nclipped to 64 if larger than 64. It\ + \ is treated as an unsigned integer,\nso a value of \u22121 is effectively the\ + \ same as 64.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html" + examples: [] + - name: EXTRACT + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: EXTRACT(unit FROM date) + args: + - name: unit FROM date + optional: false + type: any + tags: [] + aliases: [] + summary: The EXTRACT() function uses the same kinds of unit specifiers as + description: 'The EXTRACT() function uses the same kinds of unit specifiers as + DATE_ADD() or DATE_SUB(), but extracts parts from the date rather than performing + date arithmetic. For information on the unit argument, see https://dev.mysql.com/doc/refman/8.3/en/expressions.html#temporal-inter + vals. Returns NULL if date is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: EXTRACTVALUE + category_id: xml + category_label: XML + signature: + display: EXTRACTVALUE(xml_frag, xpath_expr) + args: + - name: xml_frag + optional: false + type: any + - name: xpath_expr + optional: false + type: any + tags: [] + aliases: [] + summary: ExtractValue() takes two string arguments, a fragment of XML markup + description: "ExtractValue() takes two string arguments, a fragment of XML markup\n\ + xml_frag and an XPath expression xpath_expr (also known as a locator);\nit returns\ + \ the text (CDATA) of the first text node which is a child of\nthe element or\ + \ elements matched by the XPath expression.\n\nUsing this function is the equivalent\ + \ of performing a match using the\nxpath_expr after appending /text(). In other\ + \ words,\nExtractValue('Sakila', '/a/b') and\nExtractValue('Sakila',\ + \ '/a/b/text()') produce the same\nresult. If xml_frag or xpath_expr is NULL,\ + \ the function returns NULL.\n\nIf multiple matches are found, the content of\ + \ the first child text node\nof each matching element is returned (in the order\ + \ matched) as a\nsingle, space-delimited string.\n\nIf no matching text node\ + \ is found for the expression (including the\nimplicit /text())---for whatever\ + \ reason, as long as xpath_expr is\nvalid, and xml_frag consists of elements\ + \ which are properly nested and\nclosed---an empty string is returned. No distinction\ + \ is made between a\nmatch on an empty element and no match at all. This is\ + \ by design.\n\nIf you need to determine whether no matching element was found\ + \ in\nxml_frag or such an element was found but contained no child text\nnodes,\ + \ you should test the result of an expression that uses the XPath\ncount() function.\ + \ For example, both of these statements return an empty\nstring, as shown here:\n\ + \nmysql> SELECT ExtractValue('', '/a/b');\n+-------------------------------------+\n\ + | ExtractValue('', '/a/b') |\n+-------------------------------------+\n\ + | |\n+-------------------------------------+\n\ + 1 row in set (0.00 sec)\n\nmysql> SELECT ExtractValue('', '/a/b');\n\ + +-------------------------------------+\n| ExtractValue('', '/a/b')\ + \ |\n+-------------------------------------+\n| \ + \ |\n+-------------------------------------+\n1 row in set (0.00 sec)\n\ + \nHowever, you can determine whether there was actually a matching\nelement\ + \ using the following:\n\nmysql> SELECT ExtractValue('', 'count(/a/b)');\n\ + +-------------------------------------+\n| ExtractValue('', 'count(/a/b)')\ + \ |\n+-------------------------------------+\n ..." + examples: [] + - name: FIELD + category_id: string_functions + category_label: String Functions + signature: + display: FIELD(str,str1,str2,str3,...) + args: + - name: str + optional: false + type: any + - name: str1 + optional: false + type: any + - name: str2 + optional: false + type: any + - name: str3 + optional: false + type: any + - name: '...' + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the index (position) of str in the str1, str2, str3, ... + description: 'Returns the index (position) of str in the str1, str2, str3, ... + list. Returns 0 if str is not found. If all arguments to FIELD() are strings, + all arguments are compared as strings. If all arguments are numbers, they are + compared as numbers. Otherwise, the arguments are compared as double. If str + is NULL, the return value is 0 because NULL fails equality comparison with any + value. FIELD() is the complement of ELT(). URL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html' + examples: [] + - name: FIND_IN_SET + category_id: string_functions + category_label: String Functions + signature: + display: FIND_IN_SET(str,strlist) + args: + - name: str + optional: false + type: any + - name: strlist + optional: false + type: any + tags: [] + aliases: [] + summary: Returns a value in the range of 1 to N if the string str is in the + description: 'Returns a value in the range of 1 to N if the string str is in the + string list strlist consisting of N substrings. A string list is a string composed + of substrings separated by , characters. If the first argument is a constant + string and the second is a column of type SET, the FIND_IN_SET() function is + optimized to use bit arithmetic. Returns 0 if str is not in strlist or if strlist + is the empty string. Returns NULL if either argument is NULL. This function + does not work properly if the first argument contains a comma (,) character. + URL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html' + examples: [] + - name: FIRST_VALUE + category_id: window_functions + category_label: Window Functions + signature: + display: FIRST_VALUE(expr) + args: + - name: expr + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the value of expr from the first row of the window frame. + description: 'Returns the value of expr from the first row of the window frame. + over_clause is as described in https://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html. + null_treatment is as described in the section introduction. URL: https://dev.mysql.com/doc/refman/8.3/en/window-function-descriptions.html' + examples: [] + - name: FLOAT + category_id: data_types + category_label: Data Types + signature: + display: FLOAT(M,D) + args: + - name: M + optional: false + type: any + - name: D + optional: false + type: any + tags: [] + aliases: [] + summary: A small (single-precision) floating-point number. + description: 'A small (single-precision) floating-point number. Permissible values + are -3.402823466E+38 to -1.175494351E-38, 0, and 1.175494351E-38 to 3.402823466E+38. + These are the theoretical limits, based on the IEEE standard. The actual range + might be slightly smaller depending on your hardware or operating system. M + is the total number of digits and D is the number of digits following the decimal + point. If M and D are omitted, values are stored to the limits permitted by + the hardware. A single-precision floating-point number is accurate to approximately + 7 decimal places. FLOAT(M,D) is a nonstandard MySQL extension. This syntax is + deprecated, and you should expect support for it to be removed in a future version + of MySQL. UNSIGNED, if specified, disallows negative values. The UNSIGNED attribute + is deprecated for columns of type FLOAT (and any synonyms) and you should expect + support for it to be removed in a future version of MySQL. Consider using a + simple CHECK constraint instead for such columns. Using FLOAT might give you + some unexpected problems because all calculations in MySQL are done with double + precision. See https://dev.mysql.com/doc/refman/8.3/en/no-matching-rows.html. + URL: https://dev.mysql.com/doc/refman/8.3/en/numeric-type-syntax.html' + examples: [] + - name: FLOOR + category_id: numeric_functions + category_label: Numeric Functions + signature: + display: FLOOR(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the largest integer value not greater than X. + description: 'Returns the largest integer value not greater than X. Returns NULL + if X is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html' + examples: [] + - name: FORMAT + category_id: string_functions + category_label: String Functions + signature: + display: FORMAT(X,D[,locale]) + args: + - name: X + optional: false + type: any + - name: D[ + optional: false + type: any + - name: locale] + optional: false + type: any + tags: [] + aliases: [] + summary: Formats the number X to a format like '#,###,###.##', rounded to D + description: 'Formats the number X to a format like ''#,###,###.##'', rounded + to D decimal places, and returns the result as a string. If D is 0, the result + has no decimal point or fractional part. If X or D is NULL, the function returns + NULL. The optional third parameter enables a locale to be specified to be used + for the result number''s decimal point, thousands separator, and grouping between + separators. Permissible locale values are the same as the legal values for the + lc_time_names system variable (see https://dev.mysql.com/doc/refman/8.3/en/locale-support.html). + If the locale is NULL or not specified, the default locale is ''en_US''. URL: + https://dev.mysql.com/doc/refman/8.3/en/string-functions.html' + examples: [] + - name: FORMAT_BYTES + category_id: performance_schema_functions + category_label: Performance Schema Functions + signature: + display: FORMAT_BYTES(count) + args: + - name: count + optional: false + type: any + tags: [] + aliases: [] + summary: Given a numeric byte count, converts it to human-readable format and + description: 'Given a numeric byte count, converts it to human-readable format + and returns a string consisting of a value and a units indicator. The string + contains the number of bytes rounded to 2 decimal places and a minimum of 3 + significant digits. Numbers less than 1024 bytes are represented as whole numbers + and are not rounded. Returns NULL if count is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/performance-schema-functions.html' + examples: [] + - name: FORMAT_PICO_TIME + category_id: performance_schema_functions + category_label: Performance Schema Functions + signature: + display: FORMAT_PICO_TIME(time_val) + args: + - name: time_val + optional: false + type: any + tags: [] + aliases: [] + summary: Given a numeric Performance Schema latency or wait time in picoseconds, + description: 'Given a numeric Performance Schema latency or wait time in picoseconds, + converts it to human-readable format and returns a string consisting of a value + and a units indicator. The string contains the decimal time rounded to 2 decimal + places and a minimum of 3 significant digits. Times under 1 nanosecond are represented + as whole numbers and are not rounded. If time_val is NULL, this function returns + NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/performance-schema-functions.html' + examples: [] + - name: FOUND_ROWS + category_id: information_functions + category_label: Information Functions + signature: + display: FOUND_ROWS + args: [] + tags: [] + aliases: [] + summary: '*Note*:' + description: '*Note*: The SQL_CALC_FOUND_ROWS query modifier and accompanying + FOUND_ROWS() function are deprecated; expect them to be removed in a future + version of MySQL. Execute the query with LIMIT, and then a second query with + COUNT(*) and without LIMIT to determine whether there are additional rows. For + example, instead of these queries: SELECT SQL_CALC_FOUND_ROWS * FROM tbl_name + WHERE id > 100 LIMIT 10; SELECT FOUND_ROWS(); Use these queries instead: SELECT + * FROM tbl_name WHERE id > 100 LIMIT 10; SELECT COUNT(*) FROM tbl_name WHERE + id > 100; COUNT(*) is subject to certain optimizations. SQL_CALC_FOUND_ROWS + causes some optimizations to be disabled. A SELECT statement may include a LIMIT + clause to restrict the number of rows the server returns to the client. In some + cases, it is desirable to know how many rows the statement would have returned + without the LIMIT, but without running the statement again. To obtain this row + count, include an SQL_CALC_FOUND_ROWS option in the SELECT statement, and then + invoke FOUND_ROWS() afterward: URL: https://dev.mysql.com/doc/refman/8.3/en/information-functions.html' + examples: [] + - name: FROM_BASE64 + category_id: string_functions + category_label: String Functions + signature: + display: FROM_BASE64(str) + args: + - name: str + optional: false + type: any + tags: [] + aliases: [] + summary: Takes a string encoded with the base-64 encoded rules used by + description: 'Takes a string encoded with the base-64 encoded rules used by TO_BASE64() + and returns the decoded result as a binary string. The result is NULL if the + argument is NULL or not a valid base-64 string. See the description of TO_BASE64() + for details about the encoding and decoding rules. URL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html' + examples: [] + - name: FROM_DAYS + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: FROM_DAYS(N) + args: + - name: N + optional: false + type: any + tags: [] + aliases: [] + summary: Given a day number N, returns a DATE value. + description: 'Given a day number N, returns a DATE value. Returns NULL if N is + NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: FROM_UNIXTIME + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: FROM_UNIXTIME(unix_timestamp[,format]) + args: + - name: unix_timestamp[ + optional: false + type: any + - name: format] + optional: false + type: any + tags: [] + aliases: [] + summary: Returns a representation of unix_timestamp as a datetime or character + description: 'Returns a representation of unix_timestamp as a datetime or character + string value. The value returned is expressed using the session time zone. (Clients + can set the session time zone as described in https://dev.mysql.com/doc/refman/8.3/en/time-zone-support.html.) + unix_timestamp is an internal timestamp value representing seconds since ''1970-01-01 + 00:00:00'' UTC, such as produced by the UNIX_TIMESTAMP() function. If format + is omitted, this function returns a DATETIME value. If unix_timestamp or format + is NULL, this function returns NULL. If unix_timestamp is an integer, the fractional + seconds precision of the DATETIME is zero. When unix_timestamp is a decimal + value, the fractional seconds precision of the DATETIME is the same as the precision + of the decimal value, up to a maximum of 6. When unix_timestamp is a floating + point number, the fractional seconds precision of the datetime is 6. On 32-bit + platforms, the maximum useful value for unix_timestamp is 2147483647.999999, + which returns ''2038-01-19 03:14:07.999999'' UTC. On 64-bit platforms, the effective + maximum is 32536771199.999999, which returns ''3001-01-18 23:59:59.999999'' + UTC. Regardless of platform or version, a greater value for unix_timestamp than + the effective maximum returns 0. format is used to format the result in the + same way as the format string used for the DATE_FORMAT() function. If format + is supplied, the value returned is a VARCHAR. URL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: GEOMCOLLECTION + category_id: geometry_constructors + category_label: Geometry Constructors + signature: + display: GEOMCOLLECTION(g [, g] ...) + args: + - name: g [ + optional: false + type: any + - name: g] ... + optional: false + type: any + tags: [] + aliases: [] + summary: Constructs a GeomCollection value from the geometry arguments. + description: 'Constructs a GeomCollection value from the geometry arguments. GeomCollection() + returns all the proper geometries contained in the arguments even if a nonsupported + geometry is present. GeomCollection() with no arguments is permitted as a way + to create an empty geometry. Also, functions such as ST_GeomFromText() that + accept WKT geometry collection arguments understand both OpenGIS ''GEOMETRYCOLLECTION + EMPTY'' standard syntax and MySQL ''GEOMETRYCOLLECTION()'' nonstandard syntax. + GeomCollection() and GeometryCollection() are synonymous, with GeomCollection() + the preferred function. URL: https://dev.mysql.com/doc/refman/8.3/en/gis-mysql-specific-functions.html' + examples: [] + - name: GEOMETRYCOLLECTION + category_id: geometry_constructors + category_label: Geometry Constructors + signature: + display: GEOMETRYCOLLECTION(g [, g] ...) + args: + - name: g [ + optional: false + type: any + - name: g] ... + optional: false + type: any + tags: [] + aliases: [] + summary: Constructs a GeomCollection value from the geometry arguments. + description: 'Constructs a GeomCollection value from the geometry arguments. GeometryCollection() + returns all the proper geometries contained in the arguments even if a nonsupported + geometry is present. GeometryCollection() with no arguments is permitted as + a way to create an empty geometry. Also, functions such as ST_GeomFromText() + that accept WKT geometry collection arguments understand both OpenGIS ''GEOMETRYCOLLECTION + EMPTY'' standard syntax and MySQL ''GEOMETRYCOLLECTION()'' nonstandard syntax. + GeomCollection() and GeometryCollection() are synonymous, with GeomCollection() + the preferred function. URL: https://dev.mysql.com/doc/refman/8.3/en/gis-mysql-specific-functions.html' + examples: [] + - name: GET_FORMAT + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: GET_FORMAT({DATE|TIME|DATETIME}, {'EUR'|'USA'|'JIS'|'ISO'|'INTERNAL'}) + args: + - name: '{DATE|TIME|DATETIME}' + optional: false + type: any + - name: '{''EUR''|''USA''|''JIS''|''ISO''|''INTERNAL''}' + optional: false + type: any + tags: [] + aliases: [] + summary: Returns a format string. + description: 'Returns a format string. This function is useful in combination + with the DATE_FORMAT() and the STR_TO_DATE() functions. If format is NULL, this + function returns NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: GET_LOCK + category_id: locking_functions + category_label: Locking Functions + signature: + display: GET_LOCK(str,timeout) + args: + - name: str + optional: false + type: any + - name: timeout + optional: false + type: any + tags: [] + aliases: [] + summary: Tries to obtain a lock with a name given by the string str, using a + description: "Tries to obtain a lock with a name given by the string str, using\ + \ a\ntimeout of timeout seconds. A negative timeout value means infinite\ntimeout.\ + \ The lock is exclusive. While held by one session, other\nsessions cannot obtain\ + \ a lock of the same name.\n\nReturns 1 if the lock was obtained successfully,\ + \ 0 if the attempt timed\nout (for example, because another client has previously\ + \ locked the\nname), or NULL if an error occurred (such as running out of memory\ + \ or\nthe thread was killed with mysqladmin kill).\n\nA lock obtained with GET_LOCK()\ + \ is released explicitly by executing\nRELEASE_LOCK() or implicitly when your\ + \ session terminates (either\nnormally or abnormally). Locks obtained with GET_LOCK()\ + \ are not\nreleased when transactions commit or roll back.\n\nGET_LOCK() is\ + \ implemented using the metadata locking (MDL) subsystem.\nMultiple simultaneous\ + \ locks can be acquired and GET_LOCK() does not\nrelease any existing locks.\ + \ For example, suppose that you execute these\nstatements:\n\nSELECT GET_LOCK('lock1',10);\n\ + SELECT GET_LOCK('lock2',10);\nSELECT RELEASE_LOCK('lock2');\nSELECT RELEASE_LOCK('lock1');\n\ + \nThe second GET_LOCK() acquires a second lock and both RELEASE_LOCK()\ncalls\ + \ return 1 (success).\n\nIt is even possible for a given session to acquire\ + \ multiple locks for\nthe same name. Other sessions cannot acquire a lock with\ + \ that name\nuntil the acquiring session releases all its locks for the name.\n\ + \nUniquely named locks acquired with GET_LOCK() appear in the Performance\n\ + Schema metadata_locks table. The OBJECT_TYPE column says USER LEVEL\nLOCK and\ + \ the OBJECT_NAME column indicates the lock name. In the case\nthat multiple\ + \ locks are acquired for the same name, only the first lock\nfor the name registers\ + \ a row in the metadata_locks table. Subsequent\nlocks for the name increment\ + \ a counter in the lock but do not acquire\nadditional metadata locks. The metadata_locks\ + \ row for the lock is\ndeleted when the last lock instance on the name is released.\n\ + \nThe capability of acquiring multiple locks means there is the\npossibility\ + \ of deadlock among clients. When this happens, the server\nchooses a caller\ + \ and terminates its lock-acquisition request with an\nER_USER_LOCK_DEADLOCK\n\ + (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference.html\n\ + #error_er_user_lock_deadlock) error. This error does not cause\ntransactions\ + \ to roll back.\n\nMySQL enforces a maximum length on lock names of 64 characters.\n\ + \ ..." + examples: [] + - name: GREATEST + category_id: comparison_operators + category_label: Comparison Operators + signature: + display: GREATEST(value1,value2,...) + args: + - name: value1 + optional: false + type: any + - name: value2 + optional: false + type: any + - name: '...' + optional: false + type: any + tags: [] + aliases: [] + summary: With two or more arguments, returns the largest (maximum-valued) + description: 'With two or more arguments, returns the largest (maximum-valued) + argument. The arguments are compared using the same rules as for LEAST(). URL: + https://dev.mysql.com/doc/refman/8.3/en/comparison-operators.html' + examples: [] + - name: GROUPING + category_id: miscellaneous_functions + category_label: Miscellaneous Functions + signature: + display: GROUPING(expr [, expr] ...) + args: + - name: expr [ + optional: false + type: any + - name: expr] ... + optional: false + type: any + tags: [] + aliases: [] + summary: For GROUP BY queries that include a WITH ROLLUP modifier, the ROLLUP + description: "For GROUP BY queries that include a WITH ROLLUP modifier, the ROLLUP\n\ + operation produces super-aggregate output rows where NULL represents\nthe set\ + \ of all values. The GROUPING() function enables you to\ndistinguish NULL values\ + \ for super-aggregate rows from NULL values in\nregular grouped rows.\n\nGROUPING()\ + \ is permitted in the select list, HAVING clause, and ORDER BY\nclause.\n\n\ + Each argument to GROUPING() must be an expression that exactly matches\nan expression\ + \ in the GROUP BY clause. The expression cannot be a\npositional specifier.\ + \ For each expression, GROUPING() produces 1 if the\nexpression value in the\ + \ current row is a NULL representing a\nsuper-aggregate value. Otherwise, GROUPING()\ + \ produces 0, indicating\nthat the expression value is a NULL for a regular\ + \ result row or is not\nNULL.\n\nSuppose that table t1 contains these rows,\ + \ where NULL indicates\nsomething like \"other\" or \"unknown\":\n\nmysql> SELECT\ + \ * FROM t1;\n+------+-------+----------+\n| name | size | quantity |\n+------+-------+----------+\n\ + | ball | small | 10 |\n| ball | large | 20 |\n| ball | NULL | \ + \ 5 |\n| hoop | small | 15 |\n| hoop | large | 5 |\n| hoop\ + \ | NULL | 3 |\n+------+-------+----------+\n\nA summary of the table\ + \ without WITH ROLLUP looks like this:\n\nmysql> SELECT name, size, SUM(quantity)\ + \ AS quantity\n FROM t1\n GROUP BY name, size;\n+------+-------+----------+\n\ + | name | size | quantity |\n+------+-------+----------+\n| ball | small | \ + \ 10 |\n| ball | large | 20 |\n| ball | NULL | 5 |\n| hoop\ + \ | small | 15 |\n| hoop | large | 5 |\n| hoop | NULL | \ + \ 3 |\n+------+-------+----------+\n\nThe result contains NULL values, but\ + \ those do not represent\nsuper-aggregate rows because the query does not include\ + \ WITH ROLLUP.\n ..." + examples: [] + - name: GROUP_CONCAT + category_id: aggregate_functions_and_modifiers + category_label: Aggregate Functions and Modifiers + signature: + display: GROUP_CONCAT(expr) + args: + - name: expr + optional: false + type: any + tags: [] + aliases: [] + summary: This function returns a string result with the concatenated non-NULL + description: "This function returns a string result with the concatenated non-NULL\n\ + values from a group. It returns NULL if there are no non-NULL values.\nThe full\ + \ syntax is as follows:\n\nGROUP_CONCAT([DISTINCT] expr [,expr ...]\n \ + \ [ORDER BY {unsigned_integer | col_name | expr}\n [ASC\ + \ | DESC] [,col_name ...]]\n [SEPARATOR str_val])\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/aggregate-functions.html" + examples: [] + - name: GTID_SUBSET + category_id: gtid + category_label: GTID + signature: + display: GTID_SUBSET(set1,set2) + args: + - name: set1 + optional: false + type: any + - name: set2 + optional: false + type: any + tags: [] + aliases: [] + summary: Given two sets of global transaction identifiers set1 and set2, returns + description: 'Given two sets of global transaction identifiers set1 and set2, + returns true if all GTIDs in set1 are also in set2. Returns NULL if set1 or + set2 is NULL. Returns false otherwise. URL: https://dev.mysql.com/doc/refman/8.3/en/gtid-functions.html' + examples: [] + - name: GTID_SUBTRACT + category_id: gtid + category_label: GTID + signature: + display: GTID_SUBTRACT(set1,set2) + args: + - name: set1 + optional: false + type: any + - name: set2 + optional: false + type: any + tags: [] + aliases: [] + summary: Given two sets of global transaction identifiers set1 and set2, returns + description: 'Given two sets of global transaction identifiers set1 and set2, + returns only those GTIDs from set1 that are not in set2. Returns NULL if set1 + or set2 is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/gtid-functions.html' + examples: [] + - name: HEX + category_id: string_functions + category_label: String Functions + signature: + display: HEX(str) + args: + - name: str + optional: false + type: any + tags: [] + aliases: [] + summary: For a string argument str, HEX() returns a hexadecimal string + description: 'For a string argument str, HEX() returns a hexadecimal string representation + of str where each byte of each character in str is converted to two hexadecimal + digits. (Multibyte characters therefore become more than two digits.) The inverse + of this operation is performed by the UNHEX() function. For a numeric argument + N, HEX() returns a hexadecimal string representation of the value of N treated + as a longlong (BIGINT) number. This is equivalent to CONV(N,10,16). The inverse + of this operation is performed by CONV(HEX(N),16,10). For a NULL argument, this + function returns NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html' + examples: [] + - name: HOUR + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: HOUR(time) + args: + - name: time + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the hour for time. + description: 'Returns the hour for time. The range of the return value is 0 to + 23 for time-of-day values. However, the range of TIME values actually is much + larger, so HOUR can return values greater than 23. Returns NULL if time is NULL. + URL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: ICU_VERSION + category_id: information_functions + category_label: Information Functions + signature: + display: ICU_VERSION + args: [] + tags: [] + aliases: [] + summary: The version of the International Components for Unicode (ICU) library + description: 'The version of the International Components for Unicode (ICU) library + used to support regular expression operations (see https://dev.mysql.com/doc/refman/8.3/en/regexp.html). + This function is primarily intended for use in test cases. URL: https://dev.mysql.com/doc/refman/8.3/en/information-functions.html' + examples: [] + - name: IFNULL + category_id: flow_control_functions + category_label: Flow Control Functions + signature: + display: IFNULL(expr1,expr2) + args: + - name: expr1 + optional: false + type: any + - name: expr2 + optional: false + type: any + tags: [] + aliases: [] + summary: If expr1 is not NULL, IFNULL() returns expr1; otherwise it returns + description: 'If expr1 is not NULL, IFNULL() returns expr1; otherwise it returns + expr2. URL: https://dev.mysql.com/doc/refman/8.3/en/flow-control-functions.html' + examples: [] + - name: IN + category_id: comparison_operators + category_label: Comparison Operators + signature: + display: IN(value,...) + args: + - name: value + optional: false + type: any + - name: '...' + optional: false + type: any + tags: [] + aliases: [] + summary: Returns 1 (true) if expr is equal to any of the values in the IN() + description: 'Returns 1 (true) if expr is equal to any of the values in the IN() + list, else returns 0 (false). Type conversion takes place according to the rules + described in https://dev.mysql.com/doc/refman/8.3/en/type-conversion.html, applied + to all the arguments. If no type conversion is needed for the values in the + IN() list, they are all non-JSON constants of the same type, and expr can be + compared to each of them as a value of the same type (possibly after type conversion), + an optimization takes place. The values the list are sorted and the search for + expr is done using a binary search, which makes the IN() operation very quick. + URL: https://dev.mysql.com/doc/refman/8.3/en/comparison-operators.html' + examples: [] + - name: INET6_ATON + category_id: miscellaneous_functions + category_label: Miscellaneous Functions + signature: + display: INET6_ATON(expr) + args: + - name: expr + optional: false + type: any + tags: [] + aliases: [] + summary: Given an IPv6 or IPv4 network address as a string, returns a binary + description: 'Given an IPv6 or IPv4 network address as a string, returns a binary + string that represents the numeric value of the address in network byte order + (big endian). Because numeric-format IPv6 addresses require more bytes than + the largest integer type, the representation returned by this function has the + VARBINARY data type: VARBINARY(16) for IPv6 addresses and VARBINARY(4) for IPv4 + addresses. If the argument is not a valid address, or if it is NULL, INET6_ATON() + returns NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/miscellaneous-functions.html' + examples: [] + - name: INET6_NTOA + category_id: miscellaneous_functions + category_label: Miscellaneous Functions + signature: + display: INET6_NTOA(expr) + args: + - name: expr + optional: false + type: any + tags: [] + aliases: [] + summary: Given an IPv6 or IPv4 network address represented in numeric form as + a + description: "Given an IPv6 or IPv4 network address represented in numeric form\ + \ as a\nbinary string, returns the string representation of the address as a\n\ + string in the connection character set. If the argument is not a valid\naddress,\ + \ or if it is NULL, INET6_NTOA() returns NULL.\n\nINET6_NTOA() has these properties:\n\ + \no It does not use operating system functions to perform conversions,\n thus\ + \ the output string is platform independent.\n\no The return string has a maximum\ + \ length of 39 (4 x 8 + 7). Given this\n statement:\n\nCREATE TABLE t AS SELECT\ + \ INET6_NTOA(expr) AS c1;\n\n The resulting table would have this definition:\n\ + \nCREATE TABLE t (c1 VARCHAR(39) CHARACTER SET utf8mb3 DEFAULT NULL);\n\no The\ + \ return string uses lowercase letters for IPv6 addresses.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/miscellaneous-functions.html" + examples: [] + - name: INET_ATON + category_id: miscellaneous_functions + category_label: Miscellaneous Functions + signature: + display: INET_ATON(expr) + args: + - name: expr + optional: false + type: any + tags: [] + aliases: [] + summary: Given the dotted-quad representation of an IPv4 network address as a + description: 'Given the dotted-quad representation of an IPv4 network address + as a string, returns an integer that represents the numeric value of the address + in network byte order (big endian). INET_ATON() returns NULL if it does not + understand its argument, or if expr is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/miscellaneous-functions.html' + examples: [] + - name: INET_NTOA + category_id: miscellaneous_functions + category_label: Miscellaneous Functions + signature: + display: INET_NTOA(expr) + args: + - name: expr + optional: false + type: any + tags: [] + aliases: [] + summary: Given a numeric IPv4 network address in network byte order, returns the + description: 'Given a numeric IPv4 network address in network byte order, returns + the dotted-quad string representation of the address as a string in the connection + character set. INET_NTOA() returns NULL if it does not understand its argument. + URL: https://dev.mysql.com/doc/refman/8.3/en/miscellaneous-functions.html' + examples: [] + - name: INSTR + category_id: string_functions + category_label: String Functions + signature: + display: INSTR(str,substr) + args: + - name: str + optional: false + type: any + - name: substr + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the position of the first occurrence of substring substr in + description: 'Returns the position of the first occurrence of substring substr + in string str. This is the same as the two-argument form of LOCATE(), except + that the order of the arguments is reversed. URL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html' + examples: [] + - name: INT + category_id: data_types + category_label: Data Types + signature: + display: INT(M) + args: + - name: M + optional: false + type: any + tags: [] + aliases: [] + summary: A normal-size integer. + description: 'A normal-size integer. The signed range is -2147483648 to 2147483647. + The unsigned range is 0 to 4294967295. URL: https://dev.mysql.com/doc/refman/8.3/en/numeric-type-syntax.html' + examples: [] + - name: INTEGER + category_id: data_types + category_label: Data Types + signature: + display: INTEGER(M) + args: + - name: M + optional: false + type: any + tags: [] + aliases: [] + summary: This type is a synonym for INT. + description: 'This type is a synonym for INT. URL: https://dev.mysql.com/doc/refman/8.3/en/numeric-type-syntax.html' + examples: [] + - name: INTERVAL + category_id: comparison_operators + category_label: Comparison Operators + signature: + display: INTERVAL(N,N1,N2,N3,...) + args: + - name: N + optional: false + type: any + - name: N1 + optional: false + type: any + - name: N2 + optional: false + type: any + - name: N3 + optional: false + type: any + - name: '...' + optional: false + type: any + tags: [] + aliases: [] + summary: Returns 0 if N <= N1, 1 if N <= N2 and so on, or -1 if N is NULL. + description: 'Returns 0 if N <= N1, 1 if N <= N2 and so on, or -1 if N is NULL. + All arguments are treated as integers. It is required that N1 <= N2 <= N3 <= + ... <= Nn for this function to work correctly. This is because a binary search + is used (very fast). URL: https://dev.mysql.com/doc/refman/8.3/en/comparison-operators.html' + examples: [] + - name: ISNULL + category_id: comparison_operators + category_label: Comparison Operators + signature: + display: ISNULL(expr) + args: + - name: expr + optional: false + type: any + tags: [] + aliases: [] + summary: If expr is NULL, ISNULL() returns 1, otherwise it returns 0. + description: 'If expr is NULL, ISNULL() returns 1, otherwise it returns 0. URL: + https://dev.mysql.com/doc/refman/8.3/en/comparison-operators.html' + examples: [] + - name: IS_FREE_LOCK + category_id: locking_functions + category_label: Locking Functions + signature: + display: IS_FREE_LOCK(str) + args: + - name: str + optional: false + type: any + tags: [] + aliases: [] + summary: Checks whether the lock named str is free to use (that is, not locked). + description: 'Checks whether the lock named str is free to use (that is, not locked). + Returns 1 if the lock is free (no one is using the lock), 0 if the lock is in + use, and NULL if an error occurs (such as an incorrect argument). URL: https://dev.mysql.com/doc/refman/8.3/en/locking-functions.html' + examples: [] + - name: IS_IPV4 + category_id: miscellaneous_functions + category_label: Miscellaneous Functions + signature: + display: IS_IPV4(expr) + args: + - name: expr + optional: false + type: any + tags: [] + aliases: [] + summary: Returns 1 if the argument is a valid IPv4 address specified as a + description: 'Returns 1 if the argument is a valid IPv4 address specified as a + string, 0 otherwise. Returns NULL if expr is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/miscellaneous-functions.html' + examples: [] + - name: IS_IPV4_COMPAT + category_id: miscellaneous_functions + category_label: Miscellaneous Functions + signature: + display: IS_IPV4_COMPAT(expr) + args: + - name: expr + optional: false + type: any + tags: [] + aliases: [] + summary: This function takes an IPv6 address represented in numeric form as a + description: 'This function takes an IPv6 address represented in numeric form + as a binary string, as returned by INET6_ATON(). It returns 1 if the argument + is a valid IPv4-compatible IPv6 address, 0 otherwise (unless expr is NULL, in + which case the function returns NULL). IPv4-compatible addresses have the form + ::ipv4_address. URL: https://dev.mysql.com/doc/refman/8.3/en/miscellaneous-functions.html' + examples: [] + - name: IS_IPV4_MAPPED + category_id: miscellaneous_functions + category_label: Miscellaneous Functions + signature: + display: IS_IPV4_MAPPED(expr) + args: + - name: expr + optional: false + type: any + tags: [] + aliases: [] + summary: This function takes an IPv6 address represented in numeric form as a + description: 'This function takes an IPv6 address represented in numeric form + as a binary string, as returned by INET6_ATON(). It returns 1 if the argument + is a valid IPv4-mapped IPv6 address, 0 otherwise, unless expr is NULL, in which + case the function returns NULL. IPv4-mapped addresses have the form ::ffff:ipv4_address. + URL: https://dev.mysql.com/doc/refman/8.3/en/miscellaneous-functions.html' + examples: [] + - name: IS_IPV6 + category_id: miscellaneous_functions + category_label: Miscellaneous Functions + signature: + display: IS_IPV6(expr) + args: + - name: expr + optional: false + type: any + tags: [] + aliases: [] + summary: Returns 1 if the argument is a valid IPv6 address specified as a + description: 'Returns 1 if the argument is a valid IPv6 address specified as a + string, 0 otherwise, unless expr is NULL, in which case the function returns + NULL. This function does not consider IPv4 addresses to be valid IPv6 addresses. + URL: https://dev.mysql.com/doc/refman/8.3/en/miscellaneous-functions.html' + examples: [] + - name: IS_USED_LOCK + category_id: locking_functions + category_label: Locking Functions + signature: + display: IS_USED_LOCK(str) + args: + - name: str + optional: false + type: any + tags: [] + aliases: [] + summary: Checks whether the lock named str is in use (that is, locked). + description: 'Checks whether the lock named str is in use (that is, locked). If + so, it returns the connection identifier of the client session that holds the + lock. Otherwise, it returns NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/locking-functions.html' + examples: [] + - name: IS_UUID + category_id: miscellaneous_functions + category_label: Miscellaneous Functions + signature: + display: IS_UUID(string_uuid) + args: + - name: string_uuid + optional: false + type: any + tags: [] + aliases: [] + summary: Returns 1 if the argument is a valid string-format UUID, 0 if the + description: 'Returns 1 if the argument is a valid string-format UUID, 0 if the + argument is not a valid UUID, and NULL if the argument is NULL. "Valid" means + that the value is in a format that can be parsed. That is, it has the correct + length and contains only the permitted characters (hexadecimal digits in any + lettercase and, optionally, dashes and curly braces). This format is most common: + aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee These other formats are also permitted: + aaaaaaaabbbbccccddddeeeeeeeeeeee {aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee} For + the meanings of fields within the value, see the UUID() function description. + URL: https://dev.mysql.com/doc/refman/8.3/en/miscellaneous-functions.html' + examples: [] + - name: JOIN + category_id: data_manipulation + category_label: Data Manipulation + signature: + display: JOIN(t2, t3, t4) + args: + - name: t2 + optional: false + type: any + - name: t3 + optional: false + type: any + - name: t4 + optional: false + type: any + tags: [] + aliases: [] + summary: ON (t2.a = t1.a AND t3.b = t1.b AND t4.c = t1.c) + description: "ON (t2.a = t1.a AND t3.b = t1.b AND t4.c = t1.c)\n\nis equivalent\ + \ to:\n\nSELECT * FROM t1 LEFT JOIN (t2 CROSS JOIN t3 CROSS JOIN t4)\n \ + \ ON (t2.a = t1.a AND t3.b = t1.b AND t4.c = t1.c)\n\nIn MySQL, JOIN,\ + \ CROSS JOIN, and INNER JOIN are syntactic equivalents\n(they can replace each\ + \ other). In standard SQL, they are not\nequivalent. INNER JOIN is used with\ + \ an ON clause, CROSS JOIN is used\notherwise.\n\nIn general, parentheses can\ + \ be ignored in join expressions containing\nonly inner join operations. MySQL\ + \ also supports nested joins. See\nhttps://dev.mysql.com/doc/refman/8.3/en/nested-join-optimization.html.\n\ + \nIndex hints can be specified to affect how the MySQL optimizer makes\nuse\ + \ of indexes. For more information, see\nhttps://dev.mysql.com/doc/refman/8.3/en/index-hints.html.\ + \ Optimizer\nhints and the optimizer_switch system variable are other ways to\n\ + influence optimizer use of indexes. See\nhttps://dev.mysql.com/doc/refman/8.3/en/optimizer-hints.html,\ + \ and\nhttps://dev.mysql.com/doc/refman/8.3/en/switchable-optimizations.html.\n\ + \nURL: https://dev.mysql.com/doc/refman/8.3/en/join.html" + examples: [] + - name: JSON_ARRAY + category_id: mbr_functions + category_label: MBR Functions + signature: + display: JSON_ARRAY([val[, val] ...]) + args: + - name: '[val[' + optional: false + type: any + - name: val] ...] + optional: false + type: any + tags: [] + aliases: [] + summary: Evaluates a (possibly empty) list of values and returns a JSON array + description: 'Evaluates a (possibly empty) list of values and returns a JSON array + containing those values. URL: https://dev.mysql.com/doc/refman/8.3/en/json-creation-functions.html' + examples: [] + - name: JSON_ARRAYAGG + category_id: aggregate_functions_and_modifiers + category_label: Aggregate Functions and Modifiers + signature: + display: JSON_ARRAYAGG(col_or_expr) + args: + - name: col_or_expr + optional: false + type: any + tags: [] + aliases: [] + summary: Aggregates a result set as a single JSON array whose elements consist + description: 'Aggregates a result set as a single JSON array whose elements consist + of the rows. The order of elements in this array is undefined. The function + acts on a column or an expression that evaluates to a single value. Returns + NULL if the result contains no rows, or in the event of an error. If col_or_expr + is NULL, the function returns an array of JSON [null] elements. This function + executes as a window function if over_clause is present. over_clause is as described + in https://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html. URL: + https://dev.mysql.com/doc/refman/8.3/en/aggregate-functions.html' + examples: [] + - name: JSON_ARRAY_APPEND + category_id: mbr_functions + category_label: MBR Functions + signature: + display: JSON_ARRAY_APPEND(json_doc, path, val[, path, val] ...) + args: + - name: json_doc + optional: false + type: any + - name: path + optional: false + type: any + - name: val[ + optional: false + type: any + - name: path + optional: false + type: any + - name: val] ... + optional: false + type: any + tags: [] + aliases: [] + summary: Appends values to the end of the indicated arrays within a JSON + description: 'Appends values to the end of the indicated arrays within a JSON + document and returns the result. Returns NULL if any argument is NULL. An error + occurs if the json_doc argument is not a valid JSON document or any path argument + is not a valid path expression or contains a * or ** wildcard. The path-value + pairs are evaluated left to right. The document produced by evaluating one pair + becomes the new value against which the next pair is evaluated. If a path selects + a scalar or object value, that value is autowrapped within an array and the + new value is added to that array. Pairs for which the path does not identify + any value in the JSON document are ignored. URL: https://dev.mysql.com/doc/refman/8.3/en/json-modification-functions.html' + examples: [] + - name: JSON_ARRAY_INSERT + category_id: mbr_functions + category_label: MBR Functions + signature: + display: JSON_ARRAY_INSERT(json_doc, path, val[, path, val] ...) + args: + - name: json_doc + optional: false + type: any + - name: path + optional: false + type: any + - name: val[ + optional: false + type: any + - name: path + optional: false + type: any + - name: val] ... + optional: false + type: any + tags: [] + aliases: [] + summary: Updates a JSON document, inserting into an array within the document + description: 'Updates a JSON document, inserting into an array within the document + and returning the modified document. Returns NULL if any argument is NULL. An + error occurs if the json_doc argument is not a valid JSON document or any path + argument is not a valid path expression or contains a * or ** wildcard or does + not end with an array element identifier. The path-value pairs are evaluated + left to right. The document produced by evaluating one pair becomes the new + value against which the next pair is evaluated. Pairs for which the path does + not identify any array in the JSON document are ignored. If a path identifies + an array element, the corresponding value is inserted at that element position, + shifting any following values to the right. If a path identifies an array position + past the end of an array, the value is inserted at the end of the array. URL: + https://dev.mysql.com/doc/refman/8.3/en/json-modification-functions.html' + examples: [] + - name: JSON_CONTAINS + category_id: mbr_functions + category_label: MBR Functions + signature: + display: JSON_CONTAINS(target, candidate[, path]) + args: + - name: target + optional: false + type: any + - name: candidate[ + optional: false + type: any + - name: path] + optional: false + type: any + tags: [] + aliases: [] + summary: Indicates by returning 1 or 0 whether a given candidate JSON document + description: "Indicates by returning 1 or 0 whether a given candidate JSON document\n\ + is contained within a target JSON document, or---if a path argument was\nsupplied---whether\ + \ the candidate is found at a specific path within the\ntarget. Returns NULL\ + \ if any argument is NULL, or if the path argument\ndoes not identify a section\ + \ of the target document. An error occurs if\ntarget or candidate is not a valid\ + \ JSON document, or if the path\nargument is not a valid path expression or\ + \ contains a * or ** wildcard.\n\nTo check only whether any data exists at the\ + \ path, use\nJSON_CONTAINS_PATH() instead.\n\nThe following rules define containment:\n\ + \no A candidate scalar is contained in a target scalar if and only if\n they\ + \ are comparable and are equal. Two scalar values are comparable\n if they\ + \ have the same JSON_TYPE() types, with the exception that\n values of types\ + \ INTEGER and DECIMAL are also comparable to each\n other.\n\no A candidate\ + \ array is contained in a target array if and only if every\n element in the\ + \ candidate is contained in some element of the target.\n\no A candidate nonarray\ + \ is contained in a target array if and only if\n the candidate is contained\ + \ in some element of the target.\n\no A candidate object is contained in a target\ + \ object if and only if for\n each key in the candidate there is a key with\ + \ the same name in the\n target and the value associated with the candidate\ + \ key is contained\n in the value associated with the target key.\n\nOtherwise,\ + \ the candidate value is not contained in the target document.\n\nQueries using\ + \ JSON_CONTAINS() on InnoDB tables can be optimized using\nmulti-valued indexes;\ + \ see\nhttps://dev.mysql.com/doc/refman/8.3/en/create-index.html#create-index-\n\ + multi-valued, for more information.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/json-search-functions.html" + examples: [] + - name: JSON_CONTAINS_PATH + category_id: mbr_functions + category_label: MBR Functions + signature: + display: JSON_CONTAINS_PATH(json_doc, one_or_all, path[, path] ...) + args: + - name: json_doc + optional: false + type: any + - name: one_or_all + optional: false + type: any + - name: path[ + optional: false + type: any + - name: path] ... + optional: false + type: any + tags: [] + aliases: [] + summary: Returns 0 or 1 to indicate whether a JSON document contains data at a + description: "Returns 0 or 1 to indicate whether a JSON document contains data\ + \ at a\ngiven path or paths. Returns NULL if any argument is NULL. An error\n\ + occurs if the json_doc argument is not a valid JSON document, any path\nargument\ + \ is not a valid path expression, or one_or_all is not 'one' or\n'all'.\n\n\ + To check for a specific value at a path, use JSON_CONTAINS() instead.\n\nThe\ + \ return value is 0 if no specified path exists within the document.\nOtherwise,\ + \ the return value depends on the one_or_all argument:\n\no 'one': 1 if at least\ + \ one path exists within the document, 0\n otherwise.\n\no 'all': 1 if all\ + \ paths exist within the document, 0 otherwise.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/json-search-functions.html" + examples: [] + - name: JSON_DEPTH + category_id: mbr_functions + category_label: MBR Functions + signature: + display: JSON_DEPTH(json_doc) + args: + - name: json_doc + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the maximum depth of a JSON document. + description: 'Returns the maximum depth of a JSON document. Returns NULL if the + argument is NULL. An error occurs if the argument is not a valid JSON document. + An empty array, empty object, or scalar value has depth 1. A nonempty array + containing only elements of depth 1 or nonempty object containing only member + values of depth 1 has depth 2. Otherwise, a JSON document has depth greater + than 2. URL: https://dev.mysql.com/doc/refman/8.3/en/json-attribute-functions.html' + examples: [] + - name: JSON_EXTRACT + category_id: mbr_functions + category_label: MBR Functions + signature: + display: JSON_EXTRACT(json_doc, path[, path] ...) + args: + - name: json_doc + optional: false + type: any + - name: path[ + optional: false + type: any + - name: path] ... + optional: false + type: any + tags: [] + aliases: [] + summary: Returns data from a JSON document, selected from the parts of the + description: 'Returns data from a JSON document, selected from the parts of the + document matched by the path arguments. Returns NULL if any argument is NULL + or no paths locate a value in the document. An error occurs if the json_doc + argument is not a valid JSON document or any path argument is not a valid path + expression. The return value consists of all values matched by the path arguments. + If it is possible that those arguments could return multiple values, the matched + values are autowrapped as an array, in the order corresponding to the paths + that produced them. Otherwise, the return value is the single matched value. + URL: https://dev.mysql.com/doc/refman/8.3/en/json-search-functions.html' + examples: [] + - name: JSON_INSERT + category_id: mbr_functions + category_label: MBR Functions + signature: + display: JSON_INSERT(json_doc, path, val[, path, val] ...) + args: + - name: json_doc + optional: false + type: any + - name: path + optional: false + type: any + - name: val[ + optional: false + type: any + - name: path + optional: false + type: any + - name: val] ... + optional: false + type: any + tags: [] + aliases: [] + summary: Inserts data into a JSON document and returns the result. + description: "Inserts data into a JSON document and returns the result. Returns\ + \ NULL\nif any argument is NULL. An error occurs if the json_doc argument is\n\ + not a valid JSON document or any path argument is not a valid path\nexpression\ + \ or contains a * or ** wildcard.\n\nThe path-value pairs are evaluated left\ + \ to right. The document produced\nby evaluating one pair becomes the new value\ + \ against which the next\npair is evaluated.\n\nA path-value pair for an existing\ + \ path in the document is ignored and\ndoes not overwrite the existing document\ + \ value. A path-value pair for a\nnonexisting path in the document adds the\ + \ value to the document if the\npath identifies one of these types of values:\n\ + \no A member not present in an existing object. The member is added to\n the\ + \ object and associated with the new value.\n\no A position past the end of\ + \ an existing array. The array is extended\n with the new value. If the existing\ + \ value is not an array, it is\n autowrapped as an array, then extended with\ + \ the new value.\n\nOtherwise, a path-value pair for a nonexisting path in the\ + \ document is\nignored and has no effect.\n\nFor a comparison of JSON_INSERT(),\ + \ JSON_REPLACE(), and JSON_SET(), see\nthe discussion of JSON_SET().\n\nURL:\ + \ https://dev.mysql.com/doc/refman/8.3/en/json-modification-functions.html" + examples: [] + - name: JSON_KEYS + category_id: mbr_functions + category_label: MBR Functions + signature: + display: JSON_KEYS(json_doc[, path]) + args: + - name: json_doc[ + optional: false + type: any + - name: path] + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the keys from the top-level value of a JSON object as a JSON + description: 'Returns the keys from the top-level value of a JSON object as a + JSON array, or, if a path argument is given, the top-level keys from the selected + path. Returns NULL if any argument is NULL, the json_doc argument is not an + object, or path, if given, does not locate an object. An error occurs if the + json_doc argument is not a valid JSON document or the path argument is not a + valid path expression or contains a * or ** wildcard. The result array is empty + if the selected object is empty. If the top-level value has nested subobjects, + the return value does not include keys from those subobjects. URL: https://dev.mysql.com/doc/refman/8.3/en/json-search-functions.html' + examples: [] + - name: JSON_LENGTH + category_id: mbr_functions + category_label: MBR Functions + signature: + display: JSON_LENGTH(json_doc[, path]) + args: + - name: json_doc[ + optional: false + type: any + - name: path] + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the length of a JSON document, or, if a path argument is given, + description: 'Returns the length of a JSON document, or, if a path argument is + given, the length of the value within the document identified by the path. Returns + NULL if any argument is NULL or the path argument does not identify a value + in the document. An error occurs if the json_doc argument is not a valid JSON + document or the path argument is not a valid path expression. The length of + a document is determined as follows: o The length of a scalar is 1. o The length + of an array is the number of array elements. o The length of an object is the + number of object members. o The length does not count the length of nested arrays + or objects. URL: https://dev.mysql.com/doc/refman/8.3/en/json-attribute-functions.html' + examples: [] + - name: JSON_MERGE + category_id: mbr_functions + category_label: MBR Functions + signature: + display: JSON_MERGE(json_doc, json_doc[, json_doc] ...) + args: + - name: json_doc + optional: false + type: any + - name: json_doc[ + optional: false + type: any + - name: json_doc] ... + optional: false + type: any + tags: [] + aliases: [] + summary: Deprecated synonym for JSON_MERGE_PRESERVE(). + description: 'Deprecated synonym for JSON_MERGE_PRESERVE(). URL: https://dev.mysql.com/doc/refman/8.3/en/json-modification-functions.html' + examples: [] + - name: JSON_OBJECT + category_id: mbr_functions + category_label: MBR Functions + signature: + display: JSON_OBJECT([key, val[, key, val] ...]) + args: + - name: '[key' + optional: false + type: any + - name: val[ + optional: false + type: any + - name: key + optional: false + type: any + - name: val] ...] + optional: false + type: any + tags: [] + aliases: [] + summary: Evaluates a (possibly empty) list of key-value pairs and returns a JSON + description: 'Evaluates a (possibly empty) list of key-value pairs and returns + a JSON object containing those pairs. An error occurs if any key name is NULL + or the number of arguments is odd. URL: https://dev.mysql.com/doc/refman/8.3/en/json-creation-functions.html' + examples: [] + - name: JSON_OBJECTAGG + category_id: aggregate_functions_and_modifiers + category_label: Aggregate Functions and Modifiers + signature: + display: JSON_OBJECTAGG(key, value) + args: + - name: key + optional: false + type: any + - name: value + optional: false + type: any + tags: [] + aliases: [] + summary: Takes two column names or expressions as arguments, the first of these + description: 'Takes two column names or expressions as arguments, the first of + these being used as a key and the second as a value, and returns a JSON object + containing key-value pairs. Returns NULL if the result contains no rows, or + in the event of an error. An error occurs if any key name is NULL or the number + of arguments is not equal to 2. This function executes as a window function + if over_clause is present. over_clause is as described in https://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html. + URL: https://dev.mysql.com/doc/refman/8.3/en/aggregate-functions.html' + examples: [] + - name: JSON_OVERLAPS + category_id: mbr_functions + category_label: MBR Functions + signature: + display: JSON_OVERLAPS(json_doc1, json_doc2) + args: + - name: json_doc1 + optional: false + type: any + - name: json_doc2 + optional: false + type: any + tags: [] + aliases: [] + summary: Compares two JSON documents. + description: 'Compares two JSON documents. Returns true (1) if the two document + have any key-value pairs or array elements in common. If both arguments are + scalars, the function performs a simple equality test. If either argument is + NULL, the function returns NULL. This function serves as counterpart to JSON_CONTAINS(), + which requires all elements of the array searched for to be present in the array + searched in. Thus, JSON_CONTAINS() performs an AND operation on search keys, + while JSON_OVERLAPS() performs an OR operation. Queries on JSON columns of InnoDB + tables using JSON_OVERLAPS() in the WHERE clause can be optimized using multi-valued + indexes. https://dev.mysql.com/doc/refman/8.3/en/create-index.html#create-index- + multi-valued, provides detailed information and examples. URL: https://dev.mysql.com/doc/refman/8.3/en/json-search-functions.html' + examples: [] + - name: JSON_PRETTY + category_id: mbr_functions + category_label: MBR Functions + signature: + display: JSON_PRETTY(json_val) + args: + - name: json_val + optional: false + type: any + tags: [] + aliases: [] + summary: Provides pretty-printing of JSON values similar to that implemented in + description: "Provides pretty-printing of JSON values similar to that implemented\ + \ in\nPHP and by other languages and database systems. The value supplied\n\ + must be a JSON value or a valid string representation of a JSON value.\nExtraneous\ + \ whitespaces and newlines present in this value have no\neffect on the output.\ + \ For a NULL value, the function returns NULL. If\nthe value is not a JSON document,\ + \ or if it cannot be parsed as one, the\nfunction fails with an error.\n\nFormatting\ + \ of the output from this function adheres to the following\nrules:\n\no Each\ + \ array element or object member appears on a separate line,\n indented by\ + \ one additional level as compared to its parent.\n\no Each level of indentation\ + \ adds two leading spaces.\n\no A comma separating individual array elements\ + \ or object members is\n printed before the newline that separates the two\ + \ elements or\n members.\n\no The key and the value of an object member are\ + \ separated by a colon\n followed by a space (': ').\n\no An empty object or\ + \ array is printed on a single line. No space is\n printed between the opening\ + \ and closing brace.\n\no Special characters in string scalars and key names\ + \ are escaped\n employing the same rules used by the JSON_QUOTE() function.\n\ + \nURL: https://dev.mysql.com/doc/refman/8.3/en/json-utility-functions.html" + examples: [] + - name: JSON_QUOTE + category_id: mbr_functions + category_label: MBR Functions + signature: + display: JSON_QUOTE(string) + args: + - name: string + optional: false + type: any + tags: [] + aliases: [] + summary: Quotes a string as a JSON value by wrapping it with double quote + description: 'Quotes a string as a JSON value by wrapping it with double quote + characters and escaping interior quote and other characters, then returning + the result as a utf8mb4 string. Returns NULL if the argument is NULL. This function + is typically used to produce a valid JSON string literal for inclusion within + a JSON document. Certain special characters are escaped with backslashes per + the escape sequences shown in https://dev.mysql.com/doc/refman/8.3/en/json-modification-functions.html + #json-unquote-character-escape-sequences. URL: https://dev.mysql.com/doc/refman/8.3/en/json-creation-functions.html' + examples: [] + - name: JSON_REMOVE + category_id: mbr_functions + category_label: MBR Functions + signature: + display: JSON_REMOVE(json_doc, path[, path] ...) + args: + - name: json_doc + optional: false + type: any + - name: path[ + optional: false + type: any + - name: path] ... + optional: false + type: any + tags: [] + aliases: [] + summary: Removes data from a JSON document and returns the result. + description: 'Removes data from a JSON document and returns the result. Returns + NULL if any argument is NULL. An error occurs if the json_doc argument is not + a valid JSON document or any path argument is not a valid path expression or + is $ or contains a * or ** wildcard. The path arguments are evaluated left to + right. The document produced by evaluating one path becomes the new value against + which the next path is evaluated. It is not an error if the element to be removed + does not exist in the document; in that case, the path does not affect the document. + URL: https://dev.mysql.com/doc/refman/8.3/en/json-modification-functions.html' + examples: [] + - name: JSON_REPLACE + category_id: mbr_functions + category_label: MBR Functions + signature: + display: JSON_REPLACE(json_doc, path, val[, path, val] ...) + args: + - name: json_doc + optional: false + type: any + - name: path + optional: false + type: any + - name: val[ + optional: false + type: any + - name: path + optional: false + type: any + - name: val] ... + optional: false + type: any + tags: [] + aliases: [] + summary: Replaces existing values in a JSON document and returns the result. + description: 'Replaces existing values in a JSON document and returns the result. + Returns NULL if any argument is NULL. An error occurs if the json_doc argument + is not a valid JSON document or any path argument is not a valid path expression + or contains a * or ** wildcard. The path-value pairs are evaluated left to right. + The document produced by evaluating one pair becomes the new value against which + the next pair is evaluated. A path-value pair for an existing path in the document + overwrites the existing document value with the new value. A path-value pair + for a nonexisting path in the document is ignored and has no effect. The optimizer + can perform a partial, in-place update of a JSON column instead of removing + the old document and writing the new document in its entirety to the column. + This optimization can be performed for an update statement that uses the JSON_REPLACE() + function and meets the conditions outlined in https://dev.mysql.com/doc/refman/8.3/en/json.html#json-partial-updates. + For a comparison of JSON_INSERT(), JSON_REPLACE(), and JSON_SET(), see the discussion + of JSON_SET(). URL: https://dev.mysql.com/doc/refman/8.3/en/json-modification-functions.html' + examples: [] + - name: JSON_SCHEMA_VALID + category_id: mbr_functions + category_label: MBR Functions + signature: + display: JSON_SCHEMA_VALID(schema,document) + args: + - name: schema + optional: false + type: any + - name: document + optional: false + type: any + tags: [] + aliases: [] + summary: Validates a JSON document against a JSON schema. + description: 'Validates a JSON document against a JSON schema. Both schema and + document are required. The schema must be a valid JSON object; the document + must be a valid JSON document. Provided that these conditions are met: If the + document validates against the schema, the function returns true (1); otherwise, + it returns false (0). URL: https://dev.mysql.com/doc/refman/8.3/en/json-validation-functions.html' + examples: [] + - name: JSON_SCHEMA_VALIDATION_REPORT + category_id: mbr_functions + category_label: MBR Functions + signature: + display: JSON_SCHEMA_VALIDATION_REPORT(schema,document) + args: + - name: schema + optional: false + type: any + - name: document + optional: false + type: any + tags: [] + aliases: [] + summary: Validates a JSON document against a JSON schema. + description: "Validates a JSON document against a JSON schema. Both schema and\n\ + document are required. As with JSON_VALID_SCHEMA(), the schema must be\na valid\ + \ JSON object, and the document must be a valid JSON document.\nProvided that\ + \ these conditions are met, the function returns a report,\nas a JSON document,\ + \ on the outcome of the validation. If the JSON\ndocument is considered valid\ + \ according to the JSON Schema, the function\nreturns a JSON object with one\ + \ property valid having the value \"true\".\nIf the JSON document fails validation,\ + \ the function returns a JSON\nobject which includes the properties listed here:\n\ + \no valid: Always \"false\" for a failed schema validation\n\no reason: A human-readable\ + \ string containing the reason for the failure\n\no schema-location: A JSON\ + \ pointer URI fragment identifier indicating\n where in the JSON schema the\ + \ validation failed (see Note following\n this list)\n\no document-location:\ + \ A JSON pointer URI fragment identifier indicating\n where in the JSON document\ + \ the validation failed (see Note following\n this list)\n\no schema-failed-keyword:\ + \ A string containing the name of the keyword or\n property in the JSON schema\ + \ that was violated\n\n*Note*:\n\nJSON pointer URI fragment identifiers are\ + \ defined in RFC 6901 -\nJavaScript Object Notation (JSON) Pointer\n(https://tools.ietf.org/html/rfc6901#page-5).\ + \ (These are not the same\nas the JSON path notation used by JSON_EXTRACT()\ + \ and other MySQL JSON\nfunctions.) In this notation, # represents the entire\ + \ document, and\n#/myprop represents the portion of the document included in\ + \ the\ntop-level property named myprop. See the specification just cited and\n\ + the examples shown later in this section for more information.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/json-validation-functions.html" + examples: [] + - name: JSON_SEARCH + category_id: mbr_functions + category_label: MBR Functions + signature: + display: JSON_SEARCH(json_doc, one_or_all, search_str[, escape_char[, path] + ...]) + args: + - name: json_doc + optional: false + type: any + - name: one_or_all + optional: false + type: any + - name: search_str[ + optional: false + type: any + - name: escape_char[ + optional: false + type: any + - name: path] ...] + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the path to the given string within a JSON document. + description: "Returns the path to the given string within a JSON document. Returns\n\ + NULL if any of the json_doc, search_str, or path arguments are NULL; no\npath\ + \ exists within the document; or search_str is not found. An error\noccurs if\ + \ the json_doc argument is not a valid JSON document, any path\nargument is\ + \ not a valid path expression, one_or_all is not 'one' or\n'all', or escape_char\ + \ is not a constant expression.\n\nThe one_or_all argument affects the search\ + \ as follows:\n\no 'one': The search terminates after the first match and returns\ + \ one\n path string. It is undefined which match is considered first.\n\no\ + \ 'all': The search returns all matching path strings such that no\n duplicate\ + \ paths are included. If there are multiple strings, they are\n autowrapped\ + \ as an array. The order of the array elements is\n undefined.\n\nWithin the\ + \ search_str search string argument, the % and _ characters\nwork as for the\ + \ LIKE operator: % matches any number of characters\n(including zero characters),\ + \ and _ matches exactly one character.\n\nTo specify a literal % or _ character\ + \ in the search string, precede it\nby the escape character. The default is\ + \ \\ if the escape_char argument\nis missing or NULL. Otherwise, escape_char\ + \ must be a constant that is\nempty or one character.\n\nFor more information\ + \ about matching and escape character behavior, see\nthe description of LIKE\ + \ in\nhttps://dev.mysql.com/doc/refman/8.3/en/string-comparison-functions.html\n\ + . For escape character handling, a difference from the LIKE behavior\nis that\ + \ the escape character for JSON_SEARCH() must evaluate to a\nconstant at compile\ + \ time, not just at execution time. For example, if\nJSON_SEARCH() is used in\ + \ a prepared statement and the escape_char\nargument is supplied using a ? parameter,\ + \ the parameter value might be\nconstant at execution time, but is not at compile\ + \ time.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/json-search-functions.html" + examples: [] + - name: JSON_SET + category_id: mbr_functions + category_label: MBR Functions + signature: + display: JSON_SET(json_doc, path, val[, path, val] ...) + args: + - name: json_doc + optional: false + type: any + - name: path + optional: false + type: any + - name: val[ + optional: false + type: any + - name: path + optional: false + type: any + - name: val] ... + optional: false + type: any + tags: [] + aliases: [] + summary: Inserts or updates data in a JSON document and returns the result. + description: "Inserts or updates data in a JSON document and returns the result.\n\ + Returns NULL if json_doc or path is NULL, or if path, when given, does\nnot\ + \ locate an object. Otherwise, an error occurs if the json_doc\nargument is\ + \ not a valid JSON document or any path argument is not a\nvalid path expression\ + \ or contains a * or ** wildcard.\n\nThe path-value pairs are evaluated left\ + \ to right. The document produced\nby evaluating one pair becomes the new value\ + \ against which the next\npair is evaluated.\n\nA path-value pair for an existing\ + \ path in the document overwrites the\nexisting document value with the new\ + \ value. A path-value pair for a\nnonexisting path in the document adds the\ + \ value to the document if the\npath identifies one of these types of values:\n\ + \no A member not present in an existing object. The member is added to\n the\ + \ object and associated with the new value.\n\no A position past the end of\ + \ an existing array. The array is extended\n with the new value. If the existing\ + \ value is not an array, it is\n autowrapped as an array, then extended with\ + \ the new value.\n\nOtherwise, a path-value pair for a nonexisting path in the\ + \ document is\nignored and has no effect.\n\nThe optimizer can perform a partial,\ + \ in-place update of a JSON column\ninstead of removing the old document and\ + \ writing the new document in\nits entirety to the column. This optimization\ + \ can be performed for an\nupdate statement that uses the JSON_SET() function\ + \ and meets the\nconditions outlined in\nhttps://dev.mysql.com/doc/refman/8.3/en/json.html#json-partial-updates.\n\ + \nThe JSON_SET(), JSON_INSERT(), and JSON_REPLACE() functions are\nrelated:\n\ + \no JSON_SET() replaces existing values and adds nonexisting values.\n\no JSON_INSERT()\ + \ inserts values without replacing existing values.\n\no JSON_REPLACE() replaces\ + \ only existing values.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/json-modification-functions.html" + examples: [] + - name: JSON_STORAGE_FREE + category_id: mbr_functions + category_label: MBR Functions + signature: + display: JSON_STORAGE_FREE(json_val) + args: + - name: json_val + optional: false + type: any + tags: [] + aliases: [] + summary: For a JSON column value, this function shows how much storage space was + description: 'For a JSON column value, this function shows how much storage space + was freed in its binary representation after it was updated in place using JSON_SET(), + JSON_REPLACE(), or JSON_REMOVE(). The argument can also be a valid JSON document + or a string which can be parsed as one---either as a literal value or as the + value of a user variable---in which case the function returns 0. It returns + a positive, nonzero value if the argument is a JSON column value which has been + updated as described previously, such that its binary representation takes up + less space than it did prior to the update. For a JSON column which has been + updated such that its binary representation is the same as or larger than before, + or if the update was not able to take advantage of a partial update, it returns + 0; it returns NULL if the argument is NULL. If json_val is not NULL, and neither + is a valid JSON document nor can be successfully parsed as one, an error results. + URL: https://dev.mysql.com/doc/refman/8.3/en/json-utility-functions.html' + examples: [] + - name: JSON_STORAGE_SIZE + category_id: mbr_functions + category_label: MBR Functions + signature: + display: JSON_STORAGE_SIZE(json_val) + args: + - name: json_val + optional: false + type: any + tags: [] + aliases: [] + summary: This function returns the number of bytes used to store the binary + description: 'This function returns the number of bytes used to store the binary + representation of a JSON document. When the argument is a JSON column, this + is the space used to store the JSON document as it was inserted into the column, + prior to any partial updates that may have been performed on it afterwards. + json_val must be a valid JSON document or a string which can be parsed as one. + In the case where it is string, the function returns the amount of storage space + in the JSON binary representation that is created by parsing the string as JSON + and converting it to binary. It returns NULL if the argument is NULL. An error + results when json_val is not NULL, and is not---or cannot be successfully parsed + as---a JSON document. URL: https://dev.mysql.com/doc/refman/8.3/en/json-utility-functions.html' + examples: [] + - name: JSON_TABLE + category_id: mbr_functions + category_label: MBR Functions + signature: + display: JSON_TABLE(expr, path COLUMNS (column_list) + args: + - name: expr + optional: false + type: any + - name: path COLUMNS (column_list + optional: false + type: any + tags: [] + aliases: [] + summary: Extracts data from a JSON document and returns it as a relational table + description: "Extracts data from a JSON document and returns it as a relational\ + \ table\nhaving the specified columns. The complete syntax for this function\ + \ is\nshown here:\n\nJSON_TABLE(\n expr,\n path COLUMNS (column_list)\n\ + ) [AS] alias\n\ncolumn_list:\n column[, column][, ...]\n\ncolumn:\n \ + \ name FOR ORDINALITY\n | name type PATH string path [on_empty] [on_error]\n\ + \ | name type EXISTS PATH string path\n | NESTED [PATH] path COLUMNS\ + \ (column_list)\n\non_empty:\n {NULL | DEFAULT json_string | ERROR} ON EMPTY\n\ + \non_error:\n {NULL | DEFAULT json_string | ERROR} ON ERROR\n\nexpr: This\ + \ is an expression that returns JSON data. This can be a\nconstant ('{\"a\"\ + :1}'), a column (t1.json_data, given table t1 specified\nprior to JSON_TABLE()\ + \ in the FROM clause), or a function call\n(JSON_EXTRACT(t1.json_data,'$.post.comments')).\n\ + \npath: A JSON path expression, which is applied to the data source. We\nrefer\ + \ to the JSON value matching the path as the row source; this is\nused to generate\ + \ a row of relational data. The COLUMNS clause evaluates\nthe row source, finds\ + \ specific JSON values within the row source, and\nreturns those JSON values\ + \ as SQL values in individual columns of a row\nof relational data.\n\nThe alias\ + \ is required. The usual rules for table aliases apply (see\nhttps://dev.mysql.com/doc/refman/8.3/en/identifiers.html).\n\ + \nThis function compares column names in case-insensitive fashion.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/json-table-functions.html" + examples: [] + - name: JSON_TYPE + category_id: mbr_functions + category_label: MBR Functions + signature: + display: JSON_TYPE(json_val) + args: + - name: json_val + optional: false + type: any + tags: [] + aliases: [] + summary: Returns a utf8mb4 string indicating the type of a JSON value. + description: "Returns a utf8mb4 string indicating the type of a JSON value. This\ + \ can\nbe an object, an array, or a scalar type, as shown here:\n\nmysql> SET\ + \ @j = '{\"a\": [10, true]}';\nmysql> SELECT JSON_TYPE(@j);\n+---------------+\n\ + | JSON_TYPE(@j) |\n+---------------+\n| OBJECT |\n+---------------+\n\ + mysql> SELECT JSON_TYPE(JSON_EXTRACT(@j, '$.a'));\n+------------------------------------+\n\ + | JSON_TYPE(JSON_EXTRACT(@j, '$.a')) |\n+------------------------------------+\n\ + | ARRAY |\n+------------------------------------+\n\ + mysql> SELECT JSON_TYPE(JSON_EXTRACT(@j, '$.a[0]'));\n+---------------------------------------+\n\ + | JSON_TYPE(JSON_EXTRACT(@j, '$.a[0]')) |\n+---------------------------------------+\n\ + | INTEGER |\n+---------------------------------------+\n\ + mysql> SELECT JSON_TYPE(JSON_EXTRACT(@j, '$.a[1]'));\n+---------------------------------------+\n\ + | JSON_TYPE(JSON_EXTRACT(@j, '$.a[1]')) |\n+---------------------------------------+\n\ + | BOOLEAN |\n+---------------------------------------+\n\ + \nJSON_TYPE() returns NULL if the argument is NULL:\n\nmysql> SELECT JSON_TYPE(NULL);\n\ + +-----------------+\n| JSON_TYPE(NULL) |\n+-----------------+\n| NULL \ + \ |\n+-----------------+\n\nAn error occurs if the argument is not a valid\ + \ JSON value:\n\nmysql> SELECT JSON_TYPE(1);\nERROR 3146 (22032): Invalid data\ + \ type for JSON data in argument 1\nto function json_type; a JSON string or\ + \ JSON type is required.\n\nFor a non-NULL, non-error result, the following\ + \ list describes the\npossible JSON_TYPE() return values:\n\no Purely JSON types:\n\ + \n o OBJECT: JSON objects\n ..." + examples: [] + - name: JSON_UNQUOTE + category_id: mbr_functions + category_label: MBR Functions + signature: + display: JSON_UNQUOTE(json_val) + args: + - name: json_val + optional: false + type: any + tags: [] + aliases: [] + summary: Unquotes JSON value and returns the result as a utf8mb4 string. + description: 'Unquotes JSON value and returns the result as a utf8mb4 string. + Returns NULL if the argument is NULL. An error occurs if the value starts and + ends with double quotes but is not a valid JSON string literal. URL: https://dev.mysql.com/doc/refman/8.3/en/json-modification-functions.html' + examples: [] + - name: JSON_VALID + category_id: mbr_functions + category_label: MBR Functions + signature: + display: JSON_VALID(val) + args: + - name: val + optional: false + type: any + tags: [] + aliases: [] + summary: Returns 0 or 1 to indicate whether a value is valid JSON. + description: 'Returns 0 or 1 to indicate whether a value is valid JSON. Returns + NULL if the argument is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/json-attribute-functions.html' + examples: [] + - name: JSON_VALUE + category_id: mbr_functions + category_label: MBR Functions + signature: + display: JSON_VALUE(json_doc, path) + args: + - name: json_doc + optional: false + type: any + - name: path + optional: false + type: any + tags: [] + aliases: [] + summary: Extracts a value from a JSON document at the path given in the + description: "Extracts a value from a JSON document at the path given in the\n\ + specified document, and returns the extracted value, optionally\nconverting\ + \ it to a desired type. The complete syntax is shown here:\n\nJSON_VALUE(json_doc,\ + \ path [RETURNING type] [on_empty] [on_error])\n\non_empty:\n {NULL | ERROR\ + \ | DEFAULT value} ON EMPTY\n\non_error:\n {NULL | ERROR | DEFAULT value}\ + \ ON ERROR\n\njson_doc is a valid JSON document. If this is NULL, the function\n\ + returns NULL.\n\npath is a JSON path pointing to a location in the document.\ + \ This must\nbe a string literal value.\n\ntype is one of the following data\ + \ types:\n\no FLOAT\n\no DOUBLE\n\no DECIMAL\n\no SIGNED\n\no UNSIGNED\n\no\ + \ DATE\n\no TIME\n\no DATETIME\n\no YEAR\n\n YEAR values of one or two digits\ + \ are not supported.\n\no CHAR\n\no JSON\n\nThe types just listed are the same\ + \ as the (non-array) types supported\nby the CAST() function.\n\nIf not specified\ + \ by a RETURNING clause, the JSON_VALUE() function's\nreturn type is VARCHAR(512).\ + \ When no character set is specified for the\nreturn type, JSON_VALUE() uses\ + \ utf8mb4 with the binary collation, which\n ..." + examples: [] + - name: LAG + category_id: window_functions + category_label: Window Functions + signature: + display: LAG(expr [, N[, default]]) + args: + - name: expr [ + optional: false + type: any + - name: N[ + optional: false + type: any + - name: default]] + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the value of expr from the row that lags (precedes) the current + description: 'Returns the value of expr from the row that lags (precedes) the + current row by N rows within its partition. If there is no such row, the return + value is default. For example, if N is 3, the return value is default for the + first three rows. If N or default are missing, the defaults are 1 and NULL, + respectively. N must be a literal nonnegative integer. If N is 0, expr is evaluated + for the current row. N cannot be NULL, and must be an integer in the range 0 + to 263, inclusive, in any of the following forms: o an unsigned integer constant + literal o a positional parameter marker (?) o a user-defined variable o a local + variable in a stored routine over_clause is as described in https://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html. + null_treatment is as described in the section introduction. URL: https://dev.mysql.com/doc/refman/8.3/en/window-function-descriptions.html' + examples: [] + - name: LAST_DAY + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: LAST_DAY(date) + args: + - name: date + optional: false + type: any + tags: [] + aliases: [] + summary: Takes a date or datetime value and returns the corresponding value for + description: 'Takes a date or datetime value and returns the corresponding value + for the last day of the month. Returns NULL if the argument is invalid or NULL. + URL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: LAST_INSERT_ID + category_id: information_functions + category_label: Information Functions + signature: + display: LAST_INSERT_ID + args: [] + tags: [] + aliases: [] + summary: With no argument, LAST_INSERT_ID() returns a BIGINT UNSIGNED (64-bit) + description: "With no argument, LAST_INSERT_ID() returns a BIGINT UNSIGNED (64-bit)\n\ + value representing the first automatically generated value successfully\ninserted\ + \ for an AUTO_INCREMENT column as a result of the most recently\nexecuted INSERT\ + \ statement. The value of LAST_INSERT_ID() remains\nunchanged if no rows are\ + \ successfully inserted.\n\nWith an argument, LAST_INSERT_ID() returns an unsigned\ + \ integer, or NULL\nif the argument is NULL.\n\nFor example, after inserting\ + \ a row that generates an AUTO_INCREMENT\nvalue, you can get the value like\ + \ this:\n\nmysql> SELECT LAST_INSERT_ID();\n -> 195\n\nThe currently\ + \ executing statement does not affect the value of\nLAST_INSERT_ID(). Suppose\ + \ that you generate an AUTO_INCREMENT value\nwith one statement, and then refer\ + \ to LAST_INSERT_ID() in a\nmultiple-row INSERT statement that inserts rows\ + \ into a table with its\nown AUTO_INCREMENT column. The value of LAST_INSERT_ID()\ + \ remains stable\nin the second statement; its value for the second and later\ + \ rows is not\naffected by the earlier row insertions. (You should be aware\ + \ that, if\nyou mix references to LAST_INSERT_ID() and LAST_INSERT_ID(expr),\ + \ the\neffect is undefined.)\n\nIf the previous statement returned an error,\ + \ the value of\nLAST_INSERT_ID() is undefined. For transactional tables, if\ + \ the\nstatement is rolled back due to an error, the value of LAST_INSERT_ID()\n\ + is left undefined. For manual ROLLBACK, the value of LAST_INSERT_ID()\nis not\ + \ restored to that before the transaction; it remains as it was at\nthe point\ + \ of the ROLLBACK.\n\nWithin the body of a stored routine (procedure or function)\ + \ or a\ntrigger, the value of LAST_INSERT_ID() changes the same way as for\n\ + statements executed outside the body of these kinds of objects. The\neffect\ + \ of a stored routine or trigger upon the value of\nLAST_INSERT_ID() that is\ + \ seen by following statements depends on the\nkind of routine:\n\no If a stored\ + \ procedure executes statements that change the value of\n LAST_INSERT_ID(),\ + \ the changed value is seen by statements that follow\n the procedure call.\n\ + \no For stored functions and triggers that change the value, the value is\n\ + \ restored when the function or trigger ends, so statements coming\n after\ + \ it do not see a changed value.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/information-functions.html" + examples: [] + - name: LAST_VALUE + category_id: window_functions + category_label: Window Functions + signature: + display: LAST_VALUE(expr) + args: + - name: expr + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the value of expr from the last row of the window frame. + description: 'Returns the value of expr from the last row of the window frame. + over_clause is as described in https://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html. + null_treatment is as described in the section introduction. For an example, + see the FIRST_VALUE() function description. URL: https://dev.mysql.com/doc/refman/8.3/en/window-function-descriptions.html' + examples: [] + - name: LCASE + category_id: string_functions + category_label: String Functions + signature: + display: LCASE(str) + args: + - name: str + optional: false + type: any + tags: [] + aliases: [] + summary: LCASE() is a synonym for LOWER(). + description: 'LCASE() is a synonym for LOWER(). URL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html' + examples: [] + - name: LEAD + category_id: window_functions + category_label: Window Functions + signature: + display: LEAD(expr [, N[, default]]) + args: + - name: expr [ + optional: false + type: any + - name: N[ + optional: false + type: any + - name: default]] + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the value of expr from the row that leads (follows) the current + description: 'Returns the value of expr from the row that leads (follows) the + current row by N rows within its partition. If there is no such row, the return + value is default. For example, if N is 3, the return value is default for the + last three rows. If N or default are missing, the defaults are 1 and NULL, respectively. + N must be a literal nonnegative integer. If N is 0, expr is evaluated for the + current row. N cannot be NULL, and must be an integer in the range 0 to 263, + inclusive, in any of the following forms: o an unsigned integer constant literal + o a positional parameter marker (?) o a user-defined variable o a local variable + in a stored routine over_clause is as described in https://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html. + null_treatment is as described in the section introduction. For an example, + see the LAG() function description. URL: https://dev.mysql.com/doc/refman/8.3/en/window-function-descriptions.html' + examples: [] + - name: LEAST + category_id: comparison_operators + category_label: Comparison Operators + signature: + display: LEAST(value1,value2,...) + args: + - name: value1 + optional: false + type: any + - name: value2 + optional: false + type: any + - name: '...' + optional: false + type: any + tags: [] + aliases: [] + summary: With two or more arguments, returns the smallest (minimum-valued) + description: "With two or more arguments, returns the smallest (minimum-valued)\n\ + argument. The arguments are compared using the following rules:\n\no If any\ + \ argument is NULL, the result is NULL. No comparison is needed.\n\no If all\ + \ arguments are integer-valued, they are compared as integers.\n\no If at least\ + \ one argument is double precision, they are compared as\n double-precision\ + \ values. Otherwise, if at least one argument is a\n DECIMAL value, they are\ + \ compared as DECIMAL values.\n\no If the arguments comprise a mix of numbers\ + \ and strings, they are\n compared as strings.\n\no If any argument is a nonbinary\ + \ (character) string, the arguments are\n compared as nonbinary strings.\n\n\ + o In all other cases, the arguments are compared as binary strings.\n\nThe return\ + \ type of LEAST() is the aggregated type of the comparison\nargument types.\n\ + \nURL: https://dev.mysql.com/doc/refman/8.3/en/comparison-operators.html" + examples: [] + - name: LEFT + category_id: string_functions + category_label: String Functions + signature: + display: LEFT(str,len) + args: + - name: str + optional: false + type: any + - name: len + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the leftmost len characters from the string str, or NULL if any + description: 'Returns the leftmost len characters from the string str, or NULL + if any argument is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html' + examples: [] + - name: LENGTH + category_id: string_functions + category_label: String Functions + signature: + display: LENGTH(str) + args: + - name: str + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the length of the string str, measured in bytes. + description: 'Returns the length of the string str, measured in bytes. A multibyte + character counts as multiple bytes. This means that for a string containing + five 2-byte characters, LENGTH() returns 10, whereas CHAR_LENGTH() returns 5. + Returns NULL if str is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html' + examples: [] + - name: LINESTRING + category_id: geometry_constructors + category_label: Geometry Constructors + signature: + display: LINESTRING(pt [, pt] ...) + args: + - name: pt [ + optional: false + type: any + - name: pt] ... + optional: false + type: any + tags: [] + aliases: [] + summary: Constructs a LineString value from a number of Point or WKB Point + description: 'Constructs a LineString value from a number of Point or WKB Point + arguments. If the number of arguments is less than two, the return value is + NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/gis-mysql-specific-functions.html' + examples: [] + - name: LN + category_id: numeric_functions + category_label: Numeric Functions + signature: + display: LN(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the natural logarithm of X; that is, the base-e logarithm of + X. + description: 'Returns the natural logarithm of X; that is, the base-e logarithm + of X. If X is less than or equal to 0.0E0, the function returns NULL and a warning + "Invalid argument for logarithm" is reported. Returns NULL if X is NULL. URL: + https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html' + examples: [] + - name: LOAD_FILE + category_id: string_functions + category_label: String Functions + signature: + display: LOAD_FILE(file_name) + args: + - name: file_name + optional: false + type: any + tags: [] + aliases: [] + summary: Reads the file and returns the file contents as a string. + description: 'Reads the file and returns the file contents as a string. To use + this function, the file must be located on the server host, you must specify + the full path name to the file, and you must have the FILE privilege. The file + must be readable by the server and its size less than max_allowed_packet bytes. + If the secure_file_priv system variable is set to a nonempty directory name, + the file to be loaded must be located in that directory. If the file does not + exist or cannot be read because one of the preceding conditions is not satisfied, + the function returns NULL. The character_set_filesystem system variable controls + interpretation of file names that are given as literal strings. URL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html' + examples: [] + - name: LOCALTIME + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: LOCALTIME([fsp]) + args: + - name: '[fsp]' + optional: false + type: any + tags: [] + aliases: [] + summary: LOCALTIME and LOCALTIME() are synonyms for NOW(). + description: 'LOCALTIME and LOCALTIME() are synonyms for NOW(). URL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: LOCALTIMESTAMP + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: LOCALTIMESTAMP([fsp]) + args: + - name: '[fsp]' + optional: false + type: any + tags: [] + aliases: [] + summary: LOCALTIMESTAMP and LOCALTIMESTAMP() are synonyms for NOW(). + description: 'LOCALTIMESTAMP and LOCALTIMESTAMP() are synonyms for NOW(). URL: + https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: LOCATE + category_id: string_functions + category_label: String Functions + signature: + display: LOCATE(substr,str) + args: + - name: substr + optional: false + type: any + - name: str + optional: false + type: any + tags: [] + aliases: [] + summary: The first syntax returns the position of the first occurrence of + description: 'The first syntax returns the position of the first occurrence of + substring substr in string str. The second syntax returns the position of the + first occurrence of substring substr in string str, starting at position pos. + Returns 0 if substr is not in str. Returns NULL if any argument is NULL. URL: + https://dev.mysql.com/doc/refman/8.3/en/string-functions.html' + examples: [] + - name: LOG + category_id: numeric_functions + category_label: Numeric Functions + signature: + display: LOG(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: If called with one parameter, this function returns the natural + description: 'If called with one parameter, this function returns the natural + logarithm of X. If X is less than or equal to 0.0E0, the function returns NULL + and a warning "Invalid argument for logarithm" is reported. Returns NULL if + X or B is NULL. The inverse of this function (when called with a single argument) + is the EXP() function. URL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html' + examples: [] + - name: LOG10 + category_id: numeric_functions + category_label: Numeric Functions + signature: + display: LOG10(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the base-10 logarithm of X. + description: 'Returns the base-10 logarithm of X. If X is less than or equal to + 0.0E0, the function returns NULL and a warning "Invalid argument for logarithm" + is reported. Returns NULL if X is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html' + examples: [] + - name: LOG2 + category_id: numeric_functions + category_label: Numeric Functions + signature: + display: LOG2(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the base-2 logarithm of X. + description: 'Returns the base-2 logarithm of X. If X is less than or equal to + 0.0E0, the function returns NULL and a warning "Invalid argument for logarithm" + is reported. Returns NULL if X is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html' + examples: [] + - name: LOWER + category_id: string_functions + category_label: String Functions + signature: + display: LOWER(str) + args: + - name: str + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the string str with all characters changed to lowercase + description: "Returns the string str with all characters changed to lowercase\n\ + according to the current character set mapping, or NULL if str is NULL.\nThe\ + \ default character set is utf8mb4.\n\nmysql> SELECT LOWER('QUADRATICALLY');\n\ + \ -> 'quadratically'\n\nLOWER() (and UPPER()) are ineffective when applied\ + \ to binary strings\n(BINARY, VARBINARY, BLOB). To perform lettercase conversion\ + \ of a binary\nstring, first convert it to a nonbinary string using a character\ + \ set\nappropriate for the data stored in the string:\n\nmysql> SET @str = BINARY\ + \ 'New York';\nmysql> SELECT LOWER(@str), LOWER(CONVERT(@str USING utf8mb4));\n\ + +-------------+------------------------------------+\n| LOWER(@str) | LOWER(CONVERT(@str\ + \ USING utf8mb4)) |\n+-------------+------------------------------------+\n\ + | New York | new york |\n+-------------+------------------------------------+\n\ + \nFor collations of Unicode character sets, LOWER() and UPPER() work\naccording\ + \ to the Unicode Collation Algorithm (UCA) version in the\ncollation name, if\ + \ there is one, and UCA 4.0.0 if no version is\nspecified. For example, utf8mb4_0900_ai_ci\ + \ and utf8mb3_unicode_520_ci\nwork according to UCA 9.0.0 and 5.2.0, respectively,\ + \ whereas\nutf8mb3_unicode_ci works according to UCA 4.0.0. See\nhttps://dev.mysql.com/doc/refman/8.3/en/charset-unicode-sets.html.\n\ + \nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html" + examples: [] + - name: LPAD + category_id: string_functions + category_label: String Functions + signature: + display: LPAD(str,len,padstr) + args: + - name: str + optional: false + type: any + - name: len + optional: false + type: any + - name: padstr + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the string str, left-padded with the string padstr to a length + description: 'Returns the string str, left-padded with the string padstr to a + length of len characters. If str is longer than len, the return value is shortened + to len characters. URL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html' + examples: [] + - name: LTRIM + category_id: string_functions + category_label: String Functions + signature: + display: LTRIM(str) + args: + - name: str + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the string str with leading space characters removed. + description: 'Returns the string str with leading space characters removed. Returns + NULL if str is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html' + examples: [] + - name: MAKEDATE + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: MAKEDATE(year,dayofyear) + args: + - name: year + optional: false + type: any + - name: dayofyear + optional: false + type: any + tags: [] + aliases: [] + summary: Returns a date, given year and day-of-year values. + description: 'Returns a date, given year and day-of-year values. dayofyear must + be greater than 0 or the result is NULL. The result is also NULL if either argument + is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: MAKETIME + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: MAKETIME(hour,minute,second) + args: + - name: hour + optional: false + type: any + - name: minute + optional: false + type: any + - name: second + optional: false + type: any + tags: [] + aliases: [] + summary: Returns a time value calculated from the hour, minute, and second + description: 'Returns a time value calculated from the hour, minute, and second + arguments. Returns NULL if any of its arguments are NULL. The second argument + can have a fractional part. URL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: MAKE_SET + category_id: string_functions + category_label: String Functions + signature: + display: MAKE_SET(bits,str1,str2,...) + args: + - name: bits + optional: false + type: any + - name: str1 + optional: false + type: any + - name: str2 + optional: false + type: any + - name: '...' + optional: false + type: any + tags: [] + aliases: [] + summary: Returns a set value (a string containing substrings separated by , + description: 'Returns a set value (a string containing substrings separated by + , characters) consisting of the strings that have the corresponding bit in bits + set. str1 corresponds to bit 0, str2 to bit 1, and so on. NULL values in str1, + str2, ... are not appended to the result. URL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html' + examples: [] + - name: MASTER_POS_WAIT + category_id: gtid + category_label: GTID + signature: + display: MASTER_POS_WAIT(log_name,log_pos[,timeout][,channel]) + args: + - name: log_name + optional: false + type: any + - name: log_pos[ + optional: false + type: any + - name: timeout][ + optional: false + type: any + - name: channel] + optional: false + type: any + tags: [] + aliases: [] + summary: Deprecated alias for SOURCE_POS_WAIT(). + description: 'Deprecated alias for SOURCE_POS_WAIT(). URL: https://dev.mysql.com/doc/refman/8.3/en/replication-functions-synchronization.html' + examples: [] + - name: MAX + category_id: aggregate_functions_and_modifiers + category_label: Aggregate Functions and Modifiers + signature: + display: MAX([DISTINCT] expr) + args: + - name: '[DISTINCT] expr' + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the maximum value of expr. + description: 'Returns the maximum value of expr. MAX() may take a string argument; + in such cases, it returns the maximum string value. See https://dev.mysql.com/doc/refman/8.3/en/mysql-indexes.html. + The DISTINCT keyword can be used to find the maximum of the distinct values + of expr, however, this produces the same result as omitting DISTINCT. If there + are no matching rows, or if expr is NULL, MAX() returns NULL. This function + executes as a window function if over_clause is present. over_clause is as described + in https://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html; it cannot + be used with DISTINCT. URL: https://dev.mysql.com/doc/refman/8.3/en/aggregate-functions.html' + examples: [] + - name: MBRCONTAINS + category_id: mbr_functions + category_label: MBR Functions + signature: + display: MBRCONTAINS(g1, g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + tags: [] + aliases: [] + summary: Returns 1 or 0 to indicate whether the minimum bounding rectangle of + g1 + description: 'Returns 1 or 0 to indicate whether the minimum bounding rectangle + of g1 contains the minimum bounding rectangle of g2. This tests the opposite + relationship as MBRWithin(). MBRContains() handles its arguments as described + in the introduction to this section. URL: https://dev.mysql.com/doc/refman/8.3/en/spatial-relation-functions-mbr.html' + examples: [] + - name: MBRCOVEREDBY + category_id: mbr_functions + category_label: MBR Functions + signature: + display: MBRCOVEREDBY(g1, g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + tags: [] + aliases: [] + summary: Returns 1 or 0 to indicate whether the minimum bounding rectangle of + g1 + description: 'Returns 1 or 0 to indicate whether the minimum bounding rectangle + of g1 is covered by the minimum bounding rectangle of g2. This tests the opposite + relationship as MBRCovers(). MBRCoveredBy() handles its arguments as described + in the introduction to this section. URL: https://dev.mysql.com/doc/refman/8.3/en/spatial-relation-functions-mbr.html' + examples: [] + - name: MBRCOVERS + category_id: mbr_functions + category_label: MBR Functions + signature: + display: MBRCOVERS(g1, g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + tags: [] + aliases: [] + summary: Returns 1 or 0 to indicate whether the minimum bounding rectangle of + g1 + description: 'Returns 1 or 0 to indicate whether the minimum bounding rectangle + of g1 covers the minimum bounding rectangle of g2. This tests the opposite relationship + as MBRCoveredBy(). See the description of MBRCoveredBy() for examples. MBRCovers() + handles its arguments as described in the introduction to this section. URL: + https://dev.mysql.com/doc/refman/8.3/en/spatial-relation-functions-mbr.html' + examples: [] + - name: MBRDISJOINT + category_id: mbr_functions + category_label: MBR Functions + signature: + display: MBRDISJOINT(g1, g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + tags: [] + aliases: [] + summary: Returns 1 or 0 to indicate whether the minimum bounding rectangles of + description: 'Returns 1 or 0 to indicate whether the minimum bounding rectangles + of the two geometries g1 and g2 are disjoint (do not intersect). MBRDisjoint() + handles its arguments as described in the introduction to this section. URL: + https://dev.mysql.com/doc/refman/8.3/en/spatial-relation-functions-mbr.html' + examples: [] + - name: MBREQUALS + category_id: mbr_functions + category_label: MBR Functions + signature: + display: MBREQUALS(g1, g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + tags: [] + aliases: [] + summary: Returns 1 or 0 to indicate whether the minimum bounding rectangles of + description: 'Returns 1 or 0 to indicate whether the minimum bounding rectangles + of the two geometries g1 and g2 are the same. MBREquals() handles its arguments + as described in the introduction to this section, except that it does not return + NULL for empty geometry arguments. URL: https://dev.mysql.com/doc/refman/8.3/en/spatial-relation-functions-mbr.html' + examples: [] + - name: MBRINTERSECTS + category_id: mbr_functions + category_label: MBR Functions + signature: + display: MBRINTERSECTS(g1, g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + tags: [] + aliases: [] + summary: Returns 1 or 0 to indicate whether the minimum bounding rectangles of + description: 'Returns 1 or 0 to indicate whether the minimum bounding rectangles + of the two geometries g1 and g2 intersect. MBRIntersects() handles its arguments + as described in the introduction to this section. URL: https://dev.mysql.com/doc/refman/8.3/en/spatial-relation-functions-mbr.html' + examples: [] + - name: MBROVERLAPS + category_id: mbr_functions + category_label: MBR Functions + signature: + display: MBROVERLAPS(g1, g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + tags: [] + aliases: [] + summary: Two geometries spatially overlap if they intersect and their + description: 'Two geometries spatially overlap if they intersect and their intersection + results in a geometry of the same dimension but not equal to either of the given + geometries. This function returns 1 or 0 to indicate whether the minimum bounding + rectangles of the two geometries g1 and g2 overlap. MBROverlaps() handles its + arguments as described in the introduction to this section. URL: https://dev.mysql.com/doc/refman/8.3/en/spatial-relation-functions-mbr.html' + examples: [] + - name: MBRTOUCHES + category_id: mbr_functions + category_label: MBR Functions + signature: + display: MBRTOUCHES(g1, g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + tags: [] + aliases: [] + summary: Two geometries spatially touch if their interiors do not intersect, but + description: 'Two geometries spatially touch if their interiors do not intersect, + but the boundary of one of the geometries intersects either the boundary or + the interior of the other. This function returns 1 or 0 to indicate whether + the minimum bounding rectangles of the two geometries g1 and g2 touch. MBRTouches() + handles its arguments as described in the introduction to this section. URL: + https://dev.mysql.com/doc/refman/8.3/en/spatial-relation-functions-mbr.html' + examples: [] + - name: MBRWITHIN + category_id: mbr_functions + category_label: MBR Functions + signature: + display: MBRWITHIN(g1, g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + tags: [] + aliases: [] + summary: Returns 1 or 0 to indicate whether the minimum bounding rectangle of + g1 + description: 'Returns 1 or 0 to indicate whether the minimum bounding rectangle + of g1 is within the minimum bounding rectangle of g2. This tests the opposite + relationship as MBRContains(). MBRWithin() handles its arguments as described + in the introduction to this section. URL: https://dev.mysql.com/doc/refman/8.3/en/spatial-relation-functions-mbr.html' + examples: [] + - name: MD5 + category_id: encryption_functions + category_label: Encryption Functions + signature: + display: MD5(str) + args: + - name: str + optional: false + type: any + tags: [] + aliases: [] + summary: Calculates an MD5 128-bit checksum for the string. + description: 'Calculates an MD5 128-bit checksum for the string. The value is + returned as a string of 32 hexadecimal digits, or NULL if the argument was NULL. + The return value can, for example, be used as a hash key. See the notes at the + beginning of this section about storing hash values efficiently. The return + value is a string in the connection character set. If FIPS mode is enabled, + MD5() returns NULL. See https://dev.mysql.com/doc/refman/8.3/en/fips-mode.html. + URL: https://dev.mysql.com/doc/refman/8.3/en/encryption-functions.html' + examples: [] + - name: MEDIUMINT + category_id: data_types + category_label: Data Types + signature: + display: MEDIUMINT(M) + args: + - name: M + optional: false + type: any + tags: [] + aliases: [] + summary: A medium-sized integer. + description: 'A medium-sized integer. The signed range is -8388608 to 8388607. + The unsigned range is 0 to 16777215. URL: https://dev.mysql.com/doc/refman/8.3/en/numeric-type-syntax.html' + examples: [] + - name: MICROSECOND + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: MICROSECOND(expr) + args: + - name: expr + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the microseconds from the time or datetime expression expr as + a + description: 'Returns the microseconds from the time or datetime expression expr + as a number in the range from 0 to 999999. Returns NULL if expr is NULL. URL: + https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: MID + category_id: string_functions + category_label: String Functions + signature: + display: MID(str,pos,len) + args: + - name: str + optional: false + type: any + - name: pos + optional: false + type: any + - name: len + optional: false + type: any + tags: [] + aliases: [] + summary: MID(str,pos,len) is a synonym for SUBSTRING(str,pos,len). + description: 'MID(str,pos,len) is a synonym for SUBSTRING(str,pos,len). URL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html' + examples: [] + - name: MIN + category_id: aggregate_functions_and_modifiers + category_label: Aggregate Functions and Modifiers + signature: + display: MIN([DISTINCT] expr) + args: + - name: '[DISTINCT] expr' + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the minimum value of expr. + description: 'Returns the minimum value of expr. MIN() may take a string argument; + in such cases, it returns the minimum string value. See https://dev.mysql.com/doc/refman/8.3/en/mysql-indexes.html. + The DISTINCT keyword can be used to find the minimum of the distinct values + of expr, however, this produces the same result as omitting DISTINCT. If there + are no matching rows, or if expr is NULL, MIN() returns NULL. This function + executes as a window function if over_clause is present. over_clause is as described + in https://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html; it cannot + be used with DISTINCT. URL: https://dev.mysql.com/doc/refman/8.3/en/aggregate-functions.html' + examples: [] + - name: MINUTE + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: MINUTE(time) + args: + - name: time + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the minute for time, in the range 0 to 59, or NULL if time is + description: 'Returns the minute for time, in the range 0 to 59, or NULL if time + is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: MOD + category_id: numeric_functions + category_label: Numeric Functions + signature: + display: MOD(N,M) + args: + - name: N + optional: false + type: any + - name: M + optional: false + type: any + tags: [] + aliases: [] + summary: Modulo operation. + description: 'Modulo operation. Returns the remainder of N divided by M. Returns + NULL if M or N is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html' + examples: [] + - name: MONTH + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: MONTH(date) + args: + - name: date + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the month for date, in the range 1 to 12 for January to + description: 'Returns the month for date, in the range 1 to 12 for January to + December, or 0 for dates such as ''0000-00-00'' or ''2008-00-00'' that have + a zero month part. Returns NULL if date is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: MONTHNAME + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: MONTHNAME(date) + args: + - name: date + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the full name of the month for date. + description: 'Returns the full name of the month for date. The language used for + the name is controlled by the value of the lc_time_names system variable (https://dev.mysql.com/doc/refman/8.3/en/locale-support.html). + Returns NULL if date is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: MULTILINESTRING + category_id: geometry_constructors + category_label: Geometry Constructors + signature: + display: MULTILINESTRING(ls [, ls] ...) + args: + - name: ls [ + optional: false + type: any + - name: ls] ... + optional: false + type: any + tags: [] + aliases: [] + summary: Constructs a MultiLineString value using LineString or WKB LineString + description: 'Constructs a MultiLineString value using LineString or WKB LineString + arguments. URL: https://dev.mysql.com/doc/refman/8.3/en/gis-mysql-specific-functions.html' + examples: [] + - name: MULTIPOINT + category_id: geometry_constructors + category_label: Geometry Constructors + signature: + display: MULTIPOINT(pt [, pt2] ...) + args: + - name: pt [ + optional: false + type: any + - name: pt2] ... + optional: false + type: any + tags: [] + aliases: [] + summary: Constructs a MultiPoint value using Point or WKB Point arguments. + description: 'Constructs a MultiPoint value using Point or WKB Point arguments. + URL: https://dev.mysql.com/doc/refman/8.3/en/gis-mysql-specific-functions.html' + examples: [] + - name: MULTIPOLYGON + category_id: geometry_constructors + category_label: Geometry Constructors + signature: + display: MULTIPOLYGON(poly [, poly] ...) + args: + - name: poly [ + optional: false + type: any + - name: poly] ... + optional: false + type: any + tags: [] + aliases: [] + summary: Constructs a MultiPolygon value from a set of Polygon or WKB Polygon + description: 'Constructs a MultiPolygon value from a set of Polygon or WKB Polygon + arguments. URL: https://dev.mysql.com/doc/refman/8.3/en/gis-mysql-specific-functions.html' + examples: [] + - name: NAME_CONST + category_id: miscellaneous_functions + category_label: Miscellaneous Functions + signature: + display: NAME_CONST(name,value) + args: + - name: name + optional: false + type: any + - name: value + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the given value. + description: 'Returns the given value. When used to produce a result set column, + NAME_CONST() causes the column to have the given name. The arguments should + be constants. mysql> SELECT NAME_CONST(''myname'', 14); +--------+ | myname + | +--------+ | 14 | +--------+ URL: https://dev.mysql.com/doc/refman/8.3/en/miscellaneous-functions.html' + examples: [] + - name: NOW + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: NOW([fsp]) + args: + - name: '[fsp]' + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the current date and time as a value in 'YYYY-MM-DD hh:mm:ss' + description: 'Returns the current date and time as a value in ''YYYY-MM-DD hh:mm:ss'' + or YYYYMMDDhhmmss format, depending on whether the function is used in string + or numeric context. The value is expressed in the session time zone. If the + fsp argument is given to specify a fractional seconds precision from 0 to 6, + the return value includes a fractional seconds part of that many digits. URL: + https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: NTH_VALUE + category_id: window_functions + category_label: Window Functions + signature: + display: NTH_VALUE(expr, N) + args: + - name: expr + optional: false + type: any + - name: N + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the value of expr from the N-th row of the window frame. + description: 'Returns the value of expr from the N-th row of the window frame. + If there is no such row, the return value is NULL. N must be a literal positive + integer. from_first_last is part of the SQL standard, but the MySQL implementation + permits only FROM FIRST (which is also the default). This means that calculations + begin at the first row of the window. FROM LAST is parsed, but produces an error. + To obtain the same effect as FROM LAST (begin calculations at the last row of + the window), use ORDER BY to sort in reverse order. over_clause is as described + in https://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html. null_treatment + is as described in the section introduction. For an example, see the FIRST_VALUE() + function description. URL: https://dev.mysql.com/doc/refman/8.3/en/window-function-descriptions.html' + examples: [] + - name: NTILE + category_id: window_functions + category_label: Window Functions + signature: + display: NTILE(N) + args: + - name: N + optional: false + type: any + tags: [] + aliases: [] + summary: Divides a partition into N groups (buckets), assigns each row in the + description: 'Divides a partition into N groups (buckets), assigns each row in + the partition its bucket number, and returns the bucket number of the current + row within its partition. For example, if N is 4, NTILE() divides rows into + four buckets. If N is 100, NTILE() divides rows into 100 buckets. N must be + a literal positive integer. Bucket number return values range from 1 to N. N + cannot be NULL, and must be an integer in the range 0 to 263, inclusive, in + any of the following forms: o an unsigned integer constant literal o a positional + parameter marker (?) o a user-defined variable o a local variable in a stored + routine This function should be used with ORDER BY to sort partition rows into + the desired order. over_clause is as described in https://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html. + URL: https://dev.mysql.com/doc/refman/8.3/en/window-function-descriptions.html' + examples: [] + - name: NULLIF + category_id: flow_control_functions + category_label: Flow Control Functions + signature: + display: NULLIF(expr1,expr2) + args: + - name: expr1 + optional: false + type: any + - name: expr2 + optional: false + type: any + tags: [] + aliases: [] + summary: Returns NULL if expr1 = expr2 is true, otherwise returns expr1. + description: 'Returns NULL if expr1 = expr2 is true, otherwise returns expr1. + This is the same as CASE WHEN expr1 = expr2 THEN NULL ELSE expr1 END. The return + value has the same type as the first argument. URL: https://dev.mysql.com/doc/refman/8.3/en/flow-control-functions.html' + examples: [] + - name: OCT + category_id: string_functions + category_label: String Functions + signature: + display: OCT(N) + args: + - name: N + optional: false + type: any + tags: [] + aliases: [] + summary: Returns a string representation of the octal value of N, where N is a + description: 'Returns a string representation of the octal value of N, where N + is a longlong (BIGINT) number. This is equivalent to CONV(N,10,8). Returns NULL + if N is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html' + examples: [] + - name: OCTET_LENGTH + category_id: string_functions + category_label: String Functions + signature: + display: OCTET_LENGTH(str) + args: + - name: str + optional: false + type: any + tags: [] + aliases: [] + summary: OCTET_LENGTH() is a synonym for LENGTH(). + description: 'OCTET_LENGTH() is a synonym for LENGTH(). URL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html' + examples: [] + - name: ORD + category_id: string_functions + category_label: String Functions + signature: + display: ORD(str) + args: + - name: str + optional: false + type: any + tags: [] + aliases: [] + summary: If the leftmost character of the string str is a multibyte character, + description: "If the leftmost character of the string str is a multibyte character,\n\ + returns the code for that character, calculated from the numeric values\nof\ + \ its constituent bytes using this formula:\n\n (1st byte code)\n+ (2nd byte\ + \ code * 256)\n+ (3rd byte code * 256^2) ...\n\nIf the leftmost character is\ + \ not a multibyte character, ORD() returns\nthe same value as the ASCII() function.\ + \ The function returns NULL if\nstr is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html" + examples: [] + - name: PERCENT_RANK + category_id: window_functions + category_label: Window Functions + signature: + display: PERCENT_RANK + args: [] + tags: [] + aliases: [] + summary: Returns the percentage of partition values less than the value in the + description: 'Returns the percentage of partition values less than the value in + the current row, excluding the highest value. Return values range from 0 to + 1 and represent the row relative rank, calculated as the result of this formula, + where rank is the row rank and rows is the number of partition rows: (rank - + 1) / (rows - 1) This function should be used with ORDER BY to sort partition + rows into the desired order. Without ORDER BY, all rows are peers. over_clause + is as described in https://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html. + For an example, see the CUME_DIST() function description. URL: https://dev.mysql.com/doc/refman/8.3/en/window-function-descriptions.html' + examples: [] + - name: PERIOD_ADD + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: PERIOD_ADD(P,N) + args: + - name: P + optional: false + type: any + - name: N + optional: false + type: any + tags: [] + aliases: [] + summary: Adds N months to period P (in the format YYMM or YYYYMM). + description: 'Adds N months to period P (in the format YYMM or YYYYMM). Returns + a value in the format YYYYMM. *Note*: The period argument P is not a date value. + This function returns NULL if P or N is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: PERIOD_DIFF + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: PERIOD_DIFF(P1,P2) + args: + - name: P1 + optional: false + type: any + - name: P2 + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the number of months between periods P1 and P2. + description: 'Returns the number of months between periods P1 and P2. P1 and P2 + should be in the format YYMM or YYYYMM. Note that the period arguments P1 and + P2 are not date values. This function returns NULL if P1 or P2 is NULL. URL: + https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: PI + category_id: numeric_functions + category_label: Numeric Functions + signature: + display: PI + args: [] + tags: [] + aliases: [] + summary: "Returns the value of \u03C0 (pi)." + description: "Returns the value of \u03C0 (pi). The default number of decimal\ + \ places\ndisplayed is seven, but MySQL uses the full double-precision value\n\ + internally.\n\nBecause the return value of this function is a double-precision\ + \ value,\nits exact representation may vary between platforms or implementations.\n\ + This also applies to any expressions making use of PI(). See\nhttps://dev.mysql.com/doc/refman/8.3/en/floating-point-types.html.\n\ + \nURL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html" + examples: [] + - name: POINT + category_id: geometry_constructors + category_label: Geometry Constructors + signature: + display: POINT(x, y) + args: + - name: x + optional: false + type: any + - name: y + optional: false + type: any + tags: [] + aliases: [] + summary: Constructs a Point using its coordinates. + description: 'Constructs a Point using its coordinates. URL: https://dev.mysql.com/doc/refman/8.3/en/gis-mysql-specific-functions.html' + examples: [] + - name: POLYGON + category_id: geometry_constructors + category_label: Geometry Constructors + signature: + display: POLYGON(ls [, ls] ...) + args: + - name: ls [ + optional: false + type: any + - name: ls] ... + optional: false + type: any + tags: [] + aliases: [] + summary: Constructs a Polygon value from a number of LineString or WKB + description: 'Constructs a Polygon value from a number of LineString or WKB LineString + arguments. If any argument does not represent a LinearRing (that is, not a closed + and simple LineString), the return value is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/gis-mysql-specific-functions.html' + examples: [] + - name: POSITION + category_id: string_functions + category_label: String Functions + signature: + display: POSITION(substr IN str) + args: + - name: substr IN str + optional: false + type: any + tags: [] + aliases: [] + summary: POSITION(substr IN str) is a synonym for LOCATE(substr,str). + description: 'POSITION(substr IN str) is a synonym for LOCATE(substr,str). URL: + https://dev.mysql.com/doc/refman/8.3/en/string-functions.html' + examples: [] + - name: POW + category_id: numeric_functions + category_label: Numeric Functions + signature: + display: POW(X,Y) + args: + - name: X + optional: false + type: any + - name: Y + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the value of X raised to the power of Y. + description: 'Returns the value of X raised to the power of Y. Returns NULL if + X or Y is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html' + examples: [] + - name: POWER + category_id: numeric_functions + category_label: Numeric Functions + signature: + display: POWER(X,Y) + args: + - name: X + optional: false + type: any + - name: Y + optional: false + type: any + tags: [] + aliases: [] + summary: This is a synonym for POW(). + description: 'This is a synonym for POW(). URL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html' + examples: [] + - name: PS_CURRENT_THREAD_ID + category_id: performance_schema_functions + category_label: Performance Schema Functions + signature: + display: PS_CURRENT_THREAD_ID + args: [] + tags: [] + aliases: [] + summary: Returns a BIGINT UNSIGNED value representing the Performance Schema + description: 'Returns a BIGINT UNSIGNED value representing the Performance Schema + thread ID assigned to the current connection. The thread ID return value is + a value of the type given in the THREAD_ID column of Performance Schema tables. + Performance Schema configuration affects PS_CURRENT_THREAD_ID() the same way + as for PS_THREAD_ID(). For details, see the description of that function. URL: + https://dev.mysql.com/doc/refman/8.3/en/performance-schema-functions.html' + examples: [] + - name: PS_THREAD_ID + category_id: performance_schema_functions + category_label: Performance Schema Functions + signature: + display: PS_THREAD_ID(connection_id) + args: + - name: connection_id + optional: false + type: any + tags: [] + aliases: [] + summary: Given a connection ID, returns a BIGINT UNSIGNED value representing the + description: "Given a connection ID, returns a BIGINT UNSIGNED value representing\ + \ the\nPerformance Schema thread ID assigned to the connection ID, or NULL if\n\ + no thread ID exists for the connection ID. The latter can occur for\nthreads\ + \ that are not instrumented, or if connection_id is NULL.\n\nThe connection\ + \ ID argument is a value of the type given in the\nPROCESSLIST_ID column of\ + \ the Performance Schema threads table or the Id\ncolumn of SHOW PROCESSLIST\ + \ output.\n\nThe thread ID return value is a value of the type given in the\n\ + THREAD_ID column of Performance Schema tables.\n\nPerformance Schema configuration\ + \ affects PS_THREAD_ID() operation as\nfollows. (These remarks also apply to\ + \ PS_CURRENT_THREAD_ID().)\n\no Disabling the thread_instrumentation consumer\ + \ disables statistics\n from being collected and aggregated at the thread level,\ + \ but has no\n effect on PS_THREAD_ID().\n\no If performance_schema_max_thread_instances\ + \ is not 0, the Performance\n Schema allocates memory for thread statistics\ + \ and assigns an internal\n ID to each thread for which instance memory is\ + \ available. If there\n are threads for which instance memory is not available,\n\ + \ PS_THREAD_ID() returns NULL; in this case,\n Performance_schema_thread_instances_lost\ + \ is nonzero.\n\no If performance_schema_max_thread_instances is 0, the Performance\n\ + \ Schema allocates no thread memory and PS_THREAD_ID() returns NULL.\n\no If\ + \ the Performance Schema itself is disabled, PS_THREAD_ID() produces\n an error.\n\ + \nURL: https://dev.mysql.com/doc/refman/8.3/en/performance-schema-functions.html" + examples: [] + - name: QUARTER + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: QUARTER(date) + args: + - name: date + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the quarter of the year for date, in the range 1 to 4, or NULL + description: 'Returns the quarter of the year for date, in the range 1 to 4, or + NULL if date is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: QUOTE + category_id: string_functions + category_label: String Functions + signature: + display: QUOTE(str) + args: + - name: str + optional: false + type: any + tags: [] + aliases: [] + summary: Quotes a string to produce a result that can be used as a properly + description: 'Quotes a string to produce a result that can be used as a properly + escaped data value in an SQL statement. The string is returned enclosed by single + quotation marks and with each instance of backslash (\), single quote (''), + ASCII NUL, and Control+Z preceded by a backslash. If the argument is NULL, the + return value is the word "NULL" without enclosing single quotation marks. URL: + https://dev.mysql.com/doc/refman/8.3/en/string-functions.html' + examples: [] + - name: RADIANS + category_id: numeric_functions + category_label: Numeric Functions + signature: + display: RADIANS(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the argument X, converted from degrees to radians. + description: "Returns the argument X, converted from degrees to radians. (Note\ + \ that\n\u03C0 radians equals 180 degrees.) Returns NULL if X is NULL.\n\nURL:\ + \ https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html" + examples: [] + - name: RAND + category_id: numeric_functions + category_label: Numeric Functions + signature: + display: RAND([N]) + args: + - name: '[N]' + optional: false + type: any + tags: [] + aliases: [] + summary: Returns a random floating-point value v in the range 0 <= v < 1.0. + description: "Returns a random floating-point value v in the range 0 <= v < 1.0.\ + \ To\nobtain a random integer R in the range i <= R < j, use the expression\n\ + FLOOR(i + RAND() * (j \u2212 i)). For example, to obtain a random integer\n\ + in the range the range 7 <= R < 12, use the following statement:\n\nSELECT FLOOR(7\ + \ + (RAND() * 5));\n\nIf an integer argument N is specified, it is used as the\ + \ seed value:\n\no With a constant initializer argument, the seed is initialized\ + \ once\n when the statement is prepared, prior to execution.\n\no With a nonconstant\ + \ initializer argument (such as a column name), the\n seed is initialized with\ + \ the value for each invocation of RAND().\n\nOne implication of this behavior\ + \ is that for equal argument values,\nRAND(N) returns the same value each time,\ + \ and thus produces a\nrepeatable sequence of column values. In the following\ + \ example, the\nsequence of values produced by RAND(3) is the same both places\ + \ it\noccurs.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html" + examples: [] + - name: RANDOM_BYTES + category_id: encryption_functions + category_label: Encryption Functions + signature: + display: RANDOM_BYTES(len) + args: + - name: len + optional: false + type: any + tags: [] + aliases: [] + summary: This function returns a binary string of len random bytes generated + description: 'This function returns a binary string of len random bytes generated + using the random number generator of the SSL library. Permitted values of len + range from 1 to 1024. For values outside that range, an error occurs. Returns + NULL if len is NULL. RANDOM_BYTES() can be used to provide the initialization + vector for the AES_DECRYPT() and AES_ENCRYPT() functions. For use in that context, + len must be at least 16. Larger values are permitted, but bytes in excess of + 16 are ignored. RANDOM_BYTES() generates a random value, which makes its result + nondeterministic. Consequently, statements that use this function are unsafe + for statement-based replication. If RANDOM_BYTES() is invoked from within the + mysql client, binary strings display using hexadecimal notation, depending on + the value of the --binary-as-hex. For more information about that option, see + https://dev.mysql.com/doc/refman/8.3/en/mysql.html. URL: https://dev.mysql.com/doc/refman/8.3/en/encryption-functions.html' + examples: [] + - name: RANK + category_id: window_functions + category_label: Window Functions + signature: + display: RANK + args: [] + tags: [] + aliases: [] + summary: Returns the rank of the current row within its partition, with gaps. + description: 'Returns the rank of the current row within its partition, with gaps. + Peers are considered ties and receive the same rank. This function does not + assign consecutive ranks to peer groups if groups of size greater than one exist; + the result is noncontiguous rank numbers. This function should be used with + ORDER BY to sort partition rows into the desired order. Without ORDER BY, all + rows are peers. over_clause is as described in https://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html. + URL: https://dev.mysql.com/doc/refman/8.3/en/window-function-descriptions.html' + examples: [] + - name: REGEXP_INSTR + category_id: string_functions + category_label: String Functions + signature: + display: REGEXP_INSTR(expr, pat[, pos[, occurrence[, return_option[, match_type]]]]) + args: + - name: expr + optional: false + type: any + - name: pat[ + optional: false + type: any + - name: pos[ + optional: false + type: any + - name: occurrence[ + optional: false + type: any + - name: return_option[ + optional: false + type: any + - name: match_type]]]] + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the starting index of the substring of the string expr that + description: "Returns the starting index of the substring of the string expr that\n\ + matches the regular expression specified by the pattern pat, 0 if there\nis\ + \ no match. If expr or pat is NULL, the return value is NULL.\nCharacter indexes\ + \ begin at 1.\n\nREGEXP_INSTR() takes these optional arguments:\n\no pos: The\ + \ position in expr at which to start the search. If omitted,\n the default\ + \ is 1.\n\no occurrence: Which occurrence of a match to search for. If omitted,\n\ + \ the default is 1.\n\no return_option: Which type of position to return. If\ + \ this value is 0,\n REGEXP_INSTR() returns the position of the matched substring's\ + \ first\n character. If this value is 1, REGEXP_INSTR() returns the position\n\ + \ following the matched substring. If omitted, the default is 0.\n\no match_type:\ + \ A string that specifies how to perform matching. The\n meaning is as described\ + \ for REGEXP_LIKE().\n\nFor additional information about how matching occurs,\ + \ see the\ndescription for REGEXP_LIKE().\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/regexp.html" + examples: [] + - name: REGEXP_LIKE + category_id: string_functions + category_label: String Functions + signature: + display: REGEXP_LIKE(expr, pat[, match_type]) + args: + - name: expr + optional: false + type: any + - name: pat[ + optional: false + type: any + - name: match_type] + optional: false + type: any + tags: [] + aliases: [] + summary: Returns 1 if the string expr matches the regular expression specified + description: "Returns 1 if the string expr matches the regular expression specified\n\ + by the pattern pat, 0 otherwise. If expr or pat is NULL, the return\nvalue is\ + \ NULL.\n\nThe pattern can be an extended regular expression, the syntax for\ + \ which\nis discussed in\nhttps://dev.mysql.com/doc/refman/8.3/en/regexp.html#regexp-syntax.\ + \ The\npattern need not be a literal string. For example, it can be specified\n\ + as a string expression or table column.\n\nThe optional match_type argument\ + \ is a string that may contain any or\nall the following characters specifying\ + \ how to perform matching:\n\no c: Case-sensitive matching.\n\no i: Case-insensitive\ + \ matching.\n\no m: Multiple-line mode. Recognize line terminators within the\ + \ string.\n The default behavior is to match line terminators only at the start\n\ + \ and end of the string expression.\n\no n: The . character matches line terminators.\ + \ The default is for .\n matching to stop at the end of a line.\n\no u: Unix-only\ + \ line endings. Only the newline character is recognized\n as a line ending\ + \ by the ., ^, and $ match operators.\n\nIf characters specifying contradictory\ + \ options are specified within\nmatch_type, the rightmost one takes precedence.\n\ + \nBy default, regular expression operations use the character set and\ncollation\ + \ of the expr and pat arguments when deciding the type of a\ncharacter and performing\ + \ the comparison. If the arguments have\ndifferent character sets or collations,\ + \ coercibility rules apply as\ndescribed in\nhttps://dev.mysql.com/doc/refman/8.3/en/charset-collation-coercibility.\n\ + html. Arguments may be specified with explicit collation indicators to\nchange\ + \ comparison behavior.\n\nmysql> SELECT REGEXP_LIKE('CamelCase', 'CAMELCASE');\n\ + +---------------------------------------+\n| REGEXP_LIKE('CamelCase', 'CAMELCASE')\ + \ |\n+---------------------------------------+\n| \ + \ 1 |\n+---------------------------------------+\nmysql> SELECT REGEXP_LIKE('CamelCase',\ + \ 'CAMELCASE' COLLATE utf8mb4_0900_as_cs);\n+------------------------------------------------------------------+\n\ + | REGEXP_LIKE('CamelCase', 'CAMELCASE' COLLATE utf8mb4_0900_as_cs) |\n+------------------------------------------------------------------+\n\ + | 0 |\n ..." + examples: [] + - name: REGEXP_REPLACE + category_id: string_functions + category_label: String Functions + signature: + display: REGEXP_REPLACE(expr, pat, repl[, pos[, occurrence[, match_type]]]) + args: + - name: expr + optional: false + type: any + - name: pat + optional: false + type: any + - name: repl[ + optional: false + type: any + - name: pos[ + optional: false + type: any + - name: occurrence[ + optional: false + type: any + - name: match_type]]] + optional: false + type: any + tags: [] + aliases: [] + summary: Replaces occurrences in the string expr that match the regular + description: "Replaces occurrences in the string expr that match the regular\n\ + expression specified by the pattern pat with the replacement string\nrepl, and\ + \ returns the resulting string. If expr, pat, or repl is NULL,\nthe return value\ + \ is NULL.\n\nREGEXP_REPLACE() takes these optional arguments:\n\no pos: The\ + \ position in expr at which to start the search. If omitted,\n the default\ + \ is 1.\n\no occurrence: Which occurrence of a match to replace. If omitted,\ + \ the\n default is 0 (which means \"replace all occurrences\").\n\no match_type:\ + \ A string that specifies how to perform matching. The\n meaning is as described\ + \ for REGEXP_LIKE().\n\nThe result returned by this function uses the character\ + \ set and\ncollation of the expression searched for matches.\n\nFor additional\ + \ information about how matching occurs, see the\ndescription for REGEXP_LIKE().\n\ + \nURL: https://dev.mysql.com/doc/refman/8.3/en/regexp.html" + examples: [] + - name: REGEXP_SUBSTR + category_id: string_functions + category_label: String Functions + signature: + display: REGEXP_SUBSTR(expr, pat[, pos[, occurrence[, match_type]]]) + args: + - name: expr + optional: false + type: any + - name: pat[ + optional: false + type: any + - name: pos[ + optional: false + type: any + - name: occurrence[ + optional: false + type: any + - name: match_type]]] + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the substring of the string expr that matches the regular + description: "Returns the substring of the string expr that matches the regular\n\ + expression specified by the pattern pat, NULL if there is no match. If\nexpr\ + \ or pat is NULL, the return value is NULL.\n\nREGEXP_SUBSTR() takes these optional\ + \ arguments:\n\no pos: The position in expr at which to start the search. If\ + \ omitted,\n the default is 1.\n\no occurrence: Which occurrence of a match\ + \ to search for. If omitted,\n the default is 1.\n\no match_type: A string\ + \ that specifies how to perform matching. The\n meaning is as described for\ + \ REGEXP_LIKE().\n\nThe result returned by this function uses the character\ + \ set and\ncollation of the expression searched for matches.\n\nFor additional\ + \ information about how matching occurs, see the\ndescription for REGEXP_LIKE().\n\ + \nURL: https://dev.mysql.com/doc/refman/8.3/en/regexp.html" + examples: [] + - name: RELEASE_ALL_LOCKS + category_id: locking_functions + category_label: Locking Functions + signature: + display: RELEASE_ALL_LOCKS + args: [] + tags: [] + aliases: [] + summary: Releases all named locks held by the current session and returns the + description: 'Releases all named locks held by the current session and returns + the number of locks released (0 if there were none) URL: https://dev.mysql.com/doc/refman/8.3/en/locking-functions.html' + examples: [] + - name: RELEASE_LOCK + category_id: locking_functions + category_label: Locking Functions + signature: + display: RELEASE_LOCK(str) + args: + - name: str + optional: false + type: any + tags: [] + aliases: [] + summary: Releases the lock named by the string str that was obtained with + description: 'Releases the lock named by the string str that was obtained with + GET_LOCK(). Returns 1 if the lock was released, 0 if the lock was not established + by this thread (in which case the lock is not released), and NULL if the named + lock did not exist. The lock does not exist if it was never obtained by a call + to GET_LOCK() or if it has previously been released. The DO statement is convenient + to use with RELEASE_LOCK(). See [HELP DO]. URL: https://dev.mysql.com/doc/refman/8.3/en/locking-functions.html' + examples: [] + - name: REVERSE + category_id: string_functions + category_label: String Functions + signature: + display: REVERSE(str) + args: + - name: str + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the string str with the order of the characters reversed, or + description: 'Returns the string str with the order of the characters reversed, + or NULL if str is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html' + examples: [] + - name: RIGHT + category_id: string_functions + category_label: String Functions + signature: + display: RIGHT(str,len) + args: + - name: str + optional: false + type: any + - name: len + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the rightmost len characters from the string str, or NULL if + description: 'Returns the rightmost len characters from the string str, or NULL + if any argument is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html' + examples: [] + - name: ROLES_GRAPHML + category_id: information_functions + category_label: Information Functions + signature: + display: ROLES_GRAPHML + args: [] + tags: [] + aliases: [] + summary: Returns a utf8mb3 string containing a GraphML document representing + description: 'Returns a utf8mb3 string containing a GraphML document representing + memory role subgraphs. The ROLE_ADMIN privilege (or the deprecated SUPER privilege) + is required to see content in the element. Otherwise, the result shows + only an empty element: mysql> SELECT ROLES_GRAPHML(); +---------------------------------------------------+ + | ROLES_GRAPHML() | +---------------------------------------------------+ + | | +---------------------------------------------------+ + URL: https://dev.mysql.com/doc/refman/8.3/en/information-functions.html' + examples: [] + - name: ROUND + category_id: numeric_functions + category_label: Numeric Functions + signature: + display: ROUND(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: Rounds the argument X to D decimal places. + description: 'Rounds the argument X to D decimal places. The rounding algorithm + depends on the data type of X. D defaults to 0 if not specified. D can be negative + to cause D digits left of the decimal point of the value X to become zero. The + maximum absolute value for D is 30; any digits in excess of 30 (or -30) are + truncated. If X or D is NULL, the function returns NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html' + examples: [] + - name: ROW_COUNT + category_id: information_functions + category_label: Information Functions + signature: + display: ROW_COUNT + args: [] + tags: [] + aliases: [] + summary: 'ROW_COUNT() returns a value as follows:' + description: "ROW_COUNT() returns a value as follows:\n\no DDL statements: 0.\ + \ This applies to statements such as CREATE TABLE or\n DROP TABLE.\n\no DML\ + \ statements other than SELECT: The number of affected rows. This\n applies\ + \ to statements such as UPDATE, INSERT, or DELETE (as before),\n but now also\ + \ to statements such as ALTER TABLE and LOAD DATA.\n\no SELECT: -1 if the statement\ + \ returns a result set, or the number of\n rows \"affected\" if it does not.\ + \ For example, for SELECT * FROM t1,\n ROW_COUNT() returns -1. For SELECT *\ + \ FROM t1 INTO OUTFILE\n 'file_name', ROW_COUNT() returns the number of rows\ + \ written to the\n file.\n\no SIGNAL statements: 0.\n\nFor UPDATE statements,\ + \ the affected-rows value by default is the number\nof rows actually changed.\ + \ If you specify the CLIENT_FOUND_ROWS flag to\nmysql_real_connect()\n(https://dev.mysql.com/doc/c-api/8.2/en/mysql-real-connect.html)\ + \ when\nconnecting to mysqld, the affected-rows value is the number of rows\n\ + \"found\"; that is, matched by the WHERE clause.\n\nFor REPLACE statements,\ + \ the affected-rows value is 2 if the new row\nreplaced an old row, because\ + \ in this case, one row was inserted after\nthe duplicate was deleted.\n\nFor\ + \ INSERT ... ON DUPLICATE KEY UPDATE statements, the affected-rows\nvalue per\ + \ row is 1 if the row is inserted as a new row, 2 if an\nexisting row is updated,\ + \ and 0 if an existing row is set to its current\nvalues. If you specify the\ + \ CLIENT_FOUND_ROWS flag, the affected-rows\nvalue is 1 (not 0) if an existing\ + \ row is set to its current values.\n\nThe ROW_COUNT() value is similar to the\ + \ value from the\nmysql_affected_rows()\n(https://dev.mysql.com/doc/c-api/8.2/en/mysql-affected-rows.html)\ + \ C API\nfunction and the row count that the mysql client displays following\n\ + statement execution.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/information-functions.html" + examples: [] + - name: ROW_NUMBER + category_id: window_functions + category_label: Window Functions + signature: + display: ROW_NUMBER + args: [] + tags: [] + aliases: [] + summary: Returns the number of the current row within its partition. + description: 'Returns the number of the current row within its partition. Rows + numbers range from 1 to the number of partition rows. ORDER BY affects the order + in which rows are numbered. Without ORDER BY, row numbering is nondeterministic. + ROW_NUMBER() assigns peers different row numbers. To assign peers the same value, + use RANK() or DENSE_RANK(). For an example, see the RANK() function description. + over_clause is as described in https://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html. + URL: https://dev.mysql.com/doc/refman/8.3/en/window-function-descriptions.html' + examples: [] + - name: RPAD + category_id: string_functions + category_label: String Functions + signature: + display: RPAD(str,len,padstr) + args: + - name: str + optional: false + type: any + - name: len + optional: false + type: any + - name: padstr + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the string str, right-padded with the string padstr to a length + description: 'Returns the string str, right-padded with the string padstr to a + length of len characters. If str is longer than len, the return value is shortened + to len characters. If str, padstr, or len is NULL, the function returns NULL. + URL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html' + examples: [] + - name: RTRIM + category_id: string_functions + category_label: String Functions + signature: + display: RTRIM(str) + args: + - name: str + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the string str with trailing space characters removed. + description: 'Returns the string str with trailing space characters removed. URL: + https://dev.mysql.com/doc/refman/8.3/en/string-functions.html' + examples: [] + - name: SCHEMA + category_id: information_functions + category_label: Information Functions + signature: + display: SCHEMA + args: [] + tags: [] + aliases: [] + summary: This function is a synonym for DATABASE(). + description: 'This function is a synonym for DATABASE(). URL: https://dev.mysql.com/doc/refman/8.3/en/information-functions.html' + examples: [] + - name: SECOND + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: SECOND(time) + args: + - name: time + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the second for time, in the range 0 to 59, or NULL if time is + description: 'Returns the second for time, in the range 0 to 59, or NULL if time + is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: SEC_TO_TIME + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: SEC_TO_TIME(seconds) + args: + - name: seconds + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the seconds argument, converted to hours, minutes, and seconds, + description: 'Returns the seconds argument, converted to hours, minutes, and seconds, + as a TIME value. The range of the result is constrained to that of the TIME + data type. A warning occurs if the argument corresponds to a value outside that + range. The function returns NULL if seconds is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: SESSION_USER + category_id: information_functions + category_label: Information Functions + signature: + display: SESSION_USER + args: [] + tags: [] + aliases: [] + summary: SESSION_USER() is a synonym for USER(). + description: 'SESSION_USER() is a synonym for USER(). URL: https://dev.mysql.com/doc/refman/8.3/en/information-functions.html' + examples: [] + - name: SHA1 + category_id: encryption_functions + category_label: Encryption Functions + signature: + display: SHA1(str) + args: + - name: str + optional: false + type: any + tags: [] + aliases: [] + summary: Calculates an SHA-1 160-bit checksum for the string, as described in + description: 'Calculates an SHA-1 160-bit checksum for the string, as described + in RFC 3174 (Secure Hash Algorithm). The value is returned as a string of 40 + hexadecimal digits, or NULL if the argument is NULL. One of the possible uses + for this function is as a hash key. See the notes at the beginning of this section + about storing hash values efficiently. SHA() is synonymous with SHA1(). The + return value is a string in the connection character set. URL: https://dev.mysql.com/doc/refman/8.3/en/encryption-functions.html' + examples: [] + - name: SHA2 + category_id: encryption_functions + category_label: Encryption Functions + signature: + display: SHA2(str, hash_length) + args: + - name: str + optional: false + type: any + - name: hash_length + optional: false + type: any + tags: [] + aliases: [] + summary: Calculates the SHA-2 family of hash functions (SHA-224, SHA-256, + description: 'Calculates the SHA-2 family of hash functions (SHA-224, SHA-256, + SHA-384, and SHA-512). The first argument is the plaintext string to be hashed. + The second argument indicates the desired bit length of the result, which must + have a value of 224, 256, 384, 512, or 0 (which is equivalent to 256). If either + argument is NULL or the hash length is not one of the permitted values, the + return value is NULL. Otherwise, the function result is a hash value containing + the desired number of bits. See the notes at the beginning of this section about + storing hash values efficiently. The return value is a string in the connection + character set. URL: https://dev.mysql.com/doc/refman/8.3/en/encryption-functions.html' + examples: [] + - name: SIGN + category_id: numeric_functions + category_label: Numeric Functions + signature: + display: SIGN(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the sign of the argument as -1, 0, or 1, depending on whether + X + description: 'Returns the sign of the argument as -1, 0, or 1, depending on whether + X is negative, zero, or positive. Returns NULL if X is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html' + examples: [] + - name: SIN + category_id: numeric_functions + category_label: Numeric Functions + signature: + display: SIN(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the sine of X, where X is given in radians. + description: 'Returns the sine of X, where X is given in radians. Returns NULL + if X is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html' + examples: [] + - name: SLEEP + category_id: miscellaneous_functions + category_label: Miscellaneous Functions + signature: + display: SLEEP(duration) + args: + - name: duration + optional: false + type: any + tags: [] + aliases: [] + summary: Sleeps (pauses) for the number of seconds given by the duration + description: 'Sleeps (pauses) for the number of seconds given by the duration + argument, then returns 0. The duration may have a fractional part. If the argument + is NULL or negative, SLEEP() produces a warning, or an error in strict SQL mode. + When sleep returns normally (without interruption), it returns 0: mysql> SELECT + SLEEP(1000); +-------------+ | SLEEP(1000) | +-------------+ | 0 | + +-------------+ When SLEEP() is the only thing invoked by a query that is interrupted, + it returns 1 and the query itself returns no error. This is true whether the + query is killed or times out: o This statement is interrupted using KILL QUERY + from another session: mysql> SELECT SLEEP(1000); +-------------+ | SLEEP(1000) + | +-------------+ | 1 | +-------------+ o This statement is interrupted + by timing out: mysql> SELECT /*+ MAX_EXECUTION_TIME(1) */ SLEEP(1000); +-------------+ + | SLEEP(1000) | +-------------+ | 1 | +-------------+ When SLEEP() + is only part of a query that is interrupted, the query returns an error: o This + statement is interrupted using KILL QUERY from another session: mysql> SELECT + 1 FROM t1 WHERE SLEEP(1000); ERROR 1317 (70100): Query execution was interrupted + o This statement is interrupted by timing out: mysql> SELECT /*+ MAX_EXECUTION_TIME(1000) + */ 1 FROM t1 WHERE SLEEP(1000); ERROR 3024 (HY000): Query execution was interrupted, + maximum statement execution time exceeded URL: https://dev.mysql.com/doc/refman/8.3/en/miscellaneous-functions.html' + examples: [] + - name: SMALLINT + category_id: data_types + category_label: Data Types + signature: + display: SMALLINT(M) + args: + - name: M + optional: false + type: any + tags: [] + aliases: [] + summary: A small integer. + description: 'A small integer. The signed range is -32768 to 32767. The unsigned + range is 0 to 65535. URL: https://dev.mysql.com/doc/refman/8.3/en/numeric-type-syntax.html' + examples: [] + - name: SOUNDEX + category_id: string_functions + category_label: String Functions + signature: + display: SOUNDEX(str) + args: + - name: str + optional: false + type: any + tags: [] + aliases: [] + summary: Returns a soundex string from str, or NULL if str is NULL. + description: "Returns a soundex string from str, or NULL if str is NULL. Two strings\n\ + that sound almost the same should have identical soundex strings. A\nstandard\ + \ soundex string is four characters long, but the SOUNDEX()\nfunction returns\ + \ an arbitrarily long string. You can use SUBSTRING() on\nthe result to get\ + \ a standard soundex string. All nonalphabetic\ncharacters in str are ignored.\ + \ All international alphabetic characters\noutside the A-Z range are treated\ + \ as vowels.\n\n*Important*:\n\nWhen using SOUNDEX(), you should be aware of\ + \ the following limitations:\n\no This function, as currently implemented, is\ + \ intended to work well\n with strings that are in the English language only.\ + \ Strings in other\n languages may not produce reliable results.\n\no This\ + \ function is not guaranteed to provide consistent results with\n strings that\ + \ use multibyte character sets, including utf-8. See Bug\n #22638 for more\ + \ information.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html" + examples: [] + - name: SOURCE_POS_WAIT + category_id: gtid + category_label: GTID + signature: + display: SOURCE_POS_WAIT(log_name,log_pos[,timeout][,channel]) + args: + - name: log_name + optional: false + type: any + - name: log_pos[ + optional: false + type: any + - name: timeout][ + optional: false + type: any + - name: channel] + optional: false + type: any + tags: [] + aliases: [] + summary: This function is for control of source-replica synchronization. + description: 'This function is for control of source-replica synchronization. + It blocks until the replica has read and applied all updates up to the specified + position in the source''s binary log. The return value is the number of log + events the replica had to wait for to advance to the specified position. The + function returns NULL if the replication SQL thread is not started, the replica''s + source information is not initialized, the arguments are incorrect, or an error + occurs. It returns -1 if the timeout has been exceeded. If the replication SQL + thread stops while SOURCE_POS_WAIT() is waiting, the function returns NULL. + If the replica is past the specified position, the function returns immediately. + If the binary log file position has been marked as invalid, the function waits + until a valid file position is known. The binary log file position can be marked + as invalid when the CHANGE REPLICATION SOURCE TO option GTID_ONLY is set for + the replication channel, and the server is restarted or replication is stopped. + The file position becomes valid after a transaction is successfully applied + past the given file position. If the applier does not reach the stated position, + the function waits until the timeout. Use a SHOW REPLICA STATUS statement to + check if the binary log file position has been marked as invalid. On a multithreaded + replica, the function waits until expiry of the limit set by the replica_checkpoint_group + or replica_checkpoint_period system variable, when the checkpoint operation + is called to update the status of the replica. Depending on the setting for + the system variables, the function might therefore return some time after the + specified position was reached. If binary log transaction compression is in + use and the transaction payload at the specified position is compressed (as + a Transaction_payload_event), the function waits until the whole transaction + has been read and applied, and the positions have updated. If a timeout value + is specified, SOURCE_POS_WAIT() stops waiting when timeout seconds have elapsed. + timeout must be greater than or equal to 0. (When the server is running in strict + SQL mode, a negative timeout value is immediately rejected with ER_WRONG_ARGUMENTS + (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference.html #error_er_wrong_arguments); + otherwise the function returns NULL, and raises a warning.) The optional channel + value enables you to name which replication channel the function applies to. + See https://dev.mysql.com/doc/refman/8.3/en/replication-channels.html for more + information. URL: https://dev.mysql.com/doc/refman/8.3/en/replication-functions-synchronization.html' + examples: [] + - name: SPACE + category_id: string_functions + category_label: String Functions + signature: + display: SPACE(N) + args: + - name: N + optional: false + type: any + tags: [] + aliases: [] + summary: Returns a string consisting of N space characters, or NULL if N is + description: 'Returns a string consisting of N space characters, or NULL if N + is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html' + examples: [] + - name: SQRT + category_id: numeric_functions + category_label: Numeric Functions + signature: + display: SQRT(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the square root of a nonnegative number X. + description: 'Returns the square root of a nonnegative number X. If X is NULL, + the function returns NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html' + examples: [] + - name: STATEMENT_DIGEST + category_id: encryption_functions + category_label: Encryption Functions + signature: + display: STATEMENT_DIGEST(statement) + args: + - name: statement + optional: false + type: any + tags: [] + aliases: [] + summary: Given an SQL statement as a string, returns the statement digest hash + description: 'Given an SQL statement as a string, returns the statement digest + hash value as a string in the connection character set, or NULL if the argument + is NULL. The related STATEMENT_DIGEST_TEXT() function returns the normalized + statement digest. For information about statement digesting, see https://dev.mysql.com/doc/refman/8.3/en/performance-schema-statement-di + gests.html. Both functions use the MySQL parser to parse the statement. If parsing + fails, an error occurs. The error message includes the parse error only if the + statement is provided as a literal string. The max_digest_length system variable + determines the maximum number of bytes available to these functions for computing + normalized statement digests. URL: https://dev.mysql.com/doc/refman/8.3/en/encryption-functions.html' + examples: [] + - name: STATEMENT_DIGEST_TEXT + category_id: encryption_functions + category_label: Encryption Functions + signature: + display: STATEMENT_DIGEST_TEXT(statement) + args: + - name: statement + optional: false + type: any + tags: [] + aliases: [] + summary: Given an SQL statement as a string, returns the normalized statement + description: 'Given an SQL statement as a string, returns the normalized statement + digest as a string in the connection character set, or NULL if the argument + is NULL. For additional discussion and examples, see the description of the + related STATEMENT_DIGEST() function. URL: https://dev.mysql.com/doc/refman/8.3/en/encryption-functions.html' + examples: [] + - name: STD + category_id: aggregate_functions_and_modifiers + category_label: Aggregate Functions and Modifiers + signature: + display: STD(expr) + args: + - name: expr + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the population standard deviation of expr. + description: 'Returns the population standard deviation of expr. STD() is a synonym + for the standard SQL function STDDEV_POP(), provided as a MySQL extension. If + there are no matching rows, or if expr is NULL, STD() returns NULL. This function + executes as a window function if over_clause is present. over_clause is as described + in https://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html. URL: + https://dev.mysql.com/doc/refman/8.3/en/aggregate-functions.html' + examples: [] + - name: STDDEV + category_id: aggregate_functions_and_modifiers + category_label: Aggregate Functions and Modifiers + signature: + display: STDDEV(expr) + args: + - name: expr + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the population standard deviation of expr. + description: 'Returns the population standard deviation of expr. STDDEV() is a + synonym for the standard SQL function STDDEV_POP(), provided for compatibility + with Oracle. If there are no matching rows, or if expr is NULL, STDDEV() returns + NULL. This function executes as a window function if over_clause is present. + over_clause is as described in https://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html. + URL: https://dev.mysql.com/doc/refman/8.3/en/aggregate-functions.html' + examples: [] + - name: STDDEV_POP + category_id: aggregate_functions_and_modifiers + category_label: Aggregate Functions and Modifiers + signature: + display: STDDEV_POP(expr) + args: + - name: expr + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the population standard deviation of expr (the square root of + description: 'Returns the population standard deviation of expr (the square root + of VAR_POP()). You can also use STD() or STDDEV(), which are equivalent but + not standard SQL. If there are no matching rows, or if expr is NULL, STDDEV_POP() + returns NULL. This function executes as a window function if over_clause is + present. over_clause is as described in https://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html. + URL: https://dev.mysql.com/doc/refman/8.3/en/aggregate-functions.html' + examples: [] + - name: STDDEV_SAMP + category_id: aggregate_functions_and_modifiers + category_label: Aggregate Functions and Modifiers + signature: + display: STDDEV_SAMP(expr) + args: + - name: expr + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the sample standard deviation of expr (the square root of + description: 'Returns the sample standard deviation of expr (the square root of + VAR_SAMP(). If there are no matching rows, or if expr is NULL, STDDEV_SAMP() + returns NULL. This function executes as a window function if over_clause is + present. over_clause is as described in https://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html. + URL: https://dev.mysql.com/doc/refman/8.3/en/aggregate-functions.html' + examples: [] + - name: STRCMP + category_id: string_functions + category_label: String Functions + signature: + display: STRCMP(expr1,expr2) + args: + - name: expr1 + optional: false + type: any + - name: expr2 + optional: false + type: any + tags: [] + aliases: [] + summary: STRCMP() returns 0 if the strings are the same, -1 if the first + description: 'STRCMP() returns 0 if the strings are the same, -1 if the first + argument is smaller than the second according to the current sort order, and + NULL if either argument is NULL. It returns 1 otherwise. URL: https://dev.mysql.com/doc/refman/8.3/en/string-comparison-functions.html' + examples: [] + - name: STR_TO_DATE + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: STR_TO_DATE(str,format) + args: + - name: str + optional: false + type: any + - name: format + optional: false + type: any + tags: [] + aliases: [] + summary: This is the inverse of the DATE_FORMAT() function. + description: "This is the inverse of the DATE_FORMAT() function. It takes a string\n\ + str and a format string format. STR_TO_DATE() returns a DATETIME value\nif the\ + \ format string contains both date and time parts, or a DATE or\nTIME value\ + \ if the string contains only date or time parts. If str or\nformat is NULL,\ + \ the function returns NULL. If the date, time, or\ndatetime value extracted\ + \ from str cannot be parsed according to the\nrules followed by the server,\ + \ STR_TO_DATE() returns NULL and produces a\nwarning.\n\nThe server scans str\ + \ attempting to match format to it. The format\nstring can contain literal characters\ + \ and format specifiers beginning\nwith %. Literal characters in format must\ + \ match literally in str.\nFormat specifiers in format must match a date or\ + \ time part in str. For\nthe specifiers that can be used in format, see the\ + \ DATE_FORMAT()\nfunction description.\n\nmysql> SELECT STR_TO_DATE('01,5,2013','%d,%m,%Y');\n\ + \ -> '2013-05-01'\nmysql> SELECT STR_TO_DATE('May 1, 2013','%M %d,%Y');\n\ + \ -> '2013-05-01'\n\nScanning starts at the beginning of str and fails\ + \ if format is found\nnot to match. Extra characters at the end of str are ignored.\n\ + \nmysql> SELECT STR_TO_DATE('a09:30:17','a%h:%i:%s');\n -> '09:30:17'\n\ + mysql> SELECT STR_TO_DATE('a09:30:17','%h:%i:%s');\n -> NULL\nmysql>\ + \ SELECT STR_TO_DATE('09:30:17a','%h:%i:%s');\n -> '09:30:17'\n\nUnspecified\ + \ date or time parts have a value of 0, so incompletely\nspecified values in\ + \ str produce a result with some or all parts set to\n0:\n\nmysql> SELECT STR_TO_DATE('abc','abc');\n\ + \ -> '0000-00-00'\nmysql> SELECT STR_TO_DATE('9','%m');\n -> '0000-09-00'\n\ + mysql> SELECT STR_TO_DATE('9','%s');\n -> '00:00:09'\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html" + examples: [] + - name: ST_AREA + category_id: polygon_property_functions + category_label: Polygon Property Functions + signature: + display: ST_AREA({poly|mpoly}) + args: + - name: '{poly|mpoly}' + optional: false + type: any + tags: [] + aliases: [] + summary: Returns a double-precision number indicating the area of the Polygon + or + description: "Returns a double-precision number indicating the area of the Polygon\ + \ or\nMultiPolygon argument, as measured in its spatial reference system.\n\n\ + ST_Area() handles its arguments as described in the introduction to\nthis section,\ + \ with these exceptions:\n\no If the geometry is geometrically invalid, either\ + \ the result is an\n undefined area (that is, it can be any number), or an\ + \ error occurs.\n\no If the geometry is valid but is not a Polygon or MultiPolygon\ + \ object,\n an ER_UNEXPECTED_GEOMETRY_TYPE\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n\ + \ .html#error_er_unexpected_geometry_type) error occurs.\n\no If the geometry\ + \ is a valid Polygon in a Cartesian SRS, the result is\n the Cartesian area\ + \ of the polygon.\n\no If the geometry is a valid MultiPolygon in a Cartesian\ + \ SRS, the\n result is the sum of the Cartesian area of the polygons.\n\no\ + \ If the geometry is a valid Polygon in a geographic SRS, the result is\n the\ + \ geodetic area of the polygon in that SRS, in square meters.\n\no If the geometry\ + \ is a valid MultiPolygon in a geographic SRS, the\n result is the sum of geodetic\ + \ area of the polygons in that SRS, in\n square meters.\n\no If an area computation\ + \ results in +inf, an ER_DATA_OUT_OF_RANGE\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n\ + \ .html#error_er_data_out_of_range) error occurs.\n\no If the geometry has\ + \ a geographic SRS with a longitude or latitude\n that is out of range, an\ + \ error occurs:\n\n o If a longitude value is not in the range (\u2212180,\ + \ 180], an\n ER_GEOMETRY_PARAM_LONGITUDE_OUT_OF_RANGE\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-referen\n\ + \ ce.html#error_er_geometry_param_longitude_out_of_range) error\n occurs.\n\ + \n o If a latitude value is not in the range [\u221290, 90], an\n ER_GEOMETRY_PARAM_LATITUDE_OUT_OF_RANGE\n\ + \ (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-referen\n \ + \ ce.html#error_er_geometry_param_latitude_out_of_range) error\n occurs.\n\ + \n Ranges shown are in degrees. The exact range limits deviate slightly\n \ + \ due to floating-point arithmetic.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-polygon-property-functions.html" + examples: [] + - name: ST_ASBINARY + category_id: wkb_functions + category_label: WKB Functions + signature: + display: ST_ASBINARY(g [, options]) + args: + - name: g [ + optional: false + type: any + - name: options] + optional: false + type: any + tags: [] + aliases: [] + summary: Converts a value in internal geometry format to its WKB representation + description: 'Converts a value in internal geometry format to its WKB representation + and returns the binary result. The function return value has geographic coordinates + (latitude, longitude) in the order specified by the spatial reference system + that applies to the geometry argument. An optional options argument may be given + to override the default axis order. ST_AsBinary() and ST_AsWKB() handle their + arguments as described in the introduction to this section. URL: https://dev.mysql.com/doc/refman/8.3/en/gis-format-conversion-functions.html' + examples: [] + - name: ST_ASGEOJSON + category_id: mbr_functions + category_label: MBR Functions + signature: + display: ST_ASGEOJSON(g [, max_dec_digits [, options]]) + args: + - name: g [ + optional: false + type: any + - name: max_dec_digits [ + optional: false + type: any + - name: options]] + optional: false + type: any + tags: [] + aliases: [] + summary: Generates a GeoJSON object from the geometry g. + description: 'Generates a GeoJSON object from the geometry g. The object string + has the connection character set and collation. If any argument is NULL, the + return value is NULL. If any non-NULL argument is invalid, an error occurs. + URL: https://dev.mysql.com/doc/refman/8.3/en/spatial-geojson-functions.html' + examples: [] + - name: ST_ASTEXT + category_id: wkb_functions + category_label: WKB Functions + signature: + display: ST_ASTEXT(g [, options]) + args: + - name: g [ + optional: false + type: any + - name: options] + optional: false + type: any + tags: [] + aliases: [] + summary: Converts a value in internal geometry format to its WKT representation + description: 'Converts a value in internal geometry format to its WKT representation + and returns the string result. The function return value has geographic coordinates + (latitude, longitude) in the order specified by the spatial reference system + that applies to the geometry argument. An optional options argument may be given + to override the default axis order. ST_AsText() and ST_AsWKT() handle their + arguments as described in the introduction to this section. URL: https://dev.mysql.com/doc/refman/8.3/en/gis-format-conversion-functions.html' + examples: [] + - name: ST_BUFFER + category_id: geometrycollection_property_functions + category_label: GeometryCollection Property Functions + signature: + display: ST_BUFFER(g, d [, strategy1 [, strategy2 [, strategy3]]]) + args: + - name: g + optional: false + type: any + - name: d [ + optional: false + type: any + - name: strategy1 [ + optional: false + type: any + - name: strategy2 [ + optional: false + type: any + - name: strategy3]]] + optional: false + type: any + tags: [] + aliases: [] + summary: Returns a geometry that represents all points whose distance from the + description: "Returns a geometry that represents all points whose distance from\ + \ the\ngeometry value g is less than or equal to a distance of d. The result\n\ + is in the same SRS as the geometry argument.\n\nIf the geometry argument is\ + \ empty, ST_Buffer() returns an empty\ngeometry.\n\nIf the distance is 0, ST_Buffer()\ + \ returns the geometry argument\nunchanged:\n\nmysql> SET @pt = ST_GeomFromText('POINT(0\ + \ 0)');\nmysql> SELECT ST_AsText(ST_Buffer(@pt, 0));\n+------------------------------+\n\ + | ST_AsText(ST_Buffer(@pt, 0)) |\n+------------------------------+\n| POINT(0\ + \ 0) |\n+------------------------------+\n\nIf the geometry\ + \ argument is in a Cartesian SRS:\n\no ST_Buffer() supports negative distances\ + \ for Polygon and MultiPolygon\n values, and for geometry collections containing\ + \ Polygon or\n MultiPolygon values.\n\no If the result is reduced so much that\ + \ it disappears, the result is an\n empty geometry.\n\no An ER_WRONG_ARGUMENTS\n\ + \ (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n \ + \ .html#error_er_wrong_arguments) error occurs for ST_Buffer() with a\n negative\ + \ distance for Point, MultiPoint, LineString, and\n MultiLineString values,\ + \ and for geometry collections not containing\n any Polygon or MultiPolygon\ + \ values.\n\nPoint geometries in a geographic SRS are permitted, subject to\ + \ the\nfollowing conditions:\n\no If the distance is not negative and no strategies\ + \ are specified, the\n function returns the geographic buffer of the Point\ + \ in its SRS. The\n distance argument must be in the SRS distance unit (currently\ + \ always\n meters).\n\no If the distance is negative or any strategy (except\ + \ NULL) is\n specified, an ER_WRONG_ARGUMENTS\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n\ + \ .html#error_er_wrong_arguments) error occurs.\n\nFor non-Point geometries,\ + \ an ER_NOT_IMPLEMENTED_FOR_GEOGRAPHIC_SRS\n(https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference.html\n\ + #error_er_not_implemented_for_geographic_srs) error occurs.\n ..." + examples: [] + - name: ST_BUFFER_STRATEGY + category_id: geometrycollection_property_functions + category_label: GeometryCollection Property Functions + signature: + display: ST_BUFFER_STRATEGY(strategy [, points_per_circle]) + args: + - name: strategy [ + optional: false + type: any + - name: points_per_circle] + optional: false + type: any + tags: [] + aliases: [] + summary: This function returns a strategy byte string for use with ST_Buffer() + description: "This function returns a strategy byte string for use with ST_Buffer()\n\ + to influence buffer computation.\n\nInformation about strategies is available\ + \ at Boost.org\n(http://www.boost.org).\n\nThe first argument must be a string\ + \ indicating a strategy option:\n\no For point strategies, permitted values\ + \ are 'point_circle' and\n 'point_square'.\n\no For join strategies, permitted\ + \ values are 'join_round' and\n 'join_miter'.\n\no For end strategies, permitted\ + \ values are 'end_round' and 'end_flat'.\n\nIf the first argument is 'point_circle',\ + \ 'join_round', 'join_miter', or\n'end_round', the points_per_circle argument\ + \ must be given as a positive\nnumeric value. The maximum points_per_circle\ + \ value is the value of the\nmax_points_in_geometry system variable.\n\nFor\ + \ examples, see the description of ST_Buffer().\n\nST_Buffer_Strategy() handles\ + \ its arguments as described in the\nintroduction to this section, with these\ + \ exceptions:\n\no If any argument is invalid, an ER_WRONG_ARGUMENTS\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n\ + \ .html#error_er_wrong_arguments) error occurs.\n\no If the first argument\ + \ is 'point_square' or 'end_flat', the\n points_per_circle argument must not\ + \ be given or an ER_WRONG_ARGUMENTS\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n\ + \ .html#error_er_wrong_arguments) error occurs.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-operator-functions.html" + examples: [] + - name: ST_CENTROID + category_id: polygon_property_functions + category_label: Polygon Property Functions + signature: + display: ST_CENTROID({poly|mpoly}) + args: + - name: '{poly|mpoly}' + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the mathematical centroid for the Polygon or MultiPolygon + description: "Returns the mathematical centroid for the Polygon or MultiPolygon\n\ + argument as a Point. The result is not guaranteed to be on the\nMultiPolygon.\n\ + \nThis function processes geometry collections by computing the centroid\npoint\ + \ for components of highest dimension in the collection. Such\ncomponents are\ + \ extracted and made into a single MultiPolygon,\nMultiLineString, or MultiPoint\ + \ for centroid computation.\n\nST_Centroid() handles its arguments as described\ + \ in the introduction to\nthis section, with these exceptions:\n\no The return\ + \ value is NULL for the additional condition that the\n argument is an empty\ + \ geometry collection.\n\no If the geometry has an SRID value for a geographic\ + \ spatial reference\n system (SRS), an ER_NOT_IMPLEMENTED_FOR_GEOGRAPHIC_SRS\n\ + \ (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n \ + \ .html#error_er_not_implemented_for_geographic_srs) error occurs.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-polygon-property-functions.html" + examples: [] + - name: ST_COLLECT + category_id: mbr_functions + category_label: MBR Functions + signature: + display: ST_COLLECT([DISTINCT] g) + args: + - name: '[DISTINCT] g' + optional: false + type: any + tags: [] + aliases: [] + summary: Aggregates geometry values and returns a single geometry collection + description: "Aggregates geometry values and returns a single geometry collection\n\ + value. With the DISTINCT option, returns the aggregation of the\ndistinct geometry\ + \ arguments.\n\nAs with other aggregate functions, GROUP BY may be used to group\n\ + arguments into subsets. ST_Collect() returns an aggregate value for\neach subset.\n\ + \nThis function executes as a window function if over_clause is present.\nover_clause\ + \ is as described in\nhttps://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html.\ + \ In\ncontrast to most aggregate functions that support windowing,\nST_Collect()\ + \ permits use of over_clause together with DISTINCT.\n\nST_Collect() handles\ + \ its arguments as follows:\n\no NULL arguments are ignored.\n\no If all arguments\ + \ are NULL or the aggregate result is empty, the\n return value is NULL.\n\n\ + o If any geometry argument is not a syntactically well-formed geometry,\n an\ + \ ER_GIS_INVALID_DATA\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n\ + \ .html#error_er_gis_invalid_data) error occurs.\n\no If any geometry argument\ + \ is a syntactically well-formed geometry in\n an undefined spatial reference\ + \ system (SRS), an ER_SRS_NOT_FOUND\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n\ + \ .html#error_er_srs_not_found) error occurs.\n\no If there are multiple geometry\ + \ arguments and those arguments are in\n the same SRS, the return value is\ + \ in that SRS. If those arguments are\n not in the same SRS, an ER_GIS_DIFFERENT_SRIDS_AGGREGATION\n\ + \ (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n \ + \ .html#error_er_gis_different_srids_aggregation) error occurs.\n\no The result\ + \ is the narrowest MultiXxx or GeometryCollection value\n possible, with the\ + \ result type determined from the non-NULL geometry\n arguments as follows:\n\ + \n o If all arguments are Point values, the result is a MultiPoint\n value.\n\ + \n o If all arguments are LineString values, the result is a\n MultiLineString\ + \ value.\n\n o If all arguments are Polygon values, the result is a MultiPolygon\n\ + \ value.\n\n ..." + examples: [] + - name: ST_CONTAINS + category_id: geometry_relation_functions + category_label: Geometry Relation Functions + signature: + display: ST_CONTAINS(g1, g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + tags: [] + aliases: [] + summary: Returns 1 or 0 to indicate whether g1 completely contains g2. + description: 'Returns 1 or 0 to indicate whether g1 completely contains g2. This + tests the opposite relationship as ST_Within(). ST_Contains() handles its arguments + as described in the introduction to this section. URL: https://dev.mysql.com/doc/refman/8.3/en/spatial-relation-functions-object-shapes.html' + examples: [] + - name: ST_CONVEXHULL + category_id: geometrycollection_property_functions + category_label: GeometryCollection Property Functions + signature: + display: ST_CONVEXHULL(g) + args: + - name: g + optional: false + type: any + tags: [] + aliases: [] + summary: Returns a geometry that represents the convex hull of the geometry + description: "Returns a geometry that represents the convex hull of the geometry\n\ + value g.\n\nThis function computes a geometry's convex hull by first checking\n\ + whether its vertex points are colinear. The function returns a linear\nhull\ + \ if so, a polygon hull otherwise. This function processes geometry\ncollections\ + \ by extracting all vertex points of all components of the\ncollection, creating\ + \ a MultiPoint value from them, and computing its\nconvex hull.\n\nST_ConvexHull()\ + \ handles its arguments as described in the introduction\nto this section, with\ + \ this exception:\n\no The return value is NULL for the additional condition\ + \ that the\n argument is an empty geometry collection.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-operator-functions.html" + examples: [] + - name: ST_CROSSES + category_id: geometry_relation_functions + category_label: Geometry Relation Functions + signature: + display: ST_CROSSES(g1, g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + tags: [] + aliases: [] + summary: Two geometries spatially cross if their spatial relation has the + description: "Two geometries spatially cross if their spatial relation has the\n\ + following properties:\n\no Unless g1 and g2 are both of dimension 1: g1 crosses\ + \ g2 if the\n interior of g2 has points in common with the interior of g1,\ + \ but g2\n does not cover the entire interior of g1.\n\no If both g1 and g2\ + \ are of dimension 1: If the lines cross each other\n in a finite number of\ + \ points (that is, no common line segments, only\n single points in common).\n\ + \nThis function returns 1 or 0 to indicate whether g1 spatially crosses\ng2.\n\ + \nST_Crosses() handles its arguments as described in the introduction to\nthis\ + \ section except that the return value is NULL for these additional\nconditions:\n\ + \no g1 is of dimension 2 (Polygon or MultiPolygon).\n\no g2 is of dimension\ + \ 1 (Point or MultiPoint).\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-relation-functions-object-shapes.html" + examples: [] + - name: ST_DIFFERENCE + category_id: geometrycollection_property_functions + category_label: GeometryCollection Property Functions + signature: + display: ST_DIFFERENCE(g1, g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + tags: [] + aliases: [] + summary: Returns a geometry that represents the point set difference of the + description: 'Returns a geometry that represents the point set difference of the + geometry values g1 and g2. The result is in the same SRS as the geometry arguments. + ST_Difference() permits arguments in either a Cartesian or a geographic SRS, + and handles its arguments as described in the introduction to this section. + URL: https://dev.mysql.com/doc/refman/8.3/en/spatial-operator-functions.html' + examples: [] + - name: ST_DIMENSION + category_id: geometry_property_functions + category_label: Geometry Property Functions + signature: + display: ST_DIMENSION(g) + args: + - name: g + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the inherent dimension of the geometry value g. + description: "Returns the inherent dimension of the geometry value g. The dimension\n\ + can be \u22121, 0, 1, or 2. The meaning of these values is given in\nhttps://dev.mysql.com/doc/refman/8.3/en/gis-class-geometry.html.\n\ + \nST_Dimension() handles its arguments as described in the introduction\nto\ + \ this section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-general-property-functions.html" + examples: [] + - name: ST_DISJOINT + category_id: geometry_relation_functions + category_label: Geometry Relation Functions + signature: + display: ST_DISJOINT(g1, g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + tags: [] + aliases: [] + summary: Returns 1 or 0 to indicate whether g1 is spatially disjoint from (does + description: 'Returns 1 or 0 to indicate whether g1 is spatially disjoint from + (does not intersect) g2. ST_Disjoint() handles its arguments as described in + the introduction to this section. URL: https://dev.mysql.com/doc/refman/8.3/en/spatial-relation-functions-object-shapes.html' + examples: [] + - name: ST_DISTANCE + category_id: geometry_relation_functions + category_label: Geometry Relation Functions + signature: + display: ST_DISTANCE(g1, g2 [, unit]) + args: + - name: g1 + optional: false + type: any + - name: g2 [ + optional: false + type: any + - name: unit] + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the distance between g1 and g2, measured in the length unit of + description: "Returns the distance between g1 and g2, measured in the length unit\ + \ of\nthe spatial reference system (SRS) of the geometry arguments, or in the\n\ + unit of the optional unit argument if that is specified.\n\nThis function processes\ + \ geometry collections by returning the shortest\ndistance among all combinations\ + \ of the components of the two geometry\narguments.\n\nST_Distance() handles\ + \ its geometry arguments as described in the\nintroduction to this section,\ + \ with these exceptions:\n\no ST_Distance() detects arguments in a geographic\ + \ (ellipsoidal) spatial\n reference system and returns the geodetic distance\ + \ on the ellipsoid.\n ST_Distance() supports distance calculations for geographic\ + \ SRS\n arguments of all geometry types.\n\no If any argument is geometrically\ + \ invalid, either the result is an\n undefined distance (that is, it can be\ + \ any number), or an error\n occurs.\n\no If an intermediate or final result\ + \ produces NaN or a negative number,\n an ER_GIS_INVALID_DATA\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n\ + \ .html#error_er_gis_invalid_data) error occurs.\n\nST_Distance() permits specifying\ + \ the linear unit for the returned\ndistance value with an optional unit argument\ + \ which ST_Distance()\nhandles as described in the introduction to this section.\n\ + \nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-relation-functions-object-shapes.html" + examples: [] + - name: ST_DISTANCE_SPHERE + category_id: mbr_functions + category_label: MBR Functions + signature: + display: ST_DISTANCE_SPHERE(g1, g2 [, radius]) + args: + - name: g1 + optional: false + type: any + - name: g2 [ + optional: false + type: any + - name: radius] + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the minimum spherical distance between Point or MultiPoint + description: "Returns the minimum spherical distance between Point or MultiPoint\n\ + arguments on a sphere, in meters. (For general-purpose distance\ncalculations,\ + \ see the ST_Distance() function.) The optional radius\nargument should be given\ + \ in meters.\n\nIf both geometry parameters are valid Cartesian Point or MultiPoint\n\ + values in SRID 0, the return value is shortest distance between the two\ngeometries\ + \ on a sphere with the provided radius. If omitted, the\ndefault radius is 6,370,986\ + \ meters, Point X and Y coordinates are\ninterpreted as longitude and latitude,\ + \ respectively, in degrees.\n\nIf both geometry parameters are valid Point or\ + \ MultiPoint values in a\ngeographic spatial reference system (SRS), the return\ + \ value is the\nshortest distance between the two geometries on a sphere with\ + \ the\nprovided radius. If omitted, the default radius is equal to the mean\n\ + radius, defined as (2a+b)/3, where a is the semi-major axis and b is\nthe semi-minor\ + \ axis of the SRS.\n\nST_Distance_Sphere() handles its arguments as described\ + \ in the\nintroduction to this section, with these exceptions:\n\no Supported\ + \ geometry argument combinations are Point and Point, or\n Point and MultiPoint\ + \ (in any argument order). If at least one of the\n geometries is neither Point\ + \ nor MultiPoint, and its SRID is 0, an\n ER_NOT_IMPLEMENTED_FOR_CARTESIAN_SRS\n\ + \ (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n \ + \ .html#error_er_not_implemented_for_cartesian_srs) error occurs. If at\n least\ + \ one of the geometries is neither Point nor MultiPoint, and its\n SRID refers\ + \ to a geographic SRS, an\n ER_NOT_IMPLEMENTED_FOR_GEOGRAPHIC_SRS\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n\ + \ .html#error_er_not_implemented_for_geographic_srs) error occurs. If\n any\ + \ geometry refers to a projected SRS, an\n ER_NOT_IMPLEMENTED_FOR_PROJECTED_SRS\n\ + \ (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n \ + \ .html#error_er_not_implemented_for_projected_srs) error occurs.\n\no If any\ + \ argument has a longitude or latitude that is out of range, an\n error occurs:\n\ + \n o If a longitude value is not in the range (\u2212180, 180], an\n ER_GEOMETRY_PARAM_LONGITUDE_OUT_OF_RANGE\n\ + \ (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-referen\n \ + \ ce.html#error_er_geometry_param_longitude_out_of_range) error\n occurs.\n\ + \n o If a latitude value is not in the range [\u221290, 90], an\n ER_GEOMETRY_PARAM_LATITUDE_OUT_OF_RANGE\n\ + \ (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-referen\n \ + \ ce.html#error_er_geometry_param_latitude_out_of_range) error\n ..." + examples: [] + - name: ST_ENDPOINT + category_id: linestring_property_functions + category_label: LineString Property Functions + signature: + display: ST_ENDPOINT(ls) + args: + - name: ls + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the Point that is the endpoint of the LineString value ls. + description: 'Returns the Point that is the endpoint of the LineString value ls. + ST_EndPoint() handles its arguments as described in the introduction to this + section. URL: https://dev.mysql.com/doc/refman/8.3/en/gis-linestring-property-functions.html' + examples: [] + - name: ST_ENVELOPE + category_id: geometry_property_functions + category_label: Geometry Property Functions + signature: + display: ST_ENVELOPE(g) + args: + - name: g + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the minimum bounding rectangle (MBR) for the geometry value g. + description: "Returns the minimum bounding rectangle (MBR) for the geometry value\ + \ g.\nThe result is returned as a Polygon value that is defined by the corner\n\ + points of the bounding box:\n\nPOLYGON((MINX MINY, MAXX MINY, MAXX MAXY, MINX\ + \ MAXY, MINX MINY))\n\nmysql> SELECT ST_AsText(ST_Envelope(ST_GeomFromText('LineString(1\ + \ 1,2 2)')));\n+----------------------------------------------------------------+\n\ + | ST_AsText(ST_Envelope(ST_GeomFromText('LineString(1 1,2 2)'))) |\n+----------------------------------------------------------------+\n\ + | POLYGON((1 1,2 1,2 2,1 2,1 1)) |\n+----------------------------------------------------------------+\n\ + \nIf the argument is a point or a vertical or horizontal line segment,\nST_Envelope()\ + \ returns the point or the line segment as its MBR rather\nthan returning an\ + \ invalid polygon:\n\nmysql> SELECT ST_AsText(ST_Envelope(ST_GeomFromText('LineString(1\ + \ 1,1 2)')));\n+----------------------------------------------------------------+\n\ + | ST_AsText(ST_Envelope(ST_GeomFromText('LineString(1 1,1 2)'))) |\n+----------------------------------------------------------------+\n\ + | LINESTRING(1 1,1 2) |\n+----------------------------------------------------------------+\n\ + \nST_Envelope() handles its arguments as described in the introduction to\n\ + this section, with this exception:\n\no If the geometry has an SRID value for\ + \ a geographic spatial reference\n system (SRS), an ER_NOT_IMPLEMENTED_FOR_GEOGRAPHIC_SRS\n\ + \ (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n \ + \ .html#error_er_not_implemented_for_geographic_srs) error occurs.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-general-property-functions.html" + examples: [] + - name: ST_EQUALS + category_id: geometry_relation_functions + category_label: Geometry Relation Functions + signature: + display: ST_EQUALS(g1, g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + tags: [] + aliases: [] + summary: Returns 1 or 0 to indicate whether g1 is spatially equal to g2. + description: 'Returns 1 or 0 to indicate whether g1 is spatially equal to g2. + ST_Equals() handles its arguments as described in the introduction to this section, + except that it does not return NULL for empty geometry arguments. URL: https://dev.mysql.com/doc/refman/8.3/en/spatial-relation-functions-object-shapes.html' + examples: [] + - name: ST_EXTERIORRING + category_id: polygon_property_functions + category_label: Polygon Property Functions + signature: + display: ST_EXTERIORRING(poly) + args: + - name: poly + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the exterior ring of the Polygon value poly as a LineString. + description: 'Returns the exterior ring of the Polygon value poly as a LineString. + ST_ExteriorRing() handles its arguments as described in the introduction to + this section. URL: https://dev.mysql.com/doc/refman/8.3/en/gis-polygon-property-functions.html' + examples: [] + - name: ST_FRECHETDISTANCE + category_id: geometry_relation_functions + category_label: Geometry Relation Functions + signature: + display: ST_FRECHETDISTANCE(g1, g2 [, unit]) + args: + - name: g1 + optional: false + type: any + - name: g2 [ + optional: false + type: any + - name: unit] + optional: false + type: any + tags: [] + aliases: [] + summary: "Returns the discrete Fr\xE9chet distance between two geometries," + description: "Returns the discrete Fr\xE9chet distance between two geometries,\n\ + reflecting how similar the geometries are. The result is a\ndouble-precision\ + \ number measured in the length unit of the spatial\nreference system (SRS)\ + \ of the geometry arguments, or in the length unit\nof the unit argument if\ + \ that argument is given.\n\nThis function implements the discrete Fr\xE9chet\ + \ distance, which means it\nis restricted to distances between the points of\ + \ the geometries. For\nexample, given two LineString arguments, only the points\ + \ explicitly\nmentioned in the geometries are considered. Points on the line\ + \ segments\nbetween these points are not considered.\n\nST_FrechetDistance()\ + \ handles its geometry arguments as described in the\nintroduction to this section,\ + \ with these exceptions:\n\no The geometries may have a Cartesian or geographic\ + \ SRS, but only\n LineString values are supported. If the arguments are in\ + \ the same\n Cartesian or geographic SRS, but either is not a LineString, an\n\ + \ ER_NOT_IMPLEMENTED_FOR_CARTESIAN_SRS\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n\ + \ .html#error_er_not_implemented_for_cartesian_srs) or\n ER_NOT_IMPLEMENTED_FOR_GEOGRAPHIC_SRS\n\ + \ (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n \ + \ .html#error_er_not_implemented_for_geographic_srs) error occurs,\n depending\ + \ on the SRS type.\n\nST_FrechetDistance() handles its optional unit argument\ + \ as described in\nthe introduction to this section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-relation-functions-object-shapes.html" + examples: [] + - name: ST_GEOHASH + category_id: mbr_functions + category_label: MBR Functions + signature: + display: ST_GEOHASH(longitude, latitude, max_length) + args: + - name: longitude + optional: false + type: any + - name: latitude + optional: false + type: any + - name: max_length + optional: false + type: any + tags: [] + aliases: [] + summary: max_length) + description: "max_length)\n\nReturns a geohash string in the connection character\ + \ set and collation.\n\nFor the first syntax, the longitude must be a number\ + \ in the range\n[\u2212180, 180], and the latitude must be a number in the range\ + \ [\u221290,\n90]. For the second syntax, a POINT value is required, where the\ + \ X and\nY coordinates are in the valid ranges for longitude and latitude,\n\ + respectively.\n\nThe resulting string is no longer than max_length characters,\ + \ which has\nan upper limit of 100. The string might be shorter than max_length\n\ + characters because the algorithm that creates the geohash value\ncontinues until\ + \ it has created a string that is either an exact\nrepresentation of the location\ + \ or max_length characters, whichever\ncomes first.\n\nST_GeoHash() handles\ + \ its arguments as described in the introduction to\nthis section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-geohash-functions.html" + examples: [] + - name: ST_GEOMCOLLFROMTEXT + category_id: wkt_functions + category_label: WKT Functions + signature: + display: ST_GEOMCOLLFROMTEXT(wkt [, srid [, options]]) + args: + - name: wkt [ + optional: false + type: any + - name: srid [ + optional: false + type: any + - name: options]] + optional: false + type: any + tags: [] + aliases: [] + summary: ST_GeometryCollectionFromText(wkt [, srid [, options]]), + description: 'ST_GeometryCollectionFromText(wkt [, srid [, options]]), ST_GeomCollFromTxt(wkt + [, srid [, options]]) Constructs a GeometryCollection value using its WKT representation + and SRID. These functions handle their arguments as described in the introduction + to this section. URL: https://dev.mysql.com/doc/refman/8.3/en/gis-wkt-functions.html' + examples: [] + - name: ST_GEOMCOLLFROMWKB + category_id: wkb_functions + category_label: WKB Functions + signature: + display: ST_GEOMCOLLFROMWKB(wkb [, srid [, options]]) + args: + - name: wkb [ + optional: false + type: any + - name: srid [ + optional: false + type: any + - name: options]] + optional: false + type: any + tags: [] + aliases: [] + summary: ST_GeometryCollectionFromWKB(wkb [, srid [, options]]) + description: 'ST_GeometryCollectionFromWKB(wkb [, srid [, options]]) Constructs + a GeometryCollection value using its WKB representation and SRID. These functions + handle their arguments as described in the introduction to this section. URL: + https://dev.mysql.com/doc/refman/8.3/en/gis-wkb-functions.html' + examples: [] + - name: ST_GEOMETRYN + category_id: geometrycollection_property_functions + category_label: GeometryCollection Property Functions + signature: + display: ST_GEOMETRYN(gc, N) + args: + - name: gc + optional: false + type: any + - name: N + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the N-th geometry in the GeometryCollection value gc. + description: 'Returns the N-th geometry in the GeometryCollection value gc. Geometries + are numbered beginning with 1. ST_GeometryN() handles its arguments as described + in the introduction to this section. URL: https://dev.mysql.com/doc/refman/8.3/en/gis-geometrycollection-property-functions.html' + examples: [] + - name: ST_GEOMETRYTYPE + category_id: geometry_property_functions + category_label: Geometry Property Functions + signature: + display: ST_GEOMETRYTYPE(g) + args: + - name: g + optional: false + type: any + tags: [] + aliases: [] + summary: Returns a binary string indicating the name of the geometry type of + description: 'Returns a binary string indicating the name of the geometry type + of which the geometry instance g is a member. The name corresponds to one of + the instantiable Geometry subclasses. ST_GeometryType() handles its arguments + as described in the introduction to this section. URL: https://dev.mysql.com/doc/refman/8.3/en/gis-general-property-functions.html' + examples: [] + - name: ST_GEOMFROMGEOJSON + category_id: mbr_functions + category_label: MBR Functions + signature: + display: ST_GEOMFROMGEOJSON(str [, options [, srid]]) + args: + - name: str [ + optional: false + type: any + - name: options [ + optional: false + type: any + - name: srid]] + optional: false + type: any + tags: [] + aliases: [] + summary: Parses a string str representing a GeoJSON object and returns a + description: 'Parses a string str representing a GeoJSON object and returns a + geometry. If any argument is NULL, the return value is NULL. If any non-NULL + argument is invalid, an error occurs. URL: https://dev.mysql.com/doc/refman/8.3/en/spatial-geojson-functions.html' + examples: [] + - name: ST_GEOMFROMTEXT + category_id: wkt_functions + category_label: WKT Functions + signature: + display: ST_GEOMFROMTEXT(wkt [, srid [, options]]) + args: + - name: wkt [ + optional: false + type: any + - name: srid [ + optional: false + type: any + - name: options]] + optional: false + type: any + tags: [] + aliases: [] + summary: srid [, options]]) + description: 'srid [, options]]) Constructs a geometry value of any type using + its WKT representation and SRID. These functions handle their arguments as described + in the introduction to this section. URL: https://dev.mysql.com/doc/refman/8.3/en/gis-wkt-functions.html' + examples: [] + - name: ST_GEOMFROMWKB + category_id: wkb_functions + category_label: WKB Functions + signature: + display: ST_GEOMFROMWKB(wkb [, srid [, options]]) + args: + - name: wkb [ + optional: false + type: any + - name: srid [ + optional: false + type: any + - name: options]] + optional: false + type: any + tags: [] + aliases: [] + summary: srid [, options]]) + description: 'srid [, options]]) Constructs a geometry value of any type using + its WKB representation and SRID. These functions handle their arguments as described + in the introduction to this section. URL: https://dev.mysql.com/doc/refman/8.3/en/gis-wkb-functions.html' + examples: [] + - name: ST_HAUSDORFFDISTANCE + category_id: geometry_relation_functions + category_label: Geometry Relation Functions + signature: + display: ST_HAUSDORFFDISTANCE(g1, g2 [, unit]) + args: + - name: g1 + optional: false + type: any + - name: g2 [ + optional: false + type: any + - name: unit] + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the discrete Hausdorff distance between two geometries, + description: "Returns the discrete Hausdorff distance between two geometries,\n\ + reflecting how similar the geometries are. The result is a\ndouble-precision\ + \ number measured in the length unit of the spatial\nreference system (SRS)\ + \ of the geometry arguments, or in the length unit\nof the unit argument if\ + \ that argument is given.\n\nThis function implements the discrete Hausdorff\ + \ distance, which means\nit is restricted to distances between the points of\ + \ the geometries. For\nexample, given two LineString arguments, only the points\ + \ explicitly\nmentioned in the geometries are considered. Points on the line\ + \ segments\nbetween these points are not considered.\n\nST_HausdorffDistance()\ + \ handles its geometry arguments as described in\nthe introduction to this section,\ + \ with these exceptions:\n\no If the geometry arguments are in the same Cartesian\ + \ or geographic\n SRS, but are not in a supported combination, an\n ER_NOT_IMPLEMENTED_FOR_CARTESIAN_SRS\n\ + \ (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n \ + \ .html#error_er_not_implemented_for_cartesian_srs) or\n ER_NOT_IMPLEMENTED_FOR_GEOGRAPHIC_SRS\n\ + \ (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n \ + \ .html#error_er_not_implemented_for_geographic_srs) error occurs,\n depending\ + \ on the SRS type. These combinations are supported:\n\n o LineString and LineString\n\ + \n o Point and MultiPoint\n\n o LineString and MultiLineString\n\n o MultiPoint\ + \ and MultiPoint\n\n o MultiLineString and MultiLineString\n\nST_HausdorffDistance()\ + \ handles its optional unit argument as described\nin the introduction to this\ + \ section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-relation-functions-object-shapes.html" + examples: [] + - name: ST_INTERIORRINGN + category_id: polygon_property_functions + category_label: Polygon Property Functions + signature: + display: ST_INTERIORRINGN(poly, N) + args: + - name: poly + optional: false + type: any + - name: N + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the N-th interior ring for the Polygon value poly as a + description: 'Returns the N-th interior ring for the Polygon value poly as a LineString. + Rings are numbered beginning with 1. ST_InteriorRingN() handles its arguments + as described in the introduction to this section. URL: https://dev.mysql.com/doc/refman/8.3/en/gis-polygon-property-functions.html' + examples: [] + - name: ST_INTERSECTION + category_id: geometrycollection_property_functions + category_label: GeometryCollection Property Functions + signature: + display: ST_INTERSECTION(g1, g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + tags: [] + aliases: [] + summary: Returns a geometry that represents the point set intersection of the + description: 'Returns a geometry that represents the point set intersection of + the geometry values g1 and g2. The result is in the same SRS as the geometry + arguments. ST_Intersection() permits arguments in either a Cartesian or a geographic + SRS, and handles its arguments as described in the introduction to this section. + URL: https://dev.mysql.com/doc/refman/8.3/en/spatial-operator-functions.html' + examples: [] + - name: ST_INTERSECTS + category_id: geometry_relation_functions + category_label: Geometry Relation Functions + signature: + display: ST_INTERSECTS(g1, g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + tags: [] + aliases: [] + summary: Returns 1 or 0 to indicate whether g1 spatially intersects g2. + description: 'Returns 1 or 0 to indicate whether g1 spatially intersects g2. ST_Intersects() + handles its arguments as described in the introduction to this section. URL: + https://dev.mysql.com/doc/refman/8.3/en/spatial-relation-functions-object-shapes.html' + examples: [] + - name: ST_ISCLOSED + category_id: linestring_property_functions + category_label: LineString Property Functions + signature: + display: ST_ISCLOSED(ls) + args: + - name: ls + optional: false + type: any + tags: [] + aliases: [] + summary: For a LineString value ls, ST_IsClosed() returns 1 if ls is closed + description: "For a LineString value ls, ST_IsClosed() returns 1 if ls is closed\n\ + (that is, its ST_StartPoint() and ST_EndPoint() values are the same).\n\nFor\ + \ a MultiLineString value ls, ST_IsClosed() returns 1 if ls is closed\n(that\ + \ is, the ST_StartPoint() and ST_EndPoint() values are the same for\neach LineString\ + \ in ls).\n\nST_IsClosed() returns 0 if ls is not closed, and NULL if ls is\ + \ NULL.\n\nST_IsClosed() handles its arguments as described in the introduction\ + \ to\nthis section, with this exception:\n\no If the geometry has an SRID value\ + \ for a geographic spatial reference\n system (SRS), an ER_NOT_IMPLEMENTED_FOR_GEOGRAPHIC_SRS\n\ + \ (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n \ + \ .html#error_er_not_implemented_for_geographic_srs) error occurs.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-linestring-property-functions.html" + examples: [] + - name: ST_ISEMPTY + category_id: geometry_property_functions + category_label: Geometry Property Functions + signature: + display: ST_ISEMPTY(g) + args: + - name: g + optional: false + type: any + tags: [] + aliases: [] + summary: This function is a placeholder that returns 1 for an empty geometry + description: 'This function is a placeholder that returns 1 for an empty geometry + collection value or 0 otherwise. The only valid empty geometry is represented + in the form of an empty geometry collection value. MySQL does not support GIS + EMPTY values such as POINT EMPTY. ST_IsEmpty() handles its arguments as described + in the introduction to this section. URL: https://dev.mysql.com/doc/refman/8.3/en/gis-general-property-functions.html' + examples: [] + - name: ST_ISSIMPLE + category_id: geometry_property_functions + category_label: Geometry Property Functions + signature: + display: ST_ISSIMPLE(g) + args: + - name: g + optional: false + type: any + tags: [] + aliases: [] + summary: Returns 1 if the geometry value g is simple according to the ISO SQL/MM + description: "Returns 1 if the geometry value g is simple according to the ISO\ + \ SQL/MM\nPart 3: Spatial standard. ST_IsSimple() returns 0 if the argument\ + \ is\nnot simple.\n\nThe descriptions of the instantiable geometric classes\ + \ given under\nhttps://dev.mysql.com/doc/refman/8.3/en/opengis-geometry-model.html\n\ + include the specific conditions that cause class instances to be\nclassified\ + \ as not simple.\n\nST_IsSimple() handles its arguments as described in the\ + \ introduction to\nthis section, with this exception:\n\no If the geometry has\ + \ a geographic SRS with a longitude or latitude\n that is out of range, an\ + \ error occurs:\n\n o If a longitude value is not in the range (\u2212180,\ + \ 180], an\n ER_GEOMETRY_PARAM_LONGITUDE_OUT_OF_RANGE\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-referen\n\ + \ ce.html#error_er_geometry_param_longitude_out_of_range) error\n occurs.\n\ + \n o If a latitude value is not in the range [\u221290, 90], an\n ER_GEOMETRY_PARAM_LATITUDE_OUT_OF_RANGE\n\ + \ (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-referen\n \ + \ ce.html#error_er_geometry_param_latitude_out_of_range) error\n occurs.\n\ + \n Ranges shown are in degrees. The exact range limits deviate slightly\n \ + \ due to floating-point arithmetic.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-general-property-functions.html" + examples: [] + - name: ST_ISVALID + category_id: mbr_functions + category_label: MBR Functions + signature: + display: ST_ISVALID(g) + args: + - name: g + optional: false + type: any + tags: [] + aliases: [] + summary: Returns 1 if the argument is geometrically valid, 0 if the argument is + description: "Returns 1 if the argument is geometrically valid, 0 if the argument\ + \ is\nnot geometrically valid. Geometry validity is defined by the OGC\nspecification.\n\ + \nThe only valid empty geometry is represented in the form of an empty\ngeometry\ + \ collection value. ST_IsValid() returns 1 in this case. MySQL\ndoes not support\ + \ GIS EMPTY values such as POINT EMPTY.\n\nST_IsValid() handles its arguments\ + \ as described in the introduction to\nthis section, with this exception:\n\n\ + o If the geometry has a geographic SRS with a longitude or latitude\n that\ + \ is out of range, an error occurs:\n\n o If a longitude value is not in the\ + \ range (\u2212180, 180], an\n ER_GEOMETRY_PARAM_LONGITUDE_OUT_OF_RANGE\n\ + \ (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-referen\n \ + \ ce.html#error_er_geometry_param_longitude_out_of_range) error\n occurs.\n\ + \n o If a latitude value is not in the range [\u221290, 90], an\n ER_GEOMETRY_PARAM_LATITUDE_OUT_OF_RANGE\n\ + \ (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-referen\n \ + \ ce.html#error_er_geometry_param_latitude_out_of_range) error\n occurs.\n\ + \n Ranges shown are in degrees. If an SRS uses another unit, the range\n uses\ + \ the corresponding values in its unit. The exact range limits\n deviate slightly\ + \ due to floating-point arithmetic.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-convenience-functions.html" + examples: [] + - name: ST_LATFROMGEOHASH + category_id: mbr_functions + category_label: MBR Functions + signature: + display: ST_LATFROMGEOHASH(geohash_str) + args: + - name: geohash_str + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the latitude from a geohash string value, as a double-precision + description: "Returns the latitude from a geohash string value, as a double-precision\n\ + number in the range [\u221290, 90].\n\nThe ST_LatFromGeoHash() decoding function\ + \ reads no more than 433\ncharacters from the geohash_str argument. That represents\ + \ the upper\nlimit on information in the internal representation of coordinate\n\ + values. Characters past the 433rd are ignored, even if they are\notherwise illegal\ + \ and produce an error.\n\nST_LatFromGeoHash() handles its arguments as described\ + \ in the\nintroduction to this section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-geohash-functions.html" + examples: [] + - name: ST_LATITUDE + category_id: point_property_functions + category_label: Point Property Functions + signature: + display: ST_LATITUDE(p [, new_latitude_val]) + args: + - name: p [ + optional: false + type: any + - name: new_latitude_val] + optional: false + type: any + tags: [] + aliases: [] + summary: With a single argument representing a valid Point object p that has a + description: 'With a single argument representing a valid Point object p that + has a geographic spatial reference system (SRS), ST_Latitude() returns the latitude + value of p as a double-precision number. With the optional second argument representing + a valid latitude value, ST_Latitude() returns a Point object like the first + argument with its latitude equal to the second argument. ST_Latitude() handles + its arguments as described in the introduction to this section, with the addition + that if the Point object is valid but does not have a geographic SRS, an ER_SRS_NOT_GEOGRAPHIC + (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference.html #error_er_srs_not_geographic) + error occurs. URL: https://dev.mysql.com/doc/refman/8.3/en/gis-point-property-functions.html' + examples: [] + - name: ST_LENGTH + category_id: linestring_property_functions + category_label: LineString Property Functions + signature: + display: ST_LENGTH(ls [, unit]) + args: + - name: ls [ + optional: false + type: any + - name: unit] + optional: false + type: any + tags: [] + aliases: [] + summary: Returns a double-precision number indicating the length of the + description: "Returns a double-precision number indicating the length of the\n\ + LineString or MultiLineString value ls in its associated spatial\nreference\ + \ system. The length of a MultiLineString value is equal to the\nsum of the\ + \ lengths of its elements.\n\nST_Length() computes a result as follows:\n\n\ + o If the geometry is a valid LineString in a Cartesian SRS, the return\n value\ + \ is the Cartesian length of the geometry.\n\no If the geometry is a valid MultiLineString\ + \ in a Cartesian SRS, the\n return value is the sum of the Cartesian lengths\ + \ of its elements.\n\no If the geometry is a valid LineString in a geographic\ + \ SRS, the return\n value is the geodetic length of the geometry in that SRS,\ + \ in meters.\n\no If the geometry is a valid MultiLineString in a geographic\ + \ SRS, the\n return value is the sum of the geodetic lengths of its elements\ + \ in\n that SRS, in meters.\n\nST_Length() handles its arguments as described\ + \ in the introduction to\nthis section, with these exceptions:\n\no If the geometry\ + \ is not a LineString or MultiLineString, the return\n value is NULL.\n\no\ + \ If the geometry is geometrically invalid, either the result is an\n undefined\ + \ length (that is, it can be any number), or an error occurs.\n\no If the length\ + \ computation result is +inf, an ER_DATA_OUT_OF_RANGE\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n\ + \ .html#error_er_data_out_of_range) error occurs.\n\no If the geometry has\ + \ a geographic SRS with a longitude or latitude\n that is out of range, an\ + \ error occurs:\n\n o If a longitude value is not in the range (\u2212180,\ + \ 180], an\n ER_GEOMETRY_PARAM_LONGITUDE_OUT_OF_RANGE\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-referen\n\ + \ ce.html#error_er_geometry_param_longitude_out_of_range) error\n occurs.\n\ + \n o If a latitude value is not in the range [\u221290, 90], an\n ER_GEOMETRY_PARAM_LATITUDE_OUT_OF_RANGE\n\ + \ (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-referen\n \ + \ ce.html#error_er_geometry_param_latitude_out_of_range) error\n occurs.\n\ + \n Ranges shown are in degrees. The exact range limits deviate slightly\n \ + \ due to floating-point arithmetic.\n ..." + examples: [] + - name: ST_LINEFROMTEXT + category_id: wkt_functions + category_label: WKT Functions + signature: + display: ST_LINEFROMTEXT(wkt [, srid [, options]]) + args: + - name: wkt [ + optional: false + type: any + - name: srid [ + optional: false + type: any + - name: options]] + optional: false + type: any + tags: [] + aliases: [] + summary: srid [, options]]) + description: 'srid [, options]]) Constructs a LineString value using its WKT representation + and SRID. These functions handle their arguments as described in the introduction + to this section. URL: https://dev.mysql.com/doc/refman/8.3/en/gis-wkt-functions.html' + examples: [] + - name: ST_LINEFROMWKB + category_id: wkb_functions + category_label: WKB Functions + signature: + display: ST_LINEFROMWKB(wkb [, srid [, options]]) + args: + - name: wkb [ + optional: false + type: any + - name: srid [ + optional: false + type: any + - name: options]] + optional: false + type: any + tags: [] + aliases: [] + summary: srid [, options]]) + description: 'srid [, options]]) Constructs a LineString value using its WKB representation + and SRID. These functions handle their arguments as described in the introduction + to this section. URL: https://dev.mysql.com/doc/refman/8.3/en/gis-wkb-functions.html' + examples: [] + - name: ST_LINEINTERPOLATEPOINT + category_id: geometrycollection_property_functions + category_label: GeometryCollection Property Functions + signature: + display: ST_LINEINTERPOLATEPOINT(ls, fractional_distance) + args: + - name: ls + optional: false + type: any + - name: fractional_distance + optional: false + type: any + tags: [] + aliases: [] + summary: This function takes a LineString geometry and a fractional distance in + description: "This function takes a LineString geometry and a fractional distance\ + \ in\nthe range [0.0, 1.0] and returns the Point along the LineString at the\n\ + given fraction of the distance from its start point to its endpoint. It\ncan\ + \ be used to answer questions such as which Point lies halfway along\nthe road\ + \ described by the geometry argument.\n\nThe function is implemented for LineString\ + \ geometries in all spatial\nreference systems, both Cartesian and geographic.\n\ + \nIf the fractional_distance argument is 1.0, the result may not be\nexactly\ + \ the last point of the LineString argument but a point close to\nit due to\ + \ numerical inaccuracies in approximate-value computations.\n\nA related function,\ + \ ST_LineInterpolatePoints(), takes similar arguments\nbut returns a MultiPoint\ + \ consisting of Point values along the\nLineString at each fraction of the distance\ + \ from its start point to its\nendpoint. For examples of both functions, see\ + \ the\nST_LineInterpolatePoints() description.\n\nST_LineInterpolatePoint()\ + \ handles its arguments as described in the\nintroduction to this section, with\ + \ these exceptions:\n\no If the geometry argument is not a LineString, an\n\ + \ ER_UNEXPECTED_GEOMETRY_TYPE\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n\ + \ .html#error_er_unexpected_geometry_type) error occurs.\n\no If the fractional\ + \ distance argument is outside the range [0.0, 1.0],\n an ER_DATA_OUT_OF_RANGE\n\ + \ (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n \ + \ .html#error_er_data_out_of_range) error occurs.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-operator-functions.html" + examples: [] + - name: ST_LINEINTERPOLATEPOINTS + category_id: geometrycollection_property_functions + category_label: GeometryCollection Property Functions + signature: + display: ST_LINEINTERPOLATEPOINTS(ls, fractional_distance) + args: + - name: ls + optional: false + type: any + - name: fractional_distance + optional: false + type: any + tags: [] + aliases: [] + summary: This function takes a LineString geometry and a fractional distance in + description: "This function takes a LineString geometry and a fractional distance\ + \ in\nthe range (0.0, 1.0] and returns the MultiPoint consisting of the\nLineString\ + \ start point, plus Point values along the LineString at each\nfraction of the\ + \ distance from its start point to its endpoint. It can\nbe used to answer questions\ + \ such as which Point values lie every 10% of\nthe way along the road described\ + \ by the geometry argument.\n\nThe function is implemented for LineString geometries\ + \ in all spatial\nreference systems, both Cartesian and geographic.\n\nIf the\ + \ fractional_distance argument divides 1.0 with zero remainder the\nresult may\ + \ not contain the last point of the LineString argument but a\npoint close to\ + \ it due to numerical inaccuracies in approximate-value\ncomputations.\n\nA\ + \ related function, ST_LineInterpolatePoint(), takes similar arguments\nbut\ + \ returns the Point along the LineString at the given fraction of the\ndistance\ + \ from its start point to its endpoint.\n\nST_LineInterpolatePoints() handles\ + \ its arguments as described in the\nintroduction to this section, with these\ + \ exceptions:\n\no If the geometry argument is not a LineString, an\n ER_UNEXPECTED_GEOMETRY_TYPE\n\ + \ (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n \ + \ .html#error_er_unexpected_geometry_type) error occurs.\n\no If the fractional\ + \ distance argument is outside the range [0.0, 1.0],\n an ER_DATA_OUT_OF_RANGE\n\ + \ (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n \ + \ .html#error_er_data_out_of_range) error occurs.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-operator-functions.html" + examples: [] + - name: ST_LONGFROMGEOHASH + category_id: mbr_functions + category_label: MBR Functions + signature: + display: ST_LONGFROMGEOHASH(geohash_str) + args: + - name: geohash_str + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the longitude from a geohash string value, as a + description: "Returns the longitude from a geohash string value, as a\ndouble-precision\ + \ number in the range [\u2212180, 180].\n\nThe remarks in the description of\ + \ ST_LatFromGeoHash() regarding the\nmaximum number of characters processed\ + \ from the geohash_str argument\nalso apply to ST_LongFromGeoHash().\n\nST_LongFromGeoHash()\ + \ handles its arguments as described in the\nintroduction to this section.\n\ + \nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-geohash-functions.html" + examples: [] + - name: ST_LONGITUDE + category_id: point_property_functions + category_label: Point Property Functions + signature: + display: ST_LONGITUDE(p [, new_longitude_val]) + args: + - name: p [ + optional: false + type: any + - name: new_longitude_val] + optional: false + type: any + tags: [] + aliases: [] + summary: With a single argument representing a valid Point object p that has a + description: 'With a single argument representing a valid Point object p that + has a geographic spatial reference system (SRS), ST_Longitude() returns the + longitude value of p as a double-precision number. With the optional second + argument representing a valid longitude value, ST_Longitude() returns a Point + object like the first argument with its longitude equal to the second argument. + ST_Longitude() handles its arguments as described in the introduction to this + section, with the addition that if the Point object is valid but does not have + a geographic SRS, an ER_SRS_NOT_GEOGRAPHIC (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference.html + #error_er_srs_not_geographic) error occurs. URL: https://dev.mysql.com/doc/refman/8.3/en/gis-point-property-functions.html' + examples: [] + - name: ST_MAKEENVELOPE + category_id: mbr_functions + category_label: MBR Functions + signature: + display: ST_MAKEENVELOPE(pt1, pt2) + args: + - name: pt1 + optional: false + type: any + - name: pt2 + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the rectangle that forms the envelope around two points, as a + description: "Returns the rectangle that forms the envelope around two points,\ + \ as a\nPoint, LineString, or Polygon.\n\nCalculations are done using the Cartesian\ + \ coordinate system rather than\non a sphere, spheroid, or on earth.\n\nGiven\ + \ two points pt1 and pt2, ST_MakeEnvelope() creates the result\ngeometry on\ + \ an abstract plane like this:\n\no If pt1 and pt2 are equal, the result is\ + \ the point pt1.\n\no Otherwise, if (pt1, pt2) is a vertical or horizontal line\ + \ segment,\n the result is the line segment (pt1, pt2).\n\no Otherwise, the\ + \ result is a polygon using pt1 and pt2 as diagonal\n points.\n\nThe result\ + \ geometry has an SRID of 0.\n\nST_MakeEnvelope() handles its arguments as described\ + \ in the\nintroduction to this section, with these exceptions:\n\no If the arguments\ + \ are not Point values, an ER_WRONG_ARGUMENTS\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n\ + \ .html#error_er_wrong_arguments) error occurs.\n\no An ER_GIS_INVALID_DATA\n\ + \ (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n \ + \ .html#error_er_gis_invalid_data) error occurs for the additional\n condition\ + \ that any coordinate value of the two points is infinite or\n NaN.\n\no If\ + \ any geometry has an SRID value for a geographic spatial reference\n system\ + \ (SRS), an ER_NOT_IMPLEMENTED_FOR_GEOGRAPHIC_SRS\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n\ + \ .html#error_er_not_implemented_for_geographic_srs) error occurs.\n\nURL:\ + \ https://dev.mysql.com/doc/refman/8.3/en/spatial-convenience-functions.html" + examples: [] + - name: ST_MLINEFROMTEXT + category_id: wkt_functions + category_label: WKT Functions + signature: + display: ST_MLINEFROMTEXT(wkt [, srid [, options]]) + args: + - name: wkt [ + optional: false + type: any + - name: srid [ + optional: false + type: any + - name: options]] + optional: false + type: any + tags: [] + aliases: [] + summary: ST_MultiLineStringFromText(wkt [, srid [, options]]) + description: 'ST_MultiLineStringFromText(wkt [, srid [, options]]) Constructs + a MultiLineString value using its WKT representation and SRID. These functions + handle their arguments as described in the introduction to this section. URL: + https://dev.mysql.com/doc/refman/8.3/en/gis-wkt-functions.html' + examples: [] + - name: ST_MLINEFROMWKB + category_id: wkb_functions + category_label: WKB Functions + signature: + display: ST_MLINEFROMWKB(wkb [, srid [, options]]) + args: + - name: wkb [ + optional: false + type: any + - name: srid [ + optional: false + type: any + - name: options]] + optional: false + type: any + tags: [] + aliases: [] + summary: ST_MultiLineStringFromWKB(wkb [, srid [, options]]) + description: 'ST_MultiLineStringFromWKB(wkb [, srid [, options]]) Constructs a + MultiLineString value using its WKB representation and SRID. These functions + handle their arguments as described in the introduction to this section. URL: + https://dev.mysql.com/doc/refman/8.3/en/gis-wkb-functions.html' + examples: [] + - name: ST_MPOINTFROMTEXT + category_id: wkt_functions + category_label: WKT Functions + signature: + display: ST_MPOINTFROMTEXT(wkt [, srid [, options]]) + args: + - name: wkt [ + optional: false + type: any + - name: srid [ + optional: false + type: any + - name: options]] + optional: false + type: any + tags: [] + aliases: [] + summary: '[, srid [, options]])' + description: '[, srid [, options]]) Constructs a MultiPoint value using its WKT + representation and SRID. These functions handle their arguments as described + in the introduction to this section. URL: https://dev.mysql.com/doc/refman/8.3/en/gis-wkt-functions.html' + examples: [] + - name: ST_MPOINTFROMWKB + category_id: wkb_functions + category_label: WKB Functions + signature: + display: ST_MPOINTFROMWKB(wkb [, srid [, options]]) + args: + - name: wkb [ + optional: false + type: any + - name: srid [ + optional: false + type: any + - name: options]] + optional: false + type: any + tags: [] + aliases: [] + summary: srid [, options]]) + description: 'srid [, options]]) Constructs a MultiPoint value using its WKB representation + and SRID. These functions handle their arguments as described in the introduction + to this section. URL: https://dev.mysql.com/doc/refman/8.3/en/gis-wkb-functions.html' + examples: [] + - name: ST_MPOLYFROMTEXT + category_id: wkt_functions + category_label: WKT Functions + signature: + display: ST_MPOLYFROMTEXT(wkt [, srid [, options]]) + args: + - name: wkt [ + optional: false + type: any + - name: srid [ + optional: false + type: any + - name: options]] + optional: false + type: any + tags: [] + aliases: [] + summary: '[, srid [, options]])' + description: '[, srid [, options]]) Constructs a MultiPolygon value using its + WKT representation and SRID. These functions handle their arguments as described + in the introduction to this section. URL: https://dev.mysql.com/doc/refman/8.3/en/gis-wkt-functions.html' + examples: [] + - name: ST_MPOLYFROMWKB + category_id: wkb_functions + category_label: WKB Functions + signature: + display: ST_MPOLYFROMWKB(wkb [, srid [, options]]) + args: + - name: wkb [ + optional: false + type: any + - name: srid [ + optional: false + type: any + - name: options]] + optional: false + type: any + tags: [] + aliases: [] + summary: '[, srid [, options]])' + description: '[, srid [, options]]) Constructs a MultiPolygon value using its + WKB representation and SRID. These functions handle their arguments as described + in the introduction to this section. URL: https://dev.mysql.com/doc/refman/8.3/en/gis-wkb-functions.html' + examples: [] + - name: ST_NUMGEOMETRIES + category_id: geometrycollection_property_functions + category_label: GeometryCollection Property Functions + signature: + display: ST_NUMGEOMETRIES(gc) + args: + - name: gc + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the number of geometries in the GeometryCollection value gc. + description: 'Returns the number of geometries in the GeometryCollection value + gc. ST_NumGeometries() handles its arguments as described in the introduction + to this section. URL: https://dev.mysql.com/doc/refman/8.3/en/gis-geometrycollection-property-functions.html' + examples: [] + - name: ST_NUMINTERIORRINGS + category_id: polygon_property_functions + category_label: Polygon Property Functions + signature: + display: ST_NUMINTERIORRINGS(poly) + args: + - name: poly + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the number of interior rings in the Polygon value poly. + description: 'Returns the number of interior rings in the Polygon value poly. + ST_NumInteriorRing() and ST_NuminteriorRings() handle their arguments as described + in the introduction to this section. URL: https://dev.mysql.com/doc/refman/8.3/en/gis-polygon-property-functions.html' + examples: [] + - name: ST_NUMPOINTS + category_id: linestring_property_functions + category_label: LineString Property Functions + signature: + display: ST_NUMPOINTS(ls) + args: + - name: ls + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the number of Point objects in the LineString value ls. + description: 'Returns the number of Point objects in the LineString value ls. + ST_NumPoints() handles its arguments as described in the introduction to this + section. URL: https://dev.mysql.com/doc/refman/8.3/en/gis-linestring-property-functions.html' + examples: [] + - name: ST_OVERLAPS + category_id: geometry_relation_functions + category_label: Geometry Relation Functions + signature: + display: ST_OVERLAPS(g1, g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + tags: [] + aliases: [] + summary: Two geometries spatially overlap if they intersect and their + description: 'Two geometries spatially overlap if they intersect and their intersection + results in a geometry of the same dimension but not equal to either of the given + geometries. This function returns 1 or 0 to indicate whether g1 spatially overlaps + g2. ST_Overlaps() handles its arguments as described in the introduction to + this section except that the return value is NULL for the additional condition + that the dimensions of the two geometries are not equal. URL: https://dev.mysql.com/doc/refman/8.3/en/spatial-relation-functions-object-shapes.html' + examples: [] + - name: ST_POINTATDISTANCE + category_id: geometrycollection_property_functions + category_label: GeometryCollection Property Functions + signature: + display: ST_POINTATDISTANCE(ls, distance) + args: + - name: ls + optional: false + type: any + - name: distance + optional: false + type: any + tags: [] + aliases: [] + summary: This function takes a LineString geometry and a distance in the range + description: "This function takes a LineString geometry and a distance in the\ + \ range\n[0.0, ST_Length(ls)] measured in the unit of the spatial reference\n\ + system (SRS) of the LineString, and returns the Point along the\nLineString\ + \ at that distance from its start point. It can be used to\nanswer questions\ + \ such as which Point value is 400 meters from the start\nof the road described\ + \ by the geometry argument.\n\nThe function is implemented for LineString geometries\ + \ in all spatial\nreference systems, both Cartesian and geographic.\n\nST_PointAtDistance()\ + \ handles its arguments as described in the\nintroduction to this section, with\ + \ these exceptions:\n\no If the geometry argument is not a LineString, an\n\ + \ ER_UNEXPECTED_GEOMETRY_TYPE\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n\ + \ .html#error_er_unexpected_geometry_type) error occurs.\n\no If the fractional\ + \ distance argument is outside the range [0.0,\n ST_Length(ls)], an ER_DATA_OUT_OF_RANGE\n\ + \ (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n \ + \ .html#error_er_data_out_of_range) error occurs.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-operator-functions.html" + examples: [] + - name: ST_POINTFROMGEOHASH + category_id: mbr_functions + category_label: MBR Functions + signature: + display: ST_POINTFROMGEOHASH(geohash_str, srid) + args: + - name: geohash_str + optional: false + type: any + - name: srid + optional: false + type: any + tags: [] + aliases: [] + summary: Returns a POINT value containing the decoded geohash value, given a + description: "Returns a POINT value containing the decoded geohash value, given\ + \ a\ngeohash string value.\n\nThe X and Y coordinates of the point are the longitude\ + \ in the range\n[\u2212180, 180] and the latitude in the range [\u221290, 90],\ + \ respectively.\n\nThe srid argument is an 32-bit unsigned integer.\n\nThe remarks\ + \ in the description of ST_LatFromGeoHash() regarding the\nmaximum number of\ + \ characters processed from the geohash_str argument\nalso apply to ST_PointFromGeoHash().\n\ + \nST_PointFromGeoHash() handles its arguments as described in the\nintroduction\ + \ to this section.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-geohash-functions.html" + examples: [] + - name: ST_POINTFROMTEXT + category_id: wkt_functions + category_label: WKT Functions + signature: + display: ST_POINTFROMTEXT(wkt [, srid [, options]]) + args: + - name: wkt [ + optional: false + type: any + - name: srid [ + optional: false + type: any + - name: options]] + optional: false + type: any + tags: [] + aliases: [] + summary: Constructs a Point value using its WKT representation and SRID. + description: 'Constructs a Point value using its WKT representation and SRID. + ST_PointFromText() handles its arguments as described in the introduction to + this section. URL: https://dev.mysql.com/doc/refman/8.3/en/gis-wkt-functions.html' + examples: [] + - name: ST_POINTFROMWKB + category_id: wkb_functions + category_label: WKB Functions + signature: + display: ST_POINTFROMWKB(wkb [, srid [, options]]) + args: + - name: wkb [ + optional: false + type: any + - name: srid [ + optional: false + type: any + - name: options]] + optional: false + type: any + tags: [] + aliases: [] + summary: Constructs a Point value using its WKB representation and SRID. + description: 'Constructs a Point value using its WKB representation and SRID. + ST_PointFromWKB() handles its arguments as described in the introduction to + this section. URL: https://dev.mysql.com/doc/refman/8.3/en/gis-wkb-functions.html' + examples: [] + - name: ST_POINTN + category_id: linestring_property_functions + category_label: LineString Property Functions + signature: + display: ST_POINTN(ls, N) + args: + - name: ls + optional: false + type: any + - name: N + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the N-th Point in the Linestring value ls. + description: 'Returns the N-th Point in the Linestring value ls. Points are numbered + beginning with 1. ST_PointN() handles its arguments as described in the introduction + to this section. URL: https://dev.mysql.com/doc/refman/8.3/en/gis-linestring-property-functions.html' + examples: [] + - name: ST_POLYFROMTEXT + category_id: wkt_functions + category_label: WKT Functions + signature: + display: ST_POLYFROMTEXT(wkt [, srid [, options]]) + args: + - name: wkt [ + optional: false + type: any + - name: srid [ + optional: false + type: any + - name: options]] + optional: false + type: any + tags: [] + aliases: [] + summary: srid [, options]]) + description: 'srid [, options]]) Constructs a Polygon value using its WKT representation + and SRID. These functions handle their arguments as described in the introduction + to this section. URL: https://dev.mysql.com/doc/refman/8.3/en/gis-wkt-functions.html' + examples: [] + - name: ST_POLYFROMWKB + category_id: wkb_functions + category_label: WKB Functions + signature: + display: ST_POLYFROMWKB(wkb [, srid [, options]]) + args: + - name: wkb [ + optional: false + type: any + - name: srid [ + optional: false + type: any + - name: options]] + optional: false + type: any + tags: [] + aliases: [] + summary: '[, options]])' + description: '[, options]]) Constructs a Polygon value using its WKB representation + and SRID. These functions handle their arguments as described in the introduction + to this section. URL: https://dev.mysql.com/doc/refman/8.3/en/gis-wkb-functions.html' + examples: [] + - name: ST_SIMPLIFY + category_id: mbr_functions + category_label: MBR Functions + signature: + display: ST_SIMPLIFY(g, max_distance) + args: + - name: g + optional: false + type: any + - name: max_distance + optional: false + type: any + tags: [] + aliases: [] + summary: Simplifies a geometry using the Douglas-Peucker algorithm and returns + a + description: "Simplifies a geometry using the Douglas-Peucker algorithm and returns\ + \ a\nsimplified value of the same type.\n\nThe geometry may be any geometry\ + \ type, although the Douglas-Peucker\nalgorithm may not actually process every\ + \ type. A geometry collection is\nprocessed by giving its components one by\ + \ one to the simplification\nalgorithm, and the returned geometries are put\ + \ into a geometry\ncollection as result.\n\nThe max_distance argument is the\ + \ distance (in units of the input\ncoordinates) of a vertex to other segments\ + \ to be removed. Vertices\nwithin this distance of the simplified linestring\ + \ are removed.\n\nAccording to Boost.Geometry, geometries might become invalid\ + \ as a\nresult of the simplification process, and the process might create\n\ + self-intersections. To check the validity of the result, pass it to\nST_IsValid().\n\ + \nST_Simplify() handles its arguments as described in the introduction to\n\ + this section, with this exception:\n\no If the max_distance argument is not\ + \ positive, or is NaN, an\n ER_WRONG_ARGUMENTS\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n\ + \ .html#error_er_wrong_arguments) error occurs.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-convenience-functions.html" + examples: [] + - name: ST_SRID + category_id: geometry_property_functions + category_label: Geometry Property Functions + signature: + display: ST_SRID(g [, srid]) + args: + - name: g [ + optional: false + type: any + - name: srid] + optional: false + type: any + tags: [] + aliases: [] + summary: With a single argument representing a valid geometry object g, + description: "With a single argument representing a valid geometry object g,\n\ + ST_SRID() returns an integer indicating the ID of the spatial reference\nsystem\ + \ (SRS) associated with g.\n\nWith the optional second argument representing\ + \ a valid SRID value,\nST_SRID() returns an object with the same type as its\ + \ first argument\nwith an SRID value equal to the second argument. This only\ + \ sets the\nSRID value of the object; it does not perform any transformation\ + \ of\ncoordinate values.\n\nST_SRID() handles its arguments as described in\ + \ the introduction to\nthis section, with this exception:\n\no For the single-argument\ + \ syntax, ST_SRID() returns the geometry SRID\n even if it refers to an undefined\ + \ SRS. An ER_SRS_NOT_FOUND\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n\ + \ .html#error_er_srs_not_found) error does not occur.\n\nST_SRID(g, target_srid)\ + \ and ST_Transform(g, target_srid) differ as\nfollows:\n\no ST_SRID() changes\ + \ the geometry SRID value without transforming its\n coordinates.\n\no ST_Transform()\ + \ transforms the geometry coordinates in addition to\n changing its SRID value.\n\ + \nURL: https://dev.mysql.com/doc/refman/8.3/en/gis-general-property-functions.html" + examples: [] + - name: ST_STARTPOINT + category_id: linestring_property_functions + category_label: LineString Property Functions + signature: + display: ST_STARTPOINT(ls) + args: + - name: ls + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the Point that is the start point of the LineString value ls. + description: 'Returns the Point that is the start point of the LineString value + ls. ST_StartPoint() handles its arguments as described in the introduction to + this section. URL: https://dev.mysql.com/doc/refman/8.3/en/gis-linestring-property-functions.html' + examples: [] + - name: ST_SWAPXY + category_id: wkb_functions + category_label: WKB Functions + signature: + display: ST_SWAPXY(g) + args: + - name: g + optional: false + type: any + tags: [] + aliases: [] + summary: Accepts an argument in internal geometry format, swaps the X and Y + description: 'Accepts an argument in internal geometry format, swaps the X and + Y values of each coordinate pair within the geometry, and returns the result. + ST_SwapXY() handles its arguments as described in the introduction to this section. + URL: https://dev.mysql.com/doc/refman/8.3/en/gis-format-conversion-functions.html' + examples: [] + - name: ST_SYMDIFFERENCE + category_id: geometrycollection_property_functions + category_label: GeometryCollection Property Functions + signature: + display: ST_SYMDIFFERENCE(g1, g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + tags: [] + aliases: [] + summary: Returns a geometry that represents the point set symmetric difference + description: 'Returns a geometry that represents the point set symmetric difference + of the geometry values g1 and g2, which is defined as: g1 symdifference g2 := + (g1 union g2) difference (g1 intersection g2) Or, in function call notation: + ST_SymDifference(g1, g2) = ST_Difference(ST_Union(g1, g2), ST_Intersection(g1, + g2)) The result is in the same SRS as the geometry arguments. ST_SymDifference() + permits arguments in either a Cartesian or a geographic SRS, and handles its + arguments as described in the introduction to this section. URL: https://dev.mysql.com/doc/refman/8.3/en/spatial-operator-functions.html' + examples: [] + - name: ST_TOUCHES + category_id: geometry_relation_functions + category_label: Geometry Relation Functions + signature: + display: ST_TOUCHES(g1, g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + tags: [] + aliases: [] + summary: Two geometries spatially touch if their interiors do not intersect, but + description: 'Two geometries spatially touch if their interiors do not intersect, + but the boundary of one of the geometries intersects either the boundary or + the interior of the other. This function returns 1 or 0 to indicate whether + g1 spatially touches g2. ST_Touches() handles its arguments as described in + the introduction to this section except that the return value is NULL for the + additional condition that both geometries are of dimension 0 (Point or MultiPoint). + URL: https://dev.mysql.com/doc/refman/8.3/en/spatial-relation-functions-object-shapes.html' + examples: [] + - name: ST_TRANSFORM + category_id: geometrycollection_property_functions + category_label: GeometryCollection Property Functions + signature: + display: ST_TRANSFORM(g, target_srid) + args: + - name: g + optional: false + type: any + - name: target_srid + optional: false + type: any + tags: [] + aliases: [] + summary: Transforms a geometry from one spatial reference system (SRS) to + description: "Transforms a geometry from one spatial reference system (SRS) to\n\ + another. The return value is a geometry of the same type as the input\ngeometry\ + \ with all coordinates transformed to the target SRID,\ntarget_srid. MySQL supports\ + \ all SRSs defined by EPSG except for those\nlisted here:\n\no EPSG 1042 Krovak\ + \ Modified\n\no EPSG 1043 Krovak Modified (North Orientated)\n\no EPSG 9816\ + \ Tunisia Mining Grid\n\no EPSG 9826 Lambert Conic Conformal (West Orientated)\n\ + \nST_Transform() handles its arguments as described in the introduction\nto\ + \ this section, with these exceptions:\n\no Geometry arguments that have an\ + \ SRID value for a geographic SRS do\n not produce an error.\n\no If the geometry\ + \ or target SRID argument has an SRID value that refers\n to an undefined spatial\ + \ reference system (SRS), an ER_SRS_NOT_FOUND\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n\ + \ .html#error_er_srs_not_found) error occurs.\n\no If the geometry is in an\ + \ SRS that ST_Transform() cannot transform\n from, an ER_TRANSFORM_SOURCE_SRS_NOT_SUPPORTED\n\ + \ (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n \ + \ .html#error_er_transform_source_srs_not_supported) error occurs.\n\no If the\ + \ target SRID is in an SRS that ST_Transform() cannot transform\n to, an ER_TRANSFORM_TARGET_SRS_NOT_SUPPORTED\n\ + \ (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n \ + \ .html#error_er_transform_target_srs_not_supported) error occurs.\n\no If the\ + \ geometry is in an SRS that is not WGS 84 and has no TOWGS84\n clause, an\ + \ ER_TRANSFORM_SOURCE_SRS_MISSING_TOWGS84\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n\ + \ .html#error_er_transform_source_srs_missing_towgs84) error occurs.\n\no If\ + \ the target SRID is in an SRS that is not WGS 84 and has no TOWGS84\n clause,\ + \ an ER_TRANSFORM_TARGET_SRS_MISSING_TOWGS84\n (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference\n\ + \ .html#error_er_transform_target_srs_missing_towgs84) error occurs.\n\nST_SRID(g,\ + \ target_srid) and ST_Transform(g, target_srid) differ as\nfollows:\n\no ST_SRID()\ + \ changes the geometry SRID value without transforming its\n coordinates.\n\ + \ ..." + examples: [] + - name: ST_UNION + category_id: geometrycollection_property_functions + category_label: GeometryCollection Property Functions + signature: + display: ST_UNION(g1, g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + tags: [] + aliases: [] + summary: Returns a geometry that represents the point set union of the geometry + description: 'Returns a geometry that represents the point set union of the geometry + values g1 and g2. The result is in the same SRS as the geometry arguments. ST_Union() + permits arguments in either a Cartesian or a geographic SRS, and handles its + arguments as described in the introduction to this section. URL: https://dev.mysql.com/doc/refman/8.3/en/spatial-operator-functions.html' + examples: [] + - name: ST_VALIDATE + category_id: mbr_functions + category_label: MBR Functions + signature: + display: ST_VALIDATE(g) + args: + - name: g + optional: false + type: any + tags: [] + aliases: [] + summary: Validates a geometry according to the OGC specification. + description: "Validates a geometry according to the OGC specification. A geometry\ + \ can\nbe syntactically well-formed (WKB value plus SRID) but geometrically\n\ + invalid. For example, this polygon is geometrically invalid: POLYGON((0\n0,\ + \ 0 0, 0 0, 0 0, 0 0))\n\nST_Validate() returns the geometry if it is syntactically\ + \ well-formed\nand is geometrically valid, NULL if the argument is not syntactically\n\ + well-formed or is not geometrically valid or is NULL.\n\nST_Validate() can be\ + \ used to filter out invalid geometry data, although\nat a cost. For applications\ + \ that require more precise results not\ntainted by invalid data, this penalty\ + \ may be worthwhile.\n\nIf the geometry argument is valid, it is returned as\ + \ is, except that if\nan input Polygon or MultiPolygon has clockwise rings,\ + \ those rings are\nreversed before checking for validity. If the geometry is\ + \ valid, the\nvalue with the reversed rings is returned.\n\nThe only valid empty\ + \ geometry is represented in the form of an empty\ngeometry collection value.\ + \ ST_Validate() returns it directly without\nfurther checks in this case.\n\n\ + ST_Validate() handles its arguments as described in the introduction to\nthis\ + \ section, with the exceptions listed here:\n\no If the geometry has a geographic\ + \ SRS with a longitude or latitude\n that is out of range, an error occurs:\n\ + \n o If a longitude value is not in the range (\u2212180, 180], an\n ER_GEOMETRY_PARAM_LONGITUDE_OUT_OF_RANGE\n\ + \ (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-referen\n \ + \ ce.html#error_er_geometry_param_longitude_out_of_range) error\n occurs.\n\ + \n o If a latitude value is not in the range [\u221290, 90], an\n ER_GEOMETRY_PARAM_LATITUDE_OUT_OF_RANGE\n\ + \ (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-referen\n \ + \ ce.html#error_er_geometry_param_latitude_out_of_range) error\n occurs.\n\ + \n Ranges shown are in degrees. The exact range limits deviate slightly\n \ + \ due to floating-point arithmetic.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/spatial-convenience-functions.html" + examples: [] + - name: ST_WITHIN + category_id: geometry_relation_functions + category_label: Geometry Relation Functions + signature: + display: ST_WITHIN(g1, g2) + args: + - name: g1 + optional: false + type: any + - name: g2 + optional: false + type: any + tags: [] + aliases: [] + summary: Returns 1 or 0 to indicate whether g1 is spatially within g2. + description: 'Returns 1 or 0 to indicate whether g1 is spatially within g2. This + tests the opposite relationship as ST_Contains(). ST_Within() handles its arguments + as described in the introduction to this section. URL: https://dev.mysql.com/doc/refman/8.3/en/spatial-relation-functions-object-shapes.html' + examples: [] + - name: ST_X + category_id: point_property_functions + category_label: Point Property Functions + signature: + display: ST_X(p [, new_x_val]) + args: + - name: p [ + optional: false + type: any + - name: new_x_val] + optional: false + type: any + tags: [] + aliases: [] + summary: With a single argument representing a valid Point object p, ST_X() + description: 'With a single argument representing a valid Point object p, ST_X() + returns the X-coordinate value of p as a double-precision number. The X coordinate + is considered to refer to the axis that appears first in the Point spatial reference + system (SRS) definition. With the optional second argument, ST_X() returns a + Point object like the first argument with its X coordinate equal to the second + argument. If the Point object has a geographic SRS, the second argument must + be in the proper range for longitude or latitude values. ST_X() handles its + arguments as described in the introduction to this section. URL: https://dev.mysql.com/doc/refman/8.3/en/gis-point-property-functions.html' + examples: [] + - name: ST_Y + category_id: point_property_functions + category_label: Point Property Functions + signature: + display: ST_Y(p [, new_y_val]) + args: + - name: p [ + optional: false + type: any + - name: new_y_val] + optional: false + type: any + tags: [] + aliases: [] + summary: With a single argument representing a valid Point object p, ST_Y() + description: 'With a single argument representing a valid Point object p, ST_Y() + returns the Y-coordinate value of p as a double-precision number.The Y coordinate + is considered to refer to the axis that appears second in the Point spatial + reference system (SRS) definition. With the optional second argument, ST_Y() + returns a Point object like the first argument with its Y coordinate equal to + the second argument. If the Point object has a geographic SRS, the second argument + must be in the proper range for longitude or latitude values. ST_Y() handles + its arguments as described in the introduction to this section. URL: https://dev.mysql.com/doc/refman/8.3/en/gis-point-property-functions.html' + examples: [] + - name: SUBDATE + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: SUBDATE(date,INTERVAL expr unit) + args: + - name: date + optional: false + type: any + - name: INTERVAL expr unit + optional: false + type: any + tags: [] + aliases: [] + summary: When invoked with the INTERVAL form of the second argument, SUBDATE() + description: "When invoked with the INTERVAL form of the second argument, SUBDATE()\n\ + is a synonym for DATE_SUB(). For information on the INTERVAL unit\nargument,\ + \ see the discussion for DATE_ADD().\n\nmysql> SELECT DATE_SUB('2008-01-02',\ + \ INTERVAL 31 DAY);\n -> '2007-12-02'\nmysql> SELECT SUBDATE('2008-01-02',\ + \ INTERVAL 31 DAY);\n -> '2007-12-02'\n\nThe second form enables the\ + \ use of an integer value for days. In such\ncases, it is interpreted as the\ + \ number of days to be subtracted from\nthe date or datetime expression expr.\n\ + \nmysql> SELECT SUBDATE('2008-01-02 12:00:00', 31);\n -> '2007-12-02\ + \ 12:00:00'\n\nThis function returns NULL if any of its arguments are NULL.\n\ + \nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html" + examples: [] + - name: SUBSTR + category_id: string_functions + category_label: String Functions + signature: + display: SUBSTR(str,pos) + args: + - name: str + optional: false + type: any + - name: pos + optional: false + type: any + tags: [] + aliases: [] + summary: FROM pos FOR len) + description: 'FROM pos FOR len) SUBSTR() is a synonym for SUBSTRING(). URL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html' + examples: [] + - name: SUBSTRING + category_id: string_functions + category_label: String Functions + signature: + display: SUBSTRING(str,pos) + args: + - name: str + optional: false + type: any + - name: pos + optional: false + type: any + tags: [] + aliases: [] + summary: SUBSTRING(str FROM pos FOR len) + description: 'SUBSTRING(str FROM pos FOR len) The forms without a len argument + return a substring from string str starting at position pos. The forms with + a len argument return a substring len characters long from string str, starting + at position pos. The forms that use FROM are standard SQL syntax. It is also + possible to use a negative value for pos. In this case, the beginning of the + substring is pos characters from the end of the string, rather than the beginning. + A negative value may be used for pos in any of the forms of this function. A + value of 0 for pos returns an empty string. For all forms of SUBSTRING(), the + position of the first character in the string from which the substring is to + be extracted is reckoned as 1. URL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html' + examples: [] + - name: SUBSTRING_INDEX + category_id: string_functions + category_label: String Functions + signature: + display: SUBSTRING_INDEX(str,delim,count) + args: + - name: str + optional: false + type: any + - name: delim + optional: false + type: any + - name: count + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the substring from string str before count occurrences of the + description: 'Returns the substring from string str before count occurrences of + the delimiter delim. If count is positive, everything to the left of the final + delimiter (counting from the left) is returned. If count is negative, everything + to the right of the final delimiter (counting from the right) is returned. SUBSTRING_INDEX() + performs a case-sensitive match when searching for delim. URL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html' + examples: [] + - name: SUBTIME + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: SUBTIME(expr1,expr2) + args: + - name: expr1 + optional: false + type: any + - name: expr2 + optional: false + type: any + tags: [] + aliases: [] + summary: "SUBTIME() returns expr1 \u2212 expr2 expressed as a value in the same" + description: "SUBTIME() returns expr1 \u2212 expr2 expressed as a value in the\ + \ same\nformat as expr1. expr1 is a time or datetime expression, and expr2 is\ + \ a\ntime expression.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html" + examples: [] + - name: SUM + category_id: aggregate_functions_and_modifiers + category_label: Aggregate Functions and Modifiers + signature: + display: SUM([DISTINCT] expr) + args: + - name: '[DISTINCT] expr' + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the sum of expr. + description: 'Returns the sum of expr. If the return set has no rows, SUM() returns + NULL. The DISTINCT keyword can be used to sum only the distinct values of expr. + If there are no matching rows, or if expr is NULL, SUM() returns NULL. This + function executes as a window function if over_clause is present. over_clause + is as described in https://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html; + it cannot be used with DISTINCT. URL: https://dev.mysql.com/doc/refman/8.3/en/aggregate-functions.html' + examples: [] + - name: SYSDATE + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: SYSDATE([fsp]) + args: + - name: '[fsp]' + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the current date and time as a value in 'YYYY-MM-DD hh:mm:ss' + description: 'Returns the current date and time as a value in ''YYYY-MM-DD hh:mm:ss'' + or YYYYMMDDhhmmss format, depending on whether the function is used in string + or numeric context. If the fsp argument is given to specify a fractional seconds + precision from 0 to 6, the return value includes a fractional seconds part of + that many digits. SYSDATE() returns the time at which it executes. This differs + from the behavior for NOW(), which returns a constant time that indicates the + time at which the statement began to execute. (Within a stored function or trigger, + NOW() returns the time at which the function or triggering statement began to + execute.) mysql> SELECT NOW(), SLEEP(2), NOW(); +---------------------+----------+---------------------+ + | NOW() | SLEEP(2) | NOW() | +---------------------+----------+---------------------+ + | 2006-04-12 13:47:36 | 0 | 2006-04-12 13:47:36 | +---------------------+----------+---------------------+ + mysql> SELECT SYSDATE(), SLEEP(2), SYSDATE(); +---------------------+----------+---------------------+ + | SYSDATE() | SLEEP(2) | SYSDATE() | +---------------------+----------+---------------------+ + | 2006-04-12 13:47:44 | 0 | 2006-04-12 13:47:46 | +---------------------+----------+---------------------+ + In addition, the SET TIMESTAMP statement affects the value returned by NOW() + but not by SYSDATE(). This means that timestamp settings in the binary log have + no effect on invocations of SYSDATE(). Because SYSDATE() can return different + values even within the same statement, and is not affected by SET TIMESTAMP, + it is nondeterministic and therefore unsafe for replication if statement-based + binary logging is used. If that is a problem, you can use row-based logging. + Alternatively, you can use the --sysdate-is-now option to cause SYSDATE() to + be an alias for NOW(). This works if the option is used on both the replication + source server and the replica. The nondeterministic nature of SYSDATE() also + means that indexes cannot be used for evaluating expressions that refer to it. + URL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: SYSTEM_USER + category_id: information_functions + category_label: Information Functions + signature: + display: SYSTEM_USER + args: [] + tags: [] + aliases: [] + summary: SYSTEM_USER() is a synonym for USER(). + description: 'SYSTEM_USER() is a synonym for USER(). *Note*: The SYSTEM_USER() + function is distinct from the SYSTEM_USER privilege. The former returns the + current MySQL account name. The latter distinguishes the system user and regular + user account categories (see https://dev.mysql.com/doc/refman/8.3/en/account-categories.html). + URL: https://dev.mysql.com/doc/refman/8.3/en/information-functions.html' + examples: [] + - name: TAN + category_id: numeric_functions + category_label: Numeric Functions + signature: + display: TAN(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the tangent of X, where X is given in radians. + description: 'Returns the tangent of X, where X is given in radians. Returns NULL + if X is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html' + examples: [] + - name: TEXT + category_id: data_types + category_label: Data Types + signature: + display: TEXT(M) + args: + - name: M + optional: false + type: any + tags: [] + aliases: [] + summary: "A TEXT column with a maximum length of 65,535 (216 \u2212 1) characters." + description: "A TEXT column with a maximum length of 65,535 (216 \u2212 1) characters.\n\ + The effective maximum length is less if the value contains multibyte\ncharacters.\ + \ Each TEXT value is stored using a 2-byte length prefix that\nindicates the\ + \ number of bytes in the value.\n\nAn optional length M can be given for this\ + \ type. If this is done, MySQL\ncreates the column as the smallest TEXT type\ + \ large enough to hold\nvalues M characters long.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/string-type-syntax.html" + examples: [] + - name: TIME + category_id: data_types + category_label: Data Types + signature: + display: TIME(fsp) + args: + - name: fsp + optional: false + type: any + tags: [] + aliases: [] + summary: A time. + description: 'A time. The range is ''-838:59:59.000000'' to ''838:59:59.000000''. + MySQL displays TIME values in ''hh:mm:ss[.fraction]'' format, but permits assignment + of values to TIME columns using either strings or numbers. An optional fsp value + in the range from 0 to 6 may be given to specify fractional seconds precision. + A value of 0 signifies that there is no fractional part. If omitted, the default + precision is 0. URL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-type-syntax.html' + examples: [] + - name: TIMEDIFF + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: TIMEDIFF(expr1,expr2) + args: + - name: expr1 + optional: false + type: any + - name: expr2 + optional: false + type: any + tags: [] + aliases: [] + summary: "TIMEDIFF() returns expr1 \u2212 expr2 expressed as a time value." + description: "TIMEDIFF() returns expr1 \u2212 expr2 expressed as a time value.\ + \ expr1 and\nexpr2 are strings which are converted to TIME or DATETIME expressions;\n\ + these must be of the same type following conversion. Returns NULL if\nexpr1\ + \ or expr2 is NULL.\n\nThe result returned by TIMEDIFF() is limited to the range\ + \ allowed for\nTIME values. Alternatively, you can use either of the functions\n\ + TIMESTAMPDIFF() and UNIX_TIMESTAMP(), both of which return integers.\n\nURL:\ + \ https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html" + examples: [] + - name: TIMESTAMP + category_id: data_types + category_label: Data Types + signature: + display: TIMESTAMP(fsp) + args: + - name: fsp + optional: false + type: any + tags: [] + aliases: [] + summary: A timestamp. + description: 'A timestamp. The range is ''1970-01-01 00:00:01.000000'' UTC to + ''2038-01-19 03:14:07.499999'' UTC. TIMESTAMP values are stored as the number + of seconds since the epoch (''1970-01-01 00:00:00'' UTC). A TIMESTAMP cannot + represent the value ''1970-01-01 00:00:00'' because that is equivalent to 0 + seconds from the epoch and the value 0 is reserved for representing ''0000-00-00 + 00:00:00'', the "zero" TIMESTAMP value. An optional fsp value in the range from + 0 to 6 may be given to specify fractional seconds precision. A value of 0 signifies + that there is no fractional part. If omitted, the default precision is 0. The + way the server handles TIMESTAMP definitions depends on the value of the explicit_defaults_for_timestamp + system variable (see https://dev.mysql.com/doc/refman/8.3/en/server-system-variables.html). + If explicit_defaults_for_timestamp is enabled, there is no automatic assignment + of the DEFAULT CURRENT_TIMESTAMP or ON UPDATE CURRENT_TIMESTAMP attributes to + any TIMESTAMP column. They must be included explicitly in the column definition. + Also, any TIMESTAMP not explicitly declared as NOT NULL permits NULL values. + If explicit_defaults_for_timestamp is disabled, the server handles TIMESTAMP + as follows: Unless specified otherwise, the first TIMESTAMP column in a table + is defined to be automatically set to the date and time of the most recent modification + if not explicitly assigned a value. This makes TIMESTAMP useful for recording + the timestamp of an INSERT or UPDATE operation. You can also set any TIMESTAMP + column to the current date and time by assigning it a NULL value, unless it + has been defined with the NULL attribute to permit NULL values. Automatic initialization + and updating to the current date and time can be specified using DEFAULT CURRENT_TIMESTAMP + and ON UPDATE CURRENT_TIMESTAMP column definition clauses. By default, the first + TIMESTAMP column has these properties, as previously noted. However, any TIMESTAMP + column in a table can be defined to have these properties. URL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-type-syntax.html' + examples: [] + - name: TIMESTAMPADD + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: TIMESTAMPADD(unit,interval,datetime_expr) + args: + - name: unit + optional: false + type: any + - name: interval + optional: false + type: any + - name: datetime_expr + optional: false + type: any + tags: [] + aliases: [] + summary: Adds the integer expression interval to the date or datetime expression + description: 'Adds the integer expression interval to the date or datetime expression + datetime_expr. The unit for interval is given by the unit argument, which should + be one of the following values: MICROSECOND (microseconds), SECOND, MINUTE, + HOUR, DAY, WEEK, MONTH, QUARTER, or YEAR. The unit value may be specified using + one of keywords as shown, or with a prefix of SQL_TSI_. For example, DAY and + SQL_TSI_DAY both are legal. This function returns NULL if interval or datetime_expr + is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: TIMESTAMPDIFF + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: TIMESTAMPDIFF(unit,datetime_expr1,datetime_expr2) + args: + - name: unit + optional: false + type: any + - name: datetime_expr1 + optional: false + type: any + - name: datetime_expr2 + optional: false + type: any + tags: [] + aliases: [] + summary: "Returns datetime_expr2 \u2212 datetime_expr1, where datetime_expr1 and" + description: "Returns datetime_expr2 \u2212 datetime_expr1, where datetime_expr1\ + \ and\ndatetime_expr2 are date or datetime expressions. One expression may be\n\ + a date and the other a datetime; a date value is treated as a datetime\nhaving\ + \ the time part '00:00:00' where necessary. The unit for the\nresult (an integer)\ + \ is given by the unit argument. The legal values for\nunit are the same as\ + \ those listed in the description of the\nTIMESTAMPADD() function.\n\nThis function\ + \ returns NULL if datetime_expr1 or datetime_expr2 is NULL.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html" + examples: [] + - name: TIME_FORMAT + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: TIME_FORMAT(time,format) + args: + - name: time + optional: false + type: any + - name: format + optional: false + type: any + tags: [] + aliases: [] + summary: This is used like the DATE_FORMAT() function, but the format string may + description: 'This is used like the DATE_FORMAT() function, but the format string + may contain format specifiers only for hours, minutes, seconds, and microseconds. + Other specifiers produce a NULL or 0. TIME_FORMAT() returns NULL if time or + format is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: TIME_TO_SEC + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: TIME_TO_SEC(time) + args: + - name: time + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the time argument, converted to seconds. + description: 'Returns the time argument, converted to seconds. Returns NULL if + time is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: TINYINT + category_id: data_types + category_label: Data Types + signature: + display: TINYINT(M) + args: + - name: M + optional: false + type: any + tags: [] + aliases: [] + summary: A very small integer. + description: 'A very small integer. The signed range is -128 to 127. The unsigned + range is 0 to 255. URL: https://dev.mysql.com/doc/refman/8.3/en/numeric-type-syntax.html' + examples: [] + - name: TO_BASE64 + category_id: string_functions + category_label: String Functions + signature: + display: TO_BASE64(str) + args: + - name: str + optional: false + type: any + tags: [] + aliases: [] + summary: Converts the string argument to base-64 encoded form and returns the + description: 'Converts the string argument to base-64 encoded form and returns + the result as a character string with the connection character set and collation. + If the argument is not a string, it is converted to a string before conversion + takes place. The result is NULL if the argument is NULL. Base-64 encoded strings + can be decoded using the FROM_BASE64() function. URL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html' + examples: [] + - name: TO_DAYS + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: TO_DAYS(date) + args: + - name: date + optional: false + type: any + tags: [] + aliases: [] + summary: Given a date date, returns a day number (the number of days since year + description: 'Given a date date, returns a day number (the number of days since + year 0). Returns NULL if date is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: TO_SECONDS + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: TO_SECONDS(expr) + args: + - name: expr + optional: false + type: any + tags: [] + aliases: [] + summary: Given a date or datetime expr, returns the number of seconds since the + description: 'Given a date or datetime expr, returns the number of seconds since + the year 0. If expr is not a valid date or datetime value (including NULL), + it returns NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: TRIM + category_id: string_functions + category_label: String Functions + signature: + display: TRIM([{BOTH | LEADING | TRAILING} [remstr] FROM] str) + args: + - name: '[{BOTH | LEADING | TRAILING} [remstr] FROM] str' + optional: false + type: any + tags: [] + aliases: [] + summary: FROM] str) + description: 'FROM] str) Returns the string str with all remstr prefixes or suffixes + removed. If none of the specifiers BOTH, LEADING, or TRAILING is given, BOTH + is assumed. remstr is optional and, if not specified, spaces are removed. URL: + https://dev.mysql.com/doc/refman/8.3/en/string-functions.html' + examples: [] + - name: TRUNCATE + category_id: numeric_functions + category_label: Numeric Functions + signature: + display: TRUNCATE(X,D) + args: + - name: X + optional: false + type: any + - name: D + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the number X, truncated to D decimal places. + description: 'Returns the number X, truncated to D decimal places. If D is 0, + the result has no decimal point or fractional part. D can be negative to cause + D digits left of the decimal point of the value X to become zero. If X or D + is NULL, the function returns NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/mathematical-functions.html' + examples: [] + - name: UCASE + category_id: string_functions + category_label: String Functions + signature: + display: UCASE(str) + args: + - name: str + optional: false + type: any + tags: [] + aliases: [] + summary: UCASE() is a synonym for UPPER(). + description: 'UCASE() is a synonym for UPPER(). URL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html' + examples: [] + - name: UNCOMPRESS + category_id: encryption_functions + category_label: Encryption Functions + signature: + display: UNCOMPRESS(string_to_uncompress) + args: + - name: string_to_uncompress + optional: false + type: any + tags: [] + aliases: [] + summary: Uncompresses a string compressed by the COMPRESS() function. + description: 'Uncompresses a string compressed by the COMPRESS() function. If + the argument is not a compressed value, the result is NULL; if string_to_uncompress + is NULL, the result is also NULL. This function requires MySQL to have been + compiled with a compression library such as zlib. Otherwise, the return value + is always NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/encryption-functions.html' + examples: [] + - name: UNCOMPRESSED_LENGTH + category_id: encryption_functions + category_label: Encryption Functions + signature: + display: UNCOMPRESSED_LENGTH(compressed_string) + args: + - name: compressed_string + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the length that the compressed string had before being + description: 'Returns the length that the compressed string had before being compressed. + Returns NULL if compressed_string is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/encryption-functions.html' + examples: [] + - name: UNHEX + category_id: string_functions + category_label: String Functions + signature: + display: UNHEX(str) + args: + - name: str + optional: false + type: any + tags: [] + aliases: [] + summary: For a string argument str, UNHEX(str) interprets each pair of + description: 'For a string argument str, UNHEX(str) interprets each pair of characters + in the argument as a hexadecimal number and converts it to the byte represented + by the number. The return value is a binary string. URL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html' + examples: [] + - name: UNIX_TIMESTAMP + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: UNIX_TIMESTAMP([date]) + args: + - name: '[date]' + optional: false + type: any + tags: [] + aliases: [] + summary: If UNIX_TIMESTAMP() is called with no date argument, it returns a Unix + description: 'If UNIX_TIMESTAMP() is called with no date argument, it returns + a Unix timestamp representing seconds since ''1970-01-01 00:00:00'' UTC. If + UNIX_TIMESTAMP() is called with a date argument, it returns the value of the + argument as seconds since ''1970-01-01 00:00:00'' UTC. The server interprets + date as a value in the session time zone and converts it to an internal Unix + timestamp value in UTC. (Clients can set the session time zone as described + in https://dev.mysql.com/doc/refman/8.3/en/time-zone-support.html.) The date + argument may be a DATE, DATETIME, or TIMESTAMP string, or a number in YYMMDD, + YYMMDDhhmmss, YYYYMMDD, or YYYYMMDDhhmmss format. If the argument includes a + time part, it may optionally include a fractional seconds part. The return value + is an integer if no argument is given or the argument does not include a fractional + seconds part, or DECIMAL if an argument is given that includes a fractional + seconds part. When the date argument is a TIMESTAMP column, UNIX_TIMESTAMP() + returns the internal timestamp value directly, with no implicit "string-to-Unix-timestamp" + conversion. The valid range of argument values is the same as for the TIMESTAMP + data type: ''1970-01-01 00:00:01.000000'' UTC to ''2038-01-19 03:14:07.999999'' + UTC for 32-bit platforms; for MySQL running on 64-bit platforms, the valid range + of argument values for UNIX_TIMESTAMP() is ''1970-01-01 00:00:01.000000'' UTC + to ''3001-01-19 03:14:07.999999'' UTC (corresponding to 32536771199.999999 seconds). + Regardless of MySQL version or platform architecture, if you pass an out-of-range + date to UNIX_TIMESTAMP(), it returns 0. If date is NULL, it returns NULL. URL: + https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: UPDATEXML + category_id: xml + category_label: XML + signature: + display: UPDATEXML(xml_target, xpath_expr, new_xml) + args: + - name: xml_target + optional: false + type: any + - name: xpath_expr + optional: false + type: any + - name: new_xml + optional: false + type: any + tags: [] + aliases: [] + summary: This function replaces a single portion of a given fragment of XML + description: 'This function replaces a single portion of a given fragment of XML + markup xml_target with a new XML fragment new_xml, and then returns the changed + XML. The portion of xml_target that is replaced matches an XPath expression + xpath_expr supplied by the user. If no expression matching xpath_expr is found, + or if multiple matches are found, the function returns the original xml_target + XML fragment. All three arguments should be strings. If any of the arguments + to UpdateXML() are NULL, the function returns NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/xml-functions.html' + examples: [] + - name: UPPER + category_id: string_functions + category_label: String Functions + signature: + display: UPPER(str) + args: + - name: str + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the string str with all characters changed to uppercase + description: "Returns the string str with all characters changed to uppercase\n\ + according to the current character set mapping, or NULL if str is NULL.\nThe\ + \ default character set is utf8mb4.\n\nmysql> SELECT UPPER('Hej');\n \ + \ -> 'HEJ'\n\nSee the description of LOWER() for information that also applies\ + \ to\nUPPER(). This included information about how to perform lettercase\nconversion\ + \ of binary strings (BINARY, VARBINARY, BLOB) for which these\nfunctions are\ + \ ineffective, and information about case folding for\nUnicode character sets.\n\ + \nURL: https://dev.mysql.com/doc/refman/8.3/en/string-functions.html" + examples: [] + - name: USER + category_id: information_functions + category_label: Information Functions + signature: + display: USER + args: [] + tags: [] + aliases: [] + summary: Returns the current MySQL user name and host name as a string in the + description: 'Returns the current MySQL user name and host name as a string in + the utf8mb3 character set. URL: https://dev.mysql.com/doc/refman/8.3/en/information-functions.html' + examples: [] + - name: UTC_DATE + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: UTC_DATE + args: [] + tags: [] + aliases: [] + summary: Returns the current UTC date as a value in 'YYYY-MM-DD' or YYYYMMDD + description: 'Returns the current UTC date as a value in ''YYYY-MM-DD'' or YYYYMMDD + format, depending on whether the function is used in string or numeric context. + URL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: UTC_TIME + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: UTC_TIME([fsp]) + args: + - name: '[fsp]' + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the current UTC time as a value in 'hh:mm:ss' or hhmmss format, + description: 'Returns the current UTC time as a value in ''hh:mm:ss'' or hhmmss + format, depending on whether the function is used in string or numeric context. + If the fsp argument is given to specify a fractional seconds precision from + 0 to 6, the return value includes a fractional seconds part of that many digits. + URL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: UTC_TIMESTAMP + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: UTC_TIMESTAMP([fsp]) + args: + - name: '[fsp]' + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the current UTC date and time as a value in 'YYYY-MM-DD + description: 'Returns the current UTC date and time as a value in ''YYYY-MM-DD + hh:mm:ss'' or YYYYMMDDhhmmss format, depending on whether the function is used + in string or numeric context. If the fsp argument is given to specify a fractional + seconds precision from 0 to 6, the return value includes a fractional seconds + part of that many digits. URL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: UUID + category_id: miscellaneous_functions + category_label: Miscellaneous Functions + signature: + display: UUID + args: [] + tags: [] + aliases: [] + summary: Returns a Universal Unique Identifier (UUID) generated according to RFC + description: "Returns a Universal Unique Identifier (UUID) generated according\ + \ to RFC\n4122, \"A Universally Unique IDentifier (UUID) URN Namespace\"\n(http://www.ietf.org/rfc/rfc4122.txt).\n\ + \nA UUID is designed as a number that is globally unique in space and\ntime.\ + \ Two calls to UUID() are expected to generate two different\nvalues, even if\ + \ these calls are performed on two separate devices not\nconnected to each other.\n\ + \n*Warning*:\n\nAlthough UUID() values are intended to be unique, they are not\n\ + necessarily unguessable or unpredictable. If unpredictability is\nrequired,\ + \ UUID values should be generated some other way.\n\nUUID() returns a value\ + \ that conforms to UUID version 1 as described in\nRFC 4122. The value is a\ + \ 128-bit number represented as a utf8mb3 string\nof five hexadecimal numbers\ + \ in aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\nformat:\n\no The first three numbers\ + \ are generated from the low, middle, and high\n parts of a timestamp. The\ + \ high part also includes the UUID version\n number.\n\no The fourth number\ + \ preserves temporal uniqueness in case the timestamp\n value loses monotonicity\ + \ (for example, due to daylight saving time).\n\no The fifth number is an IEEE\ + \ 802 node number that provides spatial\n uniqueness. A random number is substituted\ + \ if the latter is not\n available (for example, because the host device has\ + \ no Ethernet card,\n or it is unknown how to find the hardware address of\ + \ an interface on\n the host operating system). In this case, spatial uniqueness\ + \ cannot\n be guaranteed. Nevertheless, a collision should have very low\n\ + \ probability.\n\n The MAC address of an interface is taken into account only\ + \ on\n FreeBSD, Linux, and Windows. On other operating systems, MySQL uses\ + \ a\n randomly generated 48-bit number.\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/miscellaneous-functions.html" + examples: [] + - name: UUID_SHORT + category_id: miscellaneous_functions + category_label: Miscellaneous Functions + signature: + display: UUID_SHORT + args: [] + tags: [] + aliases: [] + summary: Returns a "short" universal identifier as a 64-bit unsigned integer. + description: "Returns a \"short\" universal identifier as a 64-bit unsigned integer.\n\ + Values returned by UUID_SHORT() differ from the string-format 128-bit\nidentifiers\ + \ returned by the UUID() function and have different\nuniqueness properties.\ + \ The value of UUID_SHORT() is guaranteed to be\nunique if the following conditions\ + \ hold:\n\no The server_id value of the current server is between 0 and 255\ + \ and is\n unique among your set of source and replica servers\n\no You do\ + \ not set back the system time for your server host between\n mysqld restarts\n\ + \no You invoke UUID_SHORT() on average fewer than 16 million times per\n second\ + \ between mysqld restarts\n\nThe UUID_SHORT() return value is constructed this\ + \ way:\n\n (server_id & 255) << 56\n+ (server_startup_time_in_seconds << 24)\n\ + + incremented_variable++;\n\nURL: https://dev.mysql.com/doc/refman/8.3/en/miscellaneous-functions.html" + examples: [] + - name: UUID_TO_BIN + category_id: miscellaneous_functions + category_label: Miscellaneous Functions + signature: + display: UUID_TO_BIN(string_uuid) + args: + - name: string_uuid + optional: false + type: any + tags: [] + aliases: [] + summary: Converts a string UUID to a binary UUID and returns the result. + description: "Converts a string UUID to a binary UUID and returns the result.\ + \ (The\nIS_UUID() function description lists the permitted string UUID\nformats.)\ + \ The return binary UUID is a VARBINARY(16) value. If the UUID\nargument is\ + \ NULL, the return value is NULL. If any argument is invalid,\nan error occurs.\n\ + \nUUID_TO_BIN() takes one or two arguments:\n\no The one-argument form takes\ + \ a string UUID value. The binary result is\n in the same order as the string\ + \ argument.\n\no The two-argument form takes a string UUID value and a flag\ + \ value:\n\n o If swap_flag is 0, the two-argument form is equivalent to the\n\ + \ one-argument form. The binary result is in the same order as the\n string\ + \ argument.\n\n o If swap_flag is 1, the format of the return value differs:\ + \ The\n time-low and time-high parts (the first and third groups of\n \ + \ hexadecimal digits, respectively) are swapped. This moves the more\n rapidly\ + \ varying part to the right and can improve indexing\n efficiency if the\ + \ result is stored in an indexed column.\n\nTime-part swapping assumes the use\ + \ of UUID version 1 values, such as\nare generated by the UUID() function. For\ + \ UUID values produced by other\nmeans that do not follow version 1 format,\ + \ time-part swapping provides\nno benefit. For details about version 1 format,\ + \ see the UUID() function\ndescription.\n\nSuppose that you have the following\ + \ string UUID value:\n\nmysql> SET @uuid = '6ccd780c-baba-1026-9564-5b8c656024db';\n\ + \nTo convert the string UUID to binary with or without time-part\nswapping,\ + \ use UUID_TO_BIN():\n\nmysql> SELECT HEX(UUID_TO_BIN(@uuid));\n+----------------------------------+\n\ + | HEX(UUID_TO_BIN(@uuid)) |\n+----------------------------------+\n\ + | 6CCD780CBABA102695645B8C656024DB |\n+----------------------------------+\n\ + mysql> SELECT HEX(UUID_TO_BIN(@uuid, 0));\n+----------------------------------+\n\ + | HEX(UUID_TO_BIN(@uuid, 0)) |\n+----------------------------------+\n\ + | 6CCD780CBABA102695645B8C656024DB |\n+----------------------------------+\n\ + mysql> SELECT HEX(UUID_TO_BIN(@uuid, 1));\n+----------------------------------+\n\ + \ ..." + examples: [] + - name: VALIDATE_PASSWORD_STRENGTH + category_id: encryption_functions + category_label: Encryption Functions + signature: + display: VALIDATE_PASSWORD_STRENGTH(str) + args: + - name: str + optional: false + type: any + tags: [] + aliases: [] + summary: Given an argument representing a plaintext password, this function + description: 'Given an argument representing a plaintext password, this function + returns an integer to indicate how strong the password is, or NULL if the argument + is NULL. The return value ranges from 0 (weak) to 100 (strong). URL: https://dev.mysql.com/doc/refman/8.3/en/encryption-functions.html' + examples: [] + - name: VALUES + category_id: miscellaneous_functions + category_label: Miscellaneous Functions + signature: + display: VALUES(col_name) + args: + - name: col_name + optional: false + type: any + tags: [] + aliases: [] + summary: In an INSERT ... + description: 'In an INSERT ... ON DUPLICATE KEY UPDATE statement, you can use + the VALUES(col_name) function in the UPDATE clause to refer to column values + from the INSERT portion of the statement. In other words, VALUES(col_name) in + the UPDATE clause refers to the value of col_name that would be inserted, had + no duplicate-key conflict occurred. This function is especially useful in multiple-row + inserts. The VALUES() function is meaningful only in the ON DUPLICATE KEY UPDATE + clause of INSERT statements and returns NULL otherwise. See https://dev.mysql.com/doc/refman/8.3/en/insert-on-duplicate.html. + URL: https://dev.mysql.com/doc/refman/8.3/en/miscellaneous-functions.html' + examples: [] + - name: VARBINARY + category_id: data_types + category_label: Data Types + signature: + display: VARBINARY(M) + args: + - name: M + optional: false + type: any + tags: [] + aliases: [] + summary: The VARBINARY type is similar to the VARCHAR type, but stores binary + description: 'The VARBINARY type is similar to the VARCHAR type, but stores binary + byte strings rather than nonbinary character strings. M represents the maximum + column length in bytes. URL: https://dev.mysql.com/doc/refman/8.3/en/string-type-syntax.html' + examples: [] + - name: VARCHAR + category_id: data_types + category_label: Data Types + signature: + display: VARCHAR(M) + args: + - name: M + optional: false + type: any + tags: [] + aliases: [] + summary: collation_name] + description: 'collation_name] A variable-length string. M represents the maximum + column length in characters. The range of M is 0 to 65,535. The effective maximum + length of a VARCHAR is subject to the maximum row size (65,535 bytes, which + is shared among all columns) and the character set used. For example, utf8mb3 + characters can require up to three bytes per character, so a VARCHAR column + that uses the utf8mb3 character set can be declared to be a maximum of 21,844 + characters. See https://dev.mysql.com/doc/refman/8.3/en/column-count-limit.html. + MySQL stores VARCHAR values as a 1-byte or 2-byte length prefix plus data. The + length prefix indicates the number of bytes in the value. A VARCHAR column uses + one length byte if values require no more than 255 bytes, two length bytes if + values may require more than 255 bytes. *Note*: MySQL follows the standard SQL + specification, and does not remove trailing spaces from VARCHAR values. VARCHAR + is shorthand for CHARACTER VARYING. NATIONAL VARCHAR is the standard SQL way + to define that a VARCHAR column should use some predefined character set. MySQL + uses utf8mb3 as this predefined character set. https://dev.mysql.com/doc/refman/8.3/en/charset-national.html. + NVARCHAR is shorthand for NATIONAL VARCHAR. URL: https://dev.mysql.com/doc/refman/8.3/en/string-type-syntax.html' + examples: [] + - name: VARIANCE + category_id: aggregate_functions_and_modifiers + category_label: Aggregate Functions and Modifiers + signature: + display: VARIANCE(expr) + args: + - name: expr + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the population standard variance of expr. + description: 'Returns the population standard variance of expr. VARIANCE() is + a synonym for the standard SQL function VAR_POP(), provided as a MySQL extension. + If there are no matching rows, or if expr is NULL, VARIANCE() returns NULL. + This function executes as a window function if over_clause is present. over_clause + is as described in https://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html. + URL: https://dev.mysql.com/doc/refman/8.3/en/aggregate-functions.html' + examples: [] + - name: VAR_POP + category_id: aggregate_functions_and_modifiers + category_label: Aggregate Functions and Modifiers + signature: + display: VAR_POP(expr) + args: + - name: expr + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the population standard variance of expr. + description: 'Returns the population standard variance of expr. It considers rows + as the whole population, not as a sample, so it has the number of rows as the + denominator. You can also use VARIANCE(), which is equivalent but is not standard + SQL. If there are no matching rows, or if expr is NULL, VAR_POP() returns NULL. + This function executes as a window function if over_clause is present. over_clause + is as described in https://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html. + URL: https://dev.mysql.com/doc/refman/8.3/en/aggregate-functions.html' + examples: [] + - name: VAR_SAMP + category_id: aggregate_functions_and_modifiers + category_label: Aggregate Functions and Modifiers + signature: + display: VAR_SAMP(expr) + args: + - name: expr + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the sample variance of expr. + description: 'Returns the sample variance of expr. That is, the denominator is + the number of rows minus one. If there are no matching rows, or if expr is NULL, + VAR_SAMP() returns NULL. This function executes as a window function if over_clause + is present. over_clause is as described in https://dev.mysql.com/doc/refman/8.3/en/window-functions-usage.html. + URL: https://dev.mysql.com/doc/refman/8.3/en/aggregate-functions.html' + examples: [] + - name: VERSION + category_id: information_functions + category_label: Information Functions + signature: + display: VERSION + args: [] + tags: [] + aliases: [] + summary: Returns a string that indicates the MySQL server version. + description: 'Returns a string that indicates the MySQL server version. The string + uses the utf8mb3 character set. The value might have a suffix in addition to + the version number. See the description of the version system variable in https://dev.mysql.com/doc/refman/8.3/en/server-system-variables.html. + URL: https://dev.mysql.com/doc/refman/8.3/en/information-functions.html' + examples: [] + - name: WAIT_FOR_EXECUTED_GTID_SET + category_id: gtid + category_label: GTID + signature: + display: WAIT_FOR_EXECUTED_GTID_SET(gtid_set[, timeout]) + args: + - name: gtid_set[ + optional: false + type: any + - name: timeout] + optional: false + type: any + tags: [] + aliases: [] + summary: Wait until the server has applied all of the transactions whose global + description: 'Wait until the server has applied all of the transactions whose + global transaction identifiers are contained in gtid_set; that is, until the + condition GTID_SUBSET(gtid_subset, @@GLOBAL.gtid_executed) holds. See https://dev.mysql.com/doc/refman/8.3/en/replication-gtids-concepts.html + for a definition of GTID sets. If a timeout is specified, and timeout seconds + elapse before all of the transactions in the GTID set have been applied, the + function stops waiting. timeout is optional, and the default timeout is 0 seconds, + in which case the function always waits until all of the transactions in the + GTID set have been applied. timeout must be greater than or equal to 0; when + running in strict SQL mode, a negative timeout value is immediately rejected + with an error (ER_WRONG_ARGUMENTS (https://dev.mysql.com/doc/mysql-errors/8.3/en/server-error-reference.html + #error_er_wrong_arguments)); otherwise the function returns NULL, and raises + a warning. WAIT_FOR_EXECUTED_GTID_SET() monitors all the GTIDs that are applied + on the server, including transactions that arrive from all replication channels + and user clients. It does not take into account whether replication channels + have been started or stopped. For more information, see https://dev.mysql.com/doc/refman/8.3/en/replication-gtids.html. + URL: https://dev.mysql.com/doc/refman/8.3/en/gtid-functions.html' + examples: [] + - name: WEEK + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: WEEK(date[,mode]) + args: + - name: date[ + optional: false + type: any + - name: mode] + optional: false + type: any + tags: [] + aliases: [] + summary: This function returns the week number for date. + description: 'This function returns the week number for date. The two-argument + form of WEEK() enables you to specify whether the week starts on Sunday or Monday + and whether the return value should be in the range from 0 to 53 or from 1 to + 53. If the mode argument is omitted, the value of the default_week_format system + variable is used. See https://dev.mysql.com/doc/refman/8.3/en/server-system-variables.html. + For a NULL date value, the function returns NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: WEEKDAY + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: WEEKDAY(date) + args: + - name: date + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the weekday index for date (0 = Monday, 1 = Tuesday, ... + description: 'Returns the weekday index for date (0 = Monday, 1 = Tuesday, ... + 6 = Sunday). Returns NULL if date is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: WEEKOFYEAR + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: WEEKOFYEAR(date) + args: + - name: date + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the calendar week of the date as a number in the range from 1 + description: 'Returns the calendar week of the date as a number in the range from + 1 to 53. Returns NULL if date is NULL. WEEKOFYEAR() is a compatibility function + that is equivalent to WEEK(date,3). URL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: WEIGHT_STRING + category_id: string_functions + category_label: String Functions + signature: + display: WEIGHT_STRING(str [AS {CHAR|BINARY}(N) + args: + - name: str [AS {CHAR|BINARY}(N + optional: false + type: any + tags: [] + aliases: [] + summary: This function returns the weight string for the input string. + description: "This function returns the weight string for the input string. The\n\ + return value is a binary string that represents the comparison and\nsorting\ + \ value of the string, or NULL if the argument is NULL. It has\nthese properties:\n\ + \no If WEIGHT_STRING(str1) = WEIGHT_STRING(str2), then str1 = str2 (str1\n \ + \ and str2 are considered equal)\n\no If WEIGHT_STRING(str1) < WEIGHT_STRING(str2),\ + \ then str1 < str2 (str1\n sorts before str2)\n\nWEIGHT_STRING() is a debugging\ + \ function intended for internal use. Its\nbehavior can change without notice\ + \ between MySQL versions. It can be\nused for testing and debugging of collations,\ + \ especially if you are\nadding a new collation. See\nhttps://dev.mysql.com/doc/refman/8.3/en/adding-collation.html.\n\ + \nThis list briefly summarizes the arguments. More details are given in\nthe\ + \ discussion following the list.\n\no str: The input string expression.\n\n\ + o AS clause: Optional; cast the input string to a given type and\n length.\n\ + \no flags: Optional; unused.\n\nThe input string, str, is a string expression.\ + \ If the input is a\nnonbinary (character) string such as a CHAR, VARCHAR, or\ + \ TEXT value,\nthe return value contains the collation weights for the string.\ + \ If the\ninput is a binary (byte) string such as a BINARY, VARBINARY, or BLOB\n\ + value, the return value is the same as the input (the weight for each\nbyte\ + \ in a binary string is the byte value). If the input is NULL,\nWEIGHT_STRING()\ + \ returns NULL.\n\nExamples:\n\nmysql> SET @s = _utf8mb4 'AB' COLLATE utf8mb4_0900_ai_ci;\n\ + mysql> SELECT @s, HEX(@s), HEX(WEIGHT_STRING(@s));\n+------+---------+------------------------+\n\ + | @s | HEX(@s) | HEX(WEIGHT_STRING(@s)) |\n+------+---------+------------------------+\n\ + | AB | 4142 | 1C471C60 |\n+------+---------+------------------------+\n\ + \nmysql> SET @s = _utf8mb4 'ab' COLLATE utf8mb4_0900_ai_ci;\nmysql> SELECT @s,\ + \ HEX(@s), HEX(WEIGHT_STRING(@s));\n+------+---------+------------------------+\n\ + | @s | HEX(@s) | HEX(WEIGHT_STRING(@s)) |\n+------+---------+------------------------+\n\ + \ ..." + examples: [] + - name: YEAR + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: YEAR(date) + args: + - name: date + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the year for date, in the range 1000 to 9999, or 0 for the + description: 'Returns the year for date, in the range 1000 to 9999, or 0 for the + "zero" date. Returns NULL if date is NULL. URL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] + - name: YEARWEEK + category_id: date_and_time_functions + category_label: Date and Time Functions + signature: + display: YEARWEEK(date) + args: + - name: date + optional: false + type: any + tags: [] + aliases: [] + summary: Returns year and week for a date. + description: 'Returns year and week for a date. The year in the result may be + different from the year in the date argument for the first and the last week + of the year. Returns NULL if date is NULL. The mode argument works exactly like + the mode argument to WEEK(). For the single-argument syntax, a mode value of + 0 is used. Unlike WEEK(), the value of default_week_format does not influence + YEARWEEK(). URL: https://dev.mysql.com/doc/refman/8.3/en/date-and-time-functions.html' + examples: [] +versions: + '8': {} + '9': {} diff --git a/structures/engines/postgresql/builder.py b/structures/engines/postgresql/builder.py index 98a9c8f..67dc3a0 100644 --- a/structures/engines/postgresql/builder.py +++ b/structures/engines/postgresql/builder.py @@ -61,29 +61,43 @@ def generated(self): class PostgreSQLIndexBuilder(AbstractIndexBuilder): - TEMPLATE = ["%(type)s", "%(name)s", "ON", "%(table)s", "(%(columns)s)"] + # Different templates for inline (CREATE TABLE) vs standalone (CREATE INDEX) + INLINE_TEMPLATE = ["%(type)s", "(%(columns)s)"] # For PRIMARY KEY inside CREATE TABLE + STANDALONE_TEMPLATE = ["%(type)s", "%(name)s", "ON", "%(table)s", "(%(columns)s)"] # For CREATE INDEX - def __init__(self, index: 'PostgreSQLIndex', exclude: Optional[list[str]] = None): + def __init__(self, index: 'PostgreSQLIndex', exclude: Optional[list[str]] = None, inline: bool = False): + self.inline = inline # True when building for CREATE TABLE, False for standalone CREATE INDEX super().__init__(index, exclude) + + # Use appropriate template based on context + if self.inline and self.index.type.name == "PRIMARY": + self.TEMPLATE = self.INLINE_TEMPLATE + else: + self.TEMPLATE = self.STANDALONE_TEMPLATE + + # Add table to parts dictionary for standalone template + self.parts['table'] = self.table @property def type(self): if self.index.type.name == "PRIMARY": - return "ALTER TABLE ADD CONSTRAINT PRIMARY KEY" + return "PRIMARY KEY" if self.inline else "ALTER TABLE ADD CONSTRAINT PRIMARY KEY" elif self.index.type.name == "UNIQUE INDEX": - return "CREATE UNIQUE INDEX" + return "UNIQUE" if self.inline else "CREATE UNIQUE INDEX" else: return f"CREATE INDEX" @property def name(self): if self.index.type.name == "PRIMARY": - return f'"{self.index.name}"' + return f'"{self.index.name}"' if not self.inline else '' return f'"{self.index.name}"' if self.index.name else '' @property def table(self): - return f'"{self.index.table.database.name}"."{self.index.table.name}"' + # Use schema if available, otherwise use database name + schema_or_db = self.index.table.schema if hasattr(self.index.table, 'schema') and self.index.table.schema else self.index.table.database.name + return f'"{schema_or_db}"."{self.index.table.name}"' @property def columns(self): diff --git a/structures/engines/postgresql/context.py b/structures/engines/postgresql/context.py index eccb11b..d058073 100644 --- a/structures/engines/postgresql/context.py +++ b/structures/engines/postgresql/context.py @@ -9,11 +9,27 @@ from structures.ssh_tunnel import SSHTunnel from structures.engines.context import QUERY_LOGS, AbstractContext -from structures.engines.database import SQLDatabase, SQLTable, SQLColumn, SQLIndex, SQLForeignKey, SQLTrigger +from structures.engines.database import ( + SQLDatabase, + SQLTable, + SQLColumn, + SQLIndex, + SQLForeignKey, + SQLTrigger, +) from structures.engines.datatype import SQLDataType, DataTypeCategory, DataTypeFormat from structures.engines.postgresql import MAP_COLUMN_FIELDS -from structures.engines.postgresql.database import PostgreSQLTable, PostgreSQLColumn, PostgreSQLIndex, PostgreSQLForeignKey, PostgreSQLRecord, PostgreSQLView, PostgreSQLTrigger, PostgreSQLDatabase +from structures.engines.postgresql.database import ( + PostgreSQLTable, + PostgreSQLColumn, + PostgreSQLIndex, + PostgreSQLForeignKey, + PostgreSQLRecord, + PostgreSQLView, + PostgreSQLTrigger, + PostgreSQLDatabase, +) from structures.engines.postgresql.datatype import PostgreSQLDataType from structures.engines.postgresql.indextype import PostgreSQLIndexType @@ -24,7 +40,8 @@ class PostgreSQLContext(AbstractContext): DATATYPE = PostgreSQLDataType INDEXTYPE = PostgreSQLIndexType - IDENTIFIER_QUOTE = '"' + IDENTIFIER_QUOTE_CHAR = '"' + DEFAULT_STATEMENT_SEPARATOR = ";" def __init__(self, connection: Connection): super().__init__(connection) @@ -32,29 +49,27 @@ def __init__(self, connection: Connection): self.host = connection.configuration.hostname self.user = connection.configuration.username self.password = connection.configuration.password - self.port = getattr(connection.configuration, 'port', 5432) + self.port = getattr(connection.configuration, "port", 5432) self._current_database: Optional[str] = None - self._ssh_tunnel = None - def _on_connect(self, *args, **kwargs): - super()._on_connect(*args, **kwargs) + def after_connect(self, *args, **kwargs): + super().after_connect(*args, **kwargs) self.execute("SELECT collname FROM pg_collation;") - self.COLLATIONS = {row['collname']: row['collname'] for row in self.fetchall()} + self.COLLATIONS = {row["collname"]: row["collname"] for row in self.fetchall()} - self.execute(""" - SELECT word FROM pg_get_keywords() - WHERE catcode = 'R' - ORDER BY word; - """) - self.KEYWORDS = tuple(row["word"] for row in self.fetchall()) + server_version = self.get_server_version() + self.KEYWORDS, builtin_functions = self.get_engine_vocabulary( + "postgresql", server_version + ) self.execute(""" SELECT routine_name FROM information_schema.routines WHERE routine_type = 'FUNCTION' ORDER BY routine_name; """) - self.FUNCTIONS = tuple(row["routine_name"] for row in self.fetchall()) + user_functions = tuple(row["routine_name"].upper() for row in self.fetchall()) + self.FUNCTIONS = tuple(dict.fromkeys(builtin_functions + user_functions)) self._load_custom_types() @@ -74,23 +89,24 @@ def _load_custom_types(self) -> None: SELECT enumlabel FROM pg_enum e JOIN pg_type t ON e.enumtypid = t.oid - WHERE t.typname = '{row['typname']}' + WHERE t.typname = '{row["typname"]}' ORDER BY e.enumsortorder """) - labels = [r['enumlabel'] for r in self.fetchall()] + labels = [r["enumlabel"] for r in self.fetchall()] datatype = SQLDataType( - name=row['typname'], + name=row["typname"], category=DataTypeCategory.CUSTOM, has_set=True, set=labels, - format=DataTypeFormat.STRING + format=DataTypeFormat.STRING, ) - setattr(PostgreSQLDataType, row['typname'].upper(), datatype) + setattr(PostgreSQLDataType, row["typname"].upper(), datatype) def connect(self, **connect_kwargs) -> None: if self._connection is None: try: - database = connect_kwargs.pop('database', 'postgres') + self.before_connect() + database = connect_kwargs.pop("database", "postgres") base_kwargs = dict( host=self.host, @@ -98,64 +114,35 @@ def connect(self, **connect_kwargs) -> None: password=self.password, database=database, port=self.port, - **connect_kwargs + **connect_kwargs, + ) + logger.debug( + "PostgreSQL connect target host=%s port=%s user=%s database=%s", + base_kwargs.get("host"), + base_kwargs.get("port"), + base_kwargs.get("user"), + base_kwargs.get("database"), ) - - # SSH tunnel support via connection configuration - if hasattr(self.connection, 'ssh_tunnel') and self.connection.ssh_tunnel: - ssh_config = self.connection.ssh_tunnel - self._ssh_tunnel = SSHTunnel( - ssh_config.hostname, int(ssh_config.port), - ssh_username=ssh_config.username, - ssh_password=ssh_config.password, - remote_port=self.port, - local_bind_address=(self.host, int(getattr(ssh_config, 'local_port', 0))) - ) - self._ssh_tunnel.start() - base_kwargs.update( - host=self.host, - port=self._ssh_tunnel.local_port, - ) self._connection = psycopg2.connect(**base_kwargs) - self._cursor = self._connection.cursor(cursor_factory=psycopg2.extras.RealDictCursor) + self._cursor = self._connection.cursor( + cursor_factory=psycopg2.extras.RealDictCursor + ) self._current_database = database except Exception as e: logger.error(f"Failed to connect to PostgreSQL: {e}", exc_info=True) raise else: - self._on_connect() - - def disconnect(self) -> None: - """Disconnect from database and stop SSH tunnel if active.""" - try: - if self._cursor: - self._cursor.close() - except Exception: - pass - - try: - if self._connection: - self._connection.close() - except Exception: - pass - - try: - if self._ssh_tunnel: - self._ssh_tunnel.stop() - self._ssh_tunnel = None - except Exception: - pass - - self._cursor = None - self._connection = None + self.after_connect() + + def after_disconnect(self): self._current_database = None - def _set_database(self, db_name: str) -> None: + def set_database(self, database: SQLDatabase) -> None: """Switch to a different database by reconnecting.""" - if self._current_database != db_name: + if self._current_database != database.name: self.disconnect() - self.connect(database=db_name) + self.connect(database=database.name) def get_server_version(self) -> str: self.execute("SELECT version() as version") @@ -163,9 +150,11 @@ def get_server_version(self) -> str: return version["version"] def get_server_uptime(self) -> Optional[int]: - self.execute("SELECT extract(epoch from now() - pg_postmaster_start_time()) as uptime;") + self.execute( + "SELECT extract(epoch from now() - pg_postmaster_start_time()) as uptime;" + ) result = self.fetchone() - return int(result['uptime']) if result else None + return int(result["uptime"]) if result else None def get_databases(self) -> list[SQLDatabase]: self.execute(""" @@ -176,47 +165,123 @@ def get_databases(self) -> list[SQLDatabase]: """) results = [] for i, row in enumerate(self.fetchall()): - results.append(PostgreSQLDatabase( - id=i, - name=row["database_name"], - context=self, - total_bytes=float(row["total_bytes"]), - get_tables_handler=self.get_tables, - get_views_handler=self.get_views, - get_triggers_handler=self.get_triggers, - )) + results.append( + PostgreSQLDatabase( + id=i, + name=row["database_name"], + context=self, + total_bytes=float(row["total_bytes"]), + get_tables_handler=self.get_tables, + get_views_handler=self.get_views, + get_functions_handler=self.get_functions, + get_procedures_handler=self.get_procedures, + get_triggers_handler=self.get_triggers, + ) + ) return results def get_views(self, database: SQLDatabase) -> list[PostgreSQLView]: - self._set_database(database.name) + self.set_database(database) results = [] - self.execute(f"SELECT schemaname, viewname, definition FROM pg_views WHERE schemaname NOT IN ('information_schema', 'pg_catalog') ORDER BY schemaname, viewname") + self.execute( + f"SELECT schemaname, viewname, definition FROM pg_views WHERE schemaname NOT IN ('information_schema', 'pg_catalog') ORDER BY schemaname, viewname" + ) for i, result in enumerate(self.fetchall()): - results.append(PostgreSQLView( - id=i, - name=f"{result['schemaname']}.{result['viewname']}", - database=database, - sql=result['definition'] - )) + results.append( + PostgreSQLView( + id=i, + name=result["viewname"], + database=database, + statement=result["definition"], + ) + ) + + return results + + def get_functions(self, database: SQLDatabase) -> list["PostgreSQLFunction"]: + from structures.engines.postgresql.database import PostgreSQLFunction + + self.set_database(database) + results = [] + query = """ + SELECT + routine_name, + routine_definition, + data_type as returns, + external_language as language, + is_deterministic + FROM information_schema.routines + WHERE routine_schema NOT IN ('information_schema', 'pg_catalog') + AND routine_type = 'FUNCTION' + ORDER BY routine_name + """ + self.execute(query) + for i, result in enumerate(self.fetchall()): + results.append( + PostgreSQLFunction( + id=i, + name=result["routine_name"], + database=database, + returns=result["returns"] or "void", + language=result["language"] or "plpgsql", + statement=result["routine_definition"] or "", + parameters="", + volatility="VOLATILE", + ) + ) + + return results + + def get_procedures(self, database: SQLDatabase) -> list["PostgreSQLProcedure"]: + from structures.engines.postgresql.database import PostgreSQLProcedure + + self.set_database(database) + results = [] + query = """ + SELECT + routine_name, + routine_definition, + external_language as language + FROM information_schema.routines + WHERE routine_schema NOT IN ('information_schema', 'pg_catalog') + AND routine_type = 'PROCEDURE' + ORDER BY routine_name + """ + self.execute(query) + for i, result in enumerate(self.fetchall()): + results.append( + PostgreSQLProcedure( + id=i, + name=result["routine_name"], + database=database, + language=result["language"] or "plpgsql", + statement=result["routine_definition"] or "", + parameters="", + ) + ) return results def get_triggers(self, database: SQLDatabase) -> list[PostgreSQLTrigger]: - self._set_database(database.name) + self.set_database(database) results = [] - self.execute(f"SELECT n.nspname as schemaname, tgname, pg_get_triggerdef(t.oid) as sql FROM pg_trigger t JOIN pg_class c ON t.tgrelid = c.oid JOIN pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname NOT IN ('information_schema', 'pg_catalog') ORDER BY n.nspname, tgname") + self.execute( + f"SELECT n.nspname as schemaname, tgname, pg_get_triggerdef(t.oid) as sql FROM pg_trigger t JOIN pg_class c ON t.tgrelid = c.oid JOIN pg_namespace n ON c.relnamespace = n.oid WHERE n.nspname NOT IN ('information_schema', 'pg_catalog') ORDER BY n.nspname, tgname" + ) for i, result in enumerate(self.fetchall()): - results.append(PostgreSQLTrigger( - id=i, - name=f"{result['schemaname']}.{result['tgname']}", - database=database, - sql=result['sql'] - )) + results.append( + PostgreSQLTrigger( + id=i, + name=result["tgname"], + database=database, + statement=result["sql"], + ) + ) return results def get_tables(self, database: SQLDatabase) -> list[SQLTable]: - self._set_database(database.name) + self.set_database(database) QUERY_LOGS.append(f"/* get_tables for database={database.name} */") self.execute(f""" @@ -232,13 +297,14 @@ def get_tables(self, database: SQLDatabase) -> list[SQLTable]: results.append( PostgreSQLTable( id=i, - name=row['tablename'], - schema=row['schemaname'], + name=row["tablename"], + schema=row["schemaname"], database=database, - total_bytes=float(row['total_bytes']), - total_rows=row['total_rows'], + total_bytes=float(row["total_bytes"]), + total_rows=row["total_rows"], get_columns_handler=self.get_columns, get_indexes_handler=self.get_indexes, + get_checks_handler=self.get_checks, get_foreign_keys_handler=self.get_foreign_keys, get_records_handler=self.get_records, ) @@ -262,20 +328,20 @@ def get_columns(self, table: SQLTable) -> list[SQLColumn]: """) for i, row in enumerate(self.cursor.fetchall()): - is_nullable = row['is_nullable'] == 'YES' - datatype = PostgreSQLDataType.get_by_name(row['data_type']) + is_nullable = row["is_nullable"] == "YES" + datatype = PostgreSQLDataType.get_by_name(row["data_type"]) results.append( PostgreSQLColumn( id=i, - name=row['column_name'], + name=row["column_name"], datatype=datatype, is_nullable=is_nullable, table=table, - server_default=row['column_default'], - length=row['character_maximum_length'], - numeric_precision=row['numeric_precision'], - numeric_scale=row['numeric_scale'], + server_default=row["column_default"], + length=row["character_maximum_length"], + numeric_precision=row["numeric_precision"], + numeric_scale=row["numeric_scale"], ) ) @@ -290,14 +356,20 @@ def get_indexes(self, table: SQLTable) -> list[SQLIndex]: results: list[SQLIndex] = [] index_data: dict[str, dict[str, Any]] = {} - # Get primary key + # Get primary key using pg_constraint + schema_or_db = table.schema if table.schema else table.database.name self.execute(f""" - SELECT COLUMN_NAME - FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE - WHERE TABLE_SCHEMA = '{table.schema}' AND TABLE_NAME = '{table.name}' AND CONSTRAINT_NAME = 'PRIMARY' - ORDER BY ORDINAL_POSITION + SELECT a.attname AS column_name + FROM pg_index i + JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey) + JOIN pg_class c ON c.oid = i.indrelid + JOIN pg_namespace n ON n.oid = c.relnamespace + WHERE n.nspname = '{schema_or_db}' + AND c.relname = '{table.name}' + AND i.indisprimary + ORDER BY a.attnum """) - pk_columns = [row['COLUMN_NAME'] for row in self.fetchall()] + pk_columns = [row["column_name"] for row in self.fetchall()] if pk_columns: results.append( PostgreSQLIndex( @@ -324,30 +396,73 @@ def get_indexes(self, table: SQLTable) -> list[SQLIndex]: GROUP BY idx.relname, ind.indisunique """) for row in self.fetchall(): - index_data[row['index_name']] = { - 'columns': list(row['columns']) if row['columns'] else [], - 'unique': bool(row['is_unique']) + index_data[row["index_name"]] = { + "columns": list(row["columns"]) if row["columns"] else [], + "unique": bool(row["is_unique"]), } for i, (idx_name, data) in enumerate(index_data.items(), start=1): - idx_type = PostgreSQLIndexType.UNIQUE if data['unique'] else PostgreSQLIndexType.INDEX + idx_type = ( + PostgreSQLIndexType.UNIQUE + if data["unique"] + else PostgreSQLIndexType.INDEX + ) results.append( PostgreSQLIndex( id=i, name=idx_name, type=idx_type, - columns=data['columns'], + columns=data["columns"], table=table, ) ) return results - def get_foreign_keys(self, table: SQLTable) -> list[SQLForeignKey]: + def get_checks(self, table: PostgreSQLTable) -> list[PostgreSQLCheck]: + from structures.engines.postgresql.database import PostgreSQLCheck + if table is None or table.is_new: return [] - self._set_database(table.database.name) + schema_or_db = table.schema if table.schema else table.database.name + + query = f""" + SELECT + con.conname AS constraint_name, + pg_get_constraintdef(con.oid) AS check_clause + FROM pg_constraint con + JOIN pg_class rel ON rel.oid = con.conrelid + JOIN pg_namespace nsp ON nsp.oid = rel.relnamespace + WHERE con.contype = 'c' + AND nsp.nspname = '{schema_or_db}' + AND rel.relname = '{table.name}' + ORDER BY con.conname + """ + + self.execute(query) + rows = self.fetchall() + + results = [] + for i, row in enumerate(rows): + # Extract expression from "CHECK (expression)" format + check_def = row["check_clause"] + expression = check_def.replace("CHECK (", "").rstrip(")") + + results.append( + PostgreSQLCheck( + id=i, + name=row["constraint_name"], + table=table, + expression=expression, + ) + ) + + return results + + def get_foreign_keys(self, table: SQLTable) -> list[SQLForeignKey]: + if table is None or table.is_new: + return [] logger.debug(f"get_foreign_keys for table={table.name}") @@ -385,28 +500,39 @@ def get_foreign_keys(self, table: SQLTable) -> list[SQLForeignKey]: """) foreign_keys = [] _rule_map = { - 'a': 'NO ACTION', - 'r': 'RESTRICT', - 'c': 'CASCADE', - 'n': 'SET NULL', - 'd': 'SET DEFAULT', + "a": "NO ACTION", + "r": "RESTRICT", + "c": "CASCADE", + "n": "SET NULL", + "d": "SET DEFAULT", } for i, row in enumerate(self.fetchall()): - foreign_keys.append(PostgreSQLForeignKey( - id=i, - name=row['constraint_name'], - columns=list(row['columns']), - table=table, - reference_table=f"{row['referenced_schema']}.{row['referenced_table']}", - reference_columns=list(row['referenced_columns']), - on_update=_rule_map.get(row['on_update'], 'NO ACTION'), - on_delete=_rule_map.get(row['on_delete'], 'NO ACTION'), - )) + foreign_keys.append( + PostgreSQLForeignKey( + id=i, + name=row["constraint_name"], + columns=list(row["columns"]), + table=table, + reference_table=f"{row['referenced_schema']}.{row['referenced_table']}", + reference_columns=list(row["referenced_columns"]), + on_update=_rule_map.get(row["on_update"], "NO ACTION"), + on_delete=_rule_map.get(row["on_delete"], "NO ACTION"), + ) + ) return foreign_keys - def get_records(self, table: SQLTable, /, *, filters: Optional[str] = None, limit: int = 1000, offset: int = 0, orders: Optional[str] = None) -> list[PostgreSQLRecord]: + def get_records( + self, + table: SQLTable, + /, + *, + filters: Optional[str] = None, + limit: int = 1000, + offset: int = 0, + orders: Optional[str] = None, + ) -> list[PostgreSQLRecord]: logger.debug(f"get records for table={table.name}") QUERY_LOGS.append(f"/* get_records for table={table.name} */") if table is None or table.is_new: @@ -420,24 +546,25 @@ def get_records(self, table: SQLTable, /, *, filters: Optional[str] = None, limi if orders: order = f"ORDER BY {orders}" - query = [f"SELECT *", - f'FROM "{table.schema}"."{table.name}"', - f"{where}", - f"{order}", - f"LIMIT {limit} OFFSET {offset}", - ] + query = [ + f"SELECT *", + f'FROM "{table.schema}"."{table.name}"', + f"{where}", + f"{order}", + f"LIMIT {limit} OFFSET {offset}", + ] self.execute(" ".join(query)) results = [] for i, record in enumerate(self.fetchall(), start=offset): - results.append( - PostgreSQLRecord(id=i, table=table, values=dict(record)) - ) + results.append(PostgreSQLRecord(id=i, table=table, values=dict(record))) return results - def build_empty_table(self, database: SQLDatabase, /, name: Optional[str] = None, **default_values) -> PostgreSQLTable: + def build_empty_table( + self, database: SQLDatabase, /, name: Optional[str] = None, **default_values + ) -> PostgreSQLTable: id = PostgreSQLContext.get_temporary_id(database.tables) if name is None: @@ -446,28 +573,41 @@ def build_empty_table(self, database: SQLDatabase, /, name: Optional[str] = None return PostgreSQLTable( id=id, name=name, + schema=default_values.get("schema", "public"), database=database, get_indexes_handler=self.get_indexes, get_columns_handler=self.get_columns, + get_checks_handler=self.get_checks, get_foreign_keys_handler=self.get_foreign_keys, get_records_handler=self.get_records, ).copy() - def build_empty_column(self, table: SQLTable, datatype: SQLDataType, /, name: Optional[str] = None, **default_values) -> PostgreSQLColumn: + def build_empty_column( + self, + table: SQLTable, + datatype: SQLDataType, + /, + name: Optional[str] = None, + **default_values, + ) -> PostgreSQLColumn: id = PostgreSQLContext.get_temporary_id(table.columns) if name is None: name = _(f"Column{str(id * -1):03}") return PostgreSQLColumn( - id=id, - name=name, - table=table, - datatype=datatype, - **default_values + id=id, name=name, table=table, datatype=datatype, **default_values ) - def build_empty_index(self, table: PostgreSQLTable, indextype: PostgreSQLIndexType, columns: list[str], /, name: Optional[str] = None, **default_values) -> PostgreSQLIndex: + def build_empty_index( + self, + table: PostgreSQLTable, + indextype: PostgreSQLIndexType, + columns: list[str], + /, + name: Optional[str] = None, + **default_values, + ) -> PostgreSQLIndex: id = PostgreSQLContext.get_temporary_id(table.indexes) if name is None: @@ -481,7 +621,33 @@ def build_empty_index(self, table: PostgreSQLTable, indextype: PostgreSQLIndexTy table=table, ) - def build_empty_foreign_key(self, table: PostgreSQLTable, columns: list[str], /, name: Optional[str] = None, **default_values) -> PostgreSQLForeignKey: + def build_empty_check( + self, + table: PostgreSQLTable, + /, + name: Optional[str] = None, + expression: Optional[str] = None, + **default_values, + ) -> PostgreSQLCheck: + from structures.engines.postgresql.database import PostgreSQLCheck + + id = PostgreSQLContext.get_temporary_id(table.checks) + + if name is None: + name = f"check_{abs(id)}" + + return PostgreSQLCheck( + id=id, name=name, table=table, expression=expression or "", **default_values + ) + + def build_empty_foreign_key( + self, + table: PostgreSQLTable, + columns: list[str], + /, + name: Optional[str] = None, + **default_values, + ) -> PostgreSQLForeignKey: id = PostgreSQLContext.get_temporary_id(table.foreign_keys) if name is None: @@ -492,20 +658,24 @@ def build_empty_foreign_key(self, table: PostgreSQLTable, columns: list[str], /, name=name, table=table, columns=columns, - reference_table="", - reference_columns=[], - on_update="NO ACTION", - on_delete="NO ACTION", + reference_table=default_values.get("reference_table", ""), + reference_columns=default_values.get("reference_columns", []), + on_update=default_values.get("on_update", "NO ACTION"), + on_delete=default_values.get("on_delete", "NO ACTION"), ) - def build_empty_record(self, table: PostgreSQLTable, /, *, values: dict[str, Any]) -> PostgreSQLRecord: + def build_empty_record( + self, table: PostgreSQLTable, /, *, values: dict[str, Any] + ) -> PostgreSQLRecord: return PostgreSQLRecord( id=PostgreSQLContext.get_temporary_id(table.records), table=table, - values=values + values=values, ) - def build_empty_view(self, database: SQLDatabase, /, name: Optional[str] = None, **default_values) -> PostgreSQLView: + def build_empty_view( + self, database: SQLDatabase, /, name: Optional[str] = None, **default_values + ) -> PostgreSQLView: id = PostgreSQLContext.get_temporary_id(database.views) if name is None: @@ -515,18 +685,60 @@ def build_empty_view(self, database: SQLDatabase, /, name: Optional[str] = None, id=id, name=name, database=database, - sql=default_values.get("sql", ""), + statement=default_values.get("statement", ""), + ) + + def build_empty_function( + self, database: SQLDatabase, /, name: Optional[str] = None, **default_values + ) -> "PostgreSQLFunction": + from structures.engines.postgresql.database import PostgreSQLFunction + + id = PostgreSQLContext.get_temporary_id(database.functions) + + if name is None: + name = f"function_{id}" + + return PostgreSQLFunction( + id=id, + name=name, + database=database, + parameters=default_values.get("parameters", ""), + returns=default_values.get("returns", "void"), + language=default_values.get("language", "plpgsql"), + volatility=default_values.get("volatility", "VOLATILE"), + statement=default_values.get("statement", ""), + ) + + def build_empty_procedure( + self, database: SQLDatabase, /, name: Optional[str] = None, **default_values + ) -> "PostgreSQLProcedure": + from structures.engines.postgresql.database import PostgreSQLProcedure + + id = PostgreSQLContext.get_temporary_id(database.procedures) + + if name is None: + name = f"procedure_{id}" + + return PostgreSQLProcedure( + id=id, + name=name, + database=database, + parameters=default_values.get("parameters", ""), + language=default_values.get("language", "plpgsql"), + statement=default_values.get("statement", ""), ) - def build_empty_trigger(self, database: SQLDatabase, /, name: Optional[str] = None, **default_values) -> PostgreSQLTrigger: + def build_empty_trigger( + self, database: SQLDatabase, /, name: Optional[str] = None, **default_values + ) -> PostgreSQLTrigger: id = PostgreSQLContext.get_temporary_id(database.triggers) if name is None: - name = _(f"Trigger{str(id * -1):03}") + name = f"trigger_{id}" return PostgreSQLTrigger( id=id, name=name, database=database, - sql=default_values.get("sql", ""), + statement=default_values.get("statement", ""), ) diff --git a/structures/engines/postgresql/database.py b/structures/engines/postgresql/database.py index 4979a8a..c320356 100644 --- a/structures/engines/postgresql/database.py +++ b/structures/engines/postgresql/database.py @@ -5,7 +5,7 @@ from structures.helpers import merge_original_current from structures.engines.context import QUERY_LOGS -from structures.engines.database import SQLTable, SQLColumn, SQLIndex, SQLForeignKey, SQLRecord, SQLView, SQLTrigger, SQLDatabase +from structures.engines.database import SQLTable, SQLColumn, SQLIndex, SQLForeignKey, SQLFunction, SQLRecord, SQLView, SQLTrigger, SQLDatabase, SQLCheck, SQLProcedure from structures.engines.postgresql.indextype import PostgreSQLIndexType from structures.engines.postgresql.builder import PostgreSQLColumnBuilder, PostgreSQLIndexBuilder @@ -19,29 +19,41 @@ class PostgreSQLDatabase(SQLDatabase): @dataclasses.dataclass(eq=False) class PostgreSQLTable(SQLTable): schema: str = None + + @property + def fully_qualified_name(self): + schema_or_db = self.schema if self.schema else self.database.name + return self.database.context.qualify(schema_or_db, self.name) + def raw_create(self) -> str: columns = [str(PostgreSQLColumnBuilder(column)) for column in self.columns] - indexes = [str(PostgreSQLIndexBuilder(index)) for index in self.indexes] + # Only PRIMARY KEY constraints can be inline in CREATE TABLE + # Other indexes must be created separately with CREATE INDEX + inline_constraints = [] + for index in self.indexes: + if index.type.name == "PRIMARY": + inline_constraints.append(str(PostgreSQLIndexBuilder(index, inline=True))) - columns_and_indexes = columns + indexes + columns_and_constraints = columns + inline_constraints return f""" - CREATE TABLE "{self.database.name}"."{self.name}" ( - {', '.join(columns_and_indexes)} + CREATE TABLE {self.fully_qualified_name} ( + {', '.join(columns_and_constraints)} ); """ def rename(self, table: Self, new_name: str) -> bool: - sql = f'ALTER TABLE "{self.database.name}"."{table.name}" RENAME TO "{new_name}";' - self.database.context.execute(sql) + new_name_quoted = self.database.context.quote_identifier(new_name) + statement = f'ALTER TABLE {table.fully_qualified_name} RENAME TO {new_name_quoted};' + self.database.context.execute(statement) return True def truncate(self) -> bool: try: with self.database.context.transaction() as context: - context.execute(f'TRUNCATE TABLE "{self.database.name}"."{self.name}";') + context.execute(f'TRUNCATE TABLE {self.fully_qualified_name};') except Exception as ex: logger.error(ex, exc_info=True) @@ -52,8 +64,10 @@ def create(self) -> bool: with self.database.context.transaction() as transaction: transaction.execute(self.raw_create()) + # Only create non-PRIMARY indexes separately (PRIMARY is already inline in CREATE TABLE) for index in self.indexes: - index.create() + if index.type.name != "PRIMARY": + index.create() for foreign_key in self.foreign_keys: foreign_key.create() @@ -79,20 +93,24 @@ def alter(self) -> bool: try: with self.database.context.transaction() as transaction: if self.name != original_table.name: - transaction.execute(f'ALTER TABLE "{original_table.database.name}"."{original_table.name}" RENAME TO "{self.name}";') + new_name_quoted = self.database.context.quote_identifier(self.name) + transaction.execute(f'ALTER TABLE {original_table.fully_qualified_name} RENAME TO {new_name_quoted};') # Handle column changes for column in map_columns['added']: - transaction.execute(f'ALTER TABLE "{self.database.name}"."{self.name}" ADD COLUMN {str(PostgreSQLColumnBuilder(column))};') + transaction.execute(f'ALTER TABLE {self.fully_qualified_name} ADD COLUMN {str(PostgreSQLColumnBuilder(column))};') for column in map_columns['removed']: - transaction.execute(f'ALTER TABLE "{self.database.name}"."{self.name}" DROP COLUMN "{column.name}";') + col_name_quoted = self.database.context.quote_identifier(column.name) + transaction.execute(f'ALTER TABLE {self.fully_qualified_name} DROP COLUMN {col_name_quoted};') for column in map_columns['modified']: original_column = column['original'] current_column = column['current'] if original_column.name != current_column.name: - transaction.execute(f'ALTER TABLE "{self.database.name}"."{self.name}" RENAME COLUMN "{original_column.name}" TO "{current_column.name}";') + old_name_quoted = self.database.context.quote_identifier(original_column.name) + new_name_quoted = self.database.context.quote_identifier(current_column.name) + transaction.execute(f'ALTER TABLE {self.fully_qualified_name} RENAME COLUMN {old_name_quoted} TO {new_name_quoted};') # For other changes, might need more complex ALTER statements # Handle index changes @@ -119,26 +137,99 @@ def alter(self) -> bool: return True def drop(self) -> bool: - return self.database.context.execute(f'DROP TABLE "{self.schema}"."{self.name}"') + return self.database.context.execute(f'DROP TABLE {self.fully_qualified_name}') + + +@dataclasses.dataclass(eq=False) +class PostgreSQLCheck(SQLCheck): + def create(self) -> bool: + statement = f'ALTER TABLE {self.table.fully_qualified_name} ADD CONSTRAINT {self.quoted_name} CHECK ({self.expression})' + return self.table.database.context.execute(statement) + + def drop(self) -> bool: + statement = f'ALTER TABLE {self.table.fully_qualified_name} DROP CONSTRAINT {self.quoted_name}' + return self.table.database.context.execute(statement) + + def alter(self) -> bool: + self.drop() + return self.create() @dataclasses.dataclass(eq=False) class PostgreSQLColumn(SQLColumn): - pass + def add(self) -> bool: + statement = f'ALTER TABLE {self.table.fully_qualified_name} ADD COLUMN {str(PostgreSQLColumnBuilder(self))};' + return self.table.database.context.execute(statement) + def rename(self, new_name: str) -> bool: + old_name_quoted = self.table.database.context.quote_identifier(self.name) + new_name_quoted = self.table.database.context.quote_identifier(new_name) + statement = f'ALTER TABLE {self.table.fully_qualified_name} RENAME COLUMN {old_name_quoted} TO {new_name_quoted};' + return self.table.database.context.execute(statement) -@dataclasses.dataclass + def drop(self) -> bool: + col_name_quoted = self.table.database.context.quote_identifier(self.name) + statement = f'ALTER TABLE {self.table.fully_qualified_name} DROP COLUMN {col_name_quoted};' + return self.table.database.context.execute(statement) + + def modify(self, current: Self): + statements = [] + col_name_quoted = self.table.database.context.quote_identifier(current.name) + table_name = self.table.fully_qualified_name + + if self.name != current.name: + old_name_quoted = self.table.database.context.quote_identifier(self.name) + statements.append(f'ALTER TABLE {table_name} RENAME COLUMN {old_name_quoted} TO {col_name_quoted}') + + type_changed = (self.datatype != current.datatype or + self.length != current.length or + self.numeric_precision != current.numeric_precision) + if type_changed: + datatype_str = str(current.datatype.name) + if current.datatype.has_length and current.length: + datatype_str += f"({current.length})" + elif current.datatype.has_precision: + if current.datatype.has_scale and current.numeric_scale: + datatype_str += f"({current.numeric_precision},{current.numeric_scale})" + elif current.numeric_precision: + datatype_str += f"({current.numeric_precision})" + + statements.append(f'ALTER TABLE {table_name} ALTER COLUMN {col_name_quoted} TYPE {datatype_str}') + + if self.is_nullable != current.is_nullable: + if current.is_nullable: + statements.append(f'ALTER TABLE {table_name} ALTER COLUMN {col_name_quoted} DROP NOT NULL') + else: + statements.append(f'ALTER TABLE {table_name} ALTER COLUMN {col_name_quoted} SET NOT NULL') + + if self.server_default != current.server_default: + if current.server_default: + default_stmt = f'ALTER TABLE {table_name} ALTER COLUMN {col_name_quoted} SET DEFAULT {current.server_default}' + statements.append(default_stmt) + else: + statements.append(f'ALTER TABLE {table_name} ALTER COLUMN {col_name_quoted} DROP DEFAULT') + + for stmt in statements: + self.table.database.context.execute(stmt) + + return True + + +@dataclasses.dataclass(eq=False) class PostgreSQLIndex(SQLIndex): def create(self) -> bool: - sql = str(PostgreSQLIndexBuilder(self)) - return self.table.database.context.execute(sql) + statement = str(PostgreSQLIndexBuilder(self, inline=False)) + return self.table.database.context.execute(statement) def drop(self) -> bool: if self.type.name == "PRIMARY": - sql = f'ALTER TABLE "{self.table.database.name}"."{self.table.name}" DROP CONSTRAINT "{self.name}";' + constraint_name_quoted = self.table.database.context.quote_identifier(self.name) + statement = f'ALTER TABLE {self.table.fully_qualified_name} DROP CONSTRAINT {constraint_name_quoted};' else: - sql = f'DROP INDEX IF EXISTS "{self.table.database.name}"."{self.name}";' - return self.table.database.context.execute(sql) + schema_or_db = self.table.schema if self.table.schema else self.table.database.name + index_fqn = self.table.database.context.qualify(schema_or_db, self.name) + statement = f'DROP INDEX IF EXISTS {index_fqn};' + return self.table.database.context.execute(statement) def alter(self, original_index: Self) -> bool: self.drop() @@ -148,18 +239,20 @@ def alter(self, original_index: Self) -> bool: @dataclasses.dataclass class PostgreSQLForeignKey(SQLForeignKey): def create(self) -> bool: - columns = ", ".join(f'"{col}"' for col in self.columns) - ref_columns = ", ".join(f'"{col}"' for col in self.reference_columns) - sql = f'ALTER TABLE "{self.table.schema}"."{self.table.name}" ADD CONSTRAINT "{self.name}" FOREIGN KEY ({columns}) REFERENCES {self.reference_table} ({ref_columns})' + columns = ", ".join(self.table.database.context.quote_identifier(col) for col in self.columns) + ref_columns = ", ".join(self.table.database.context.quote_identifier(col) for col in self.reference_columns) + constraint_name_quoted = self.table.database.context.quote_identifier(self.name) + statement = f'ALTER TABLE {self.table.fully_qualified_name} ADD CONSTRAINT {constraint_name_quoted} FOREIGN KEY ({columns}) REFERENCES {self.reference_table} ({ref_columns})' if self.on_update and self.on_update != "NO ACTION": - sql += f" ON UPDATE {self.on_update}" + statement += f" ON UPDATE {self.on_update}" if self.on_delete and self.on_delete != "NO ACTION": - sql += f" ON DELETE {self.on_delete}" - return self.table.database.context.execute(sql) + statement += f" ON DELETE {self.on_delete}" + return self.table.database.context.execute(statement) def drop(self) -> bool: - sql = f'ALTER TABLE "{self.table.schema}"."{self.table.name}" DROP CONSTRAINT "{self.name}"' - return self.table.database.context.execute(sql) + constraint_name_quoted = self.table.database.context.quote_identifier(self.name) + statement = f'ALTER TABLE {self.table.fully_qualified_name} DROP CONSTRAINT {constraint_name_quoted}' + return self.table.database.context.execute(statement) def modify(self, new: "PostgreSQLForeignKey") -> None: self.drop() @@ -168,46 +261,191 @@ def modify(self, new: "PostgreSQLForeignKey") -> None: @dataclasses.dataclass class PostgreSQLRecord(SQLRecord): + def raw_insert_record(self) -> str: + columns_values = {} + + for column in self.table.columns: + if hasattr(column, 'virtuality') and column.virtuality is not None: + continue + + value = self.values.get(column.name) + if value is not None and str(value).strip(): + if column.datatype.format: + value = column.datatype.format(value) + + columns_values[self.table.database.context.quote_identifier(column.name)] = str(value) + + if not columns_values: + raise AssertionError("No columns values") + + return f"INSERT INTO {self.table.fully_qualified_name} ({', '.join(columns_values.keys())}) VALUES ({', '.join(columns_values.values())})" + + def raw_update_record(self) -> Optional[str]: + identifier_columns = self._get_identifier_columns() + + identifier_conditions = " AND ".join([f"{self.table.database.context.quote_identifier(identifier_name)} = {identifier_value}" for identifier_name, identifier_value in identifier_columns.items()]) + + sql_select = f"SELECT * FROM {self.table.fully_qualified_name} WHERE {identifier_conditions}" + self.table.database.context.execute(sql_select) + + if not (existing_record := self.table.database.context.fetchone()): + logger.warning(f"Record not found for update: {identifier_columns}") + raise AssertionError("Record not found for update with identifier columns") + + changed_columns = [] + + for col_name, new_value in self.values.items(): + column: SQLColumn = next((c for c in self.table.columns if c.name == col_name), None) + existing_value = dict(existing_record).get(col_name) + if (new_value or "") != (existing_value or ""): + col_quoted = self.table.database.context.quote_identifier(col_name) + if new_value is None: + changed_columns.append(f"{col_quoted} = NULL") + elif column.datatype.format: + changed_columns.append(f"{col_quoted} = {column.datatype.format(new_value)}") + else: + changed_columns.append(f"{col_quoted} = {new_value}") + + if not changed_columns: + return None + + set_clause = ", ".join(changed_columns) + + return f"UPDATE {self.table.fully_qualified_name} SET {set_clause} WHERE {identifier_conditions}" + + def raw_delete_record(self) -> str: + identifier_columns = self._get_identifier_columns() + + identifier_conditions = " AND ".join([f"{self.table.database.context.quote_identifier(identifier_name)} = {identifier_value}" for identifier_name, identifier_value in identifier_columns.items()]) + + return f"DELETE FROM {self.table.fully_qualified_name} WHERE {identifier_conditions}" + def insert(self) -> bool: - columns = ", ".join(f'"{k}"' for k in self.values.keys()) - placeholders = ", ".join("%s" for _ in self.values) - sql = f'INSERT INTO "{self.table.database.name}"."{self.table.name}" ({columns}) VALUES ({placeholders});' - return self.table.database.context.execute(sql, tuple(self.values.values())) + with self.table.database.context.transaction() as transaction: + if raw_insert_record := self.raw_insert_record(): + return transaction.execute(raw_insert_record) + + return False def update(self) -> bool: - set_clause = ", ".join(f'"{k}" = %s' for k in self.values.keys()) - sql = f'UPDATE "{self.table.database.name}"."{self.table.name}" SET {set_clause} WHERE id = %s;' - return self.table.database.context.execute(sql, tuple(self.values.values()) + (self.id,)) + with self.table.database.context.transaction() as transaction: + if raw_update_record := self.raw_update_record(): + return transaction.execute(raw_update_record) + + return False def delete(self) -> bool: - sql = f'DELETE FROM "{self.table.database.name}"."{self.table.name}" WHERE id = %s;' - return self.table.database.context.execute(sql, (self.id,)) + with self.table.database.context.transaction() as transaction: + if raw_delete_record := self.raw_delete_record(): + return transaction.execute(raw_delete_record) + + return False @dataclasses.dataclass class PostgreSQLView(SQLView): + @property + def fully_qualified_name(self): + return self.database.context.qualify('public', self.name) + def create(self) -> bool: - sql = f'CREATE VIEW "{self.database.name}"."{self.name}" AS {self.sql};' - return self.database.context.execute(sql) + statement = f'CREATE VIEW {self.fully_qualified_name} AS {self.statement};' + return self.database.context.execute(statement) + + def drop(self) -> bool: + statement = f'DROP VIEW IF EXISTS {self.fully_qualified_name};' + return self.database.context.execute(statement) + + def alter(self) -> bool: + statement = f'CREATE OR REPLACE VIEW {self.fully_qualified_name} AS {self.statement};' + return self.database.context.execute(statement) + +@dataclasses.dataclass +class PostgreSQLFunction(SQLFunction): + parameters: str = "" + returns: str = "" + language: str = "plpgsql" + volatility: str = "VOLATILE" + statement: str = "" + + @property + def fully_qualified_name(self): + return self.database.context.qualify('public', self.name) + + def create(self) -> bool: + create_statement = f""" + CREATE OR REPLACE FUNCTION {self.fully_qualified_name}({self.parameters}) + RETURNS {self.returns} + LANGUAGE {self.language} + {self.volatility} + AS $$ + BEGIN + {self.statement} + END; + $$; + """ + return self.database.context.execute(create_statement) + + def drop(self) -> bool: + statement = f'DROP FUNCTION IF EXISTS {self.fully_qualified_name}({self.parameters});' + return self.database.context.execute(statement) + def alter(self) -> bool: - sql = f'CREATE OR REPLACE VIEW "{self.database.name}"."{self.name}" AS {self.sql};' - return self.database.context.execute(sql) + self.drop() + return self.create() + +@dataclasses.dataclass +class PostgreSQLProcedure(SQLProcedure): + parameters: str = "" + language: str = "plpgsql" + statement: str = "" + + @property + def fully_qualified_name(self): + return self.database.context.qualify('public', self.name) + + def create(self) -> bool: + create_statement = f""" + CREATE OR REPLACE PROCEDURE {self.fully_qualified_name}({self.parameters}) + LANGUAGE {self.language} + AS $$ + BEGIN + {self.statement} + END; + $$; + """ + return self.database.context.execute(create_statement) + def drop(self) -> bool: - sql = f'DROP VIEW IF EXISTS "{self.database.name}"."{self.name}";' - return self.database.context.execute(sql) + statement = f'DROP PROCEDURE IF EXISTS {self.fully_qualified_name}({self.parameters});' + return self.database.context.execute(statement) + + def alter(self) -> bool: + self.drop() + return self.create() @dataclasses.dataclass class PostgreSQLTrigger(SQLTrigger): def create(self) -> bool: - return self.database.context.execute(self.sql) + return self.database.context.execute(self.statement) def alter(self) -> bool: self.drop() return self.create() def drop(self) -> bool: - sql = f'DROP TRIGGER IF EXISTS "{self.name}" ON "{self.database.name}";' - return self.database.context.execute(sql) + import re + + trigger_name_quoted = self.database.context.quote_identifier(self.name) + + match = re.search(r'CREATE\s+TRIGGER\s+\S+\s+.*?\s+ON\s+(\S+\.\S+|\S+)', self.statement, re.IGNORECASE | re.DOTALL) + if match: + table_name = match.group(1) + statement = f'DROP TRIGGER IF EXISTS {trigger_name_quoted} ON {table_name};' + else: + statement = f'DROP TRIGGER IF EXISTS {trigger_name_quoted};' + + return self.database.context.execute(statement) diff --git a/structures/engines/postgresql/specification.yaml b/structures/engines/postgresql/specification.yaml new file mode 100644 index 0000000..9416181 --- /dev/null +++ b/structures/engines/postgresql/specification.yaml @@ -0,0 +1,9497 @@ +schema_version: 1 +engine: postgresql +documentation: + purpose: PostgreSQL vocabulary and version deltas. + fields: + - schema_version: Specification schema version. + - engine: Engine name. + - common.keywords: Engine common keywords. + - common.functions: Engine common function specs. + - versions..keywords_remove: Keywords to remove for that major version. + - versions..functions_remove: Function names to remove for that major version. + notes: + - Runtime selection uses exact major match, else highest configured major <= server major. +example: + schema_version: 1 + engine: postgresql + common: + keywords: + - ILIKE + functions: + - name: ABS + summary: Absolute value. + versions: + '17': + functions_remove: + - LEGACY_ONLY_FUNCTION +common: + keywords: [] + functions: + - name: ABBREV + category_id: network_address_functions + category_label: Network Address Functions + signature: + display: ABBREV(inet) + args: + - name: inet + optional: false + type: any + tags: [] + aliases: [] + summary: Creates an abbreviated display format as text. + description: Creates an abbreviated display format as text. (The result is the + same as the inet output function produces; it is "abbreviated" only in comparison + to the result of an explicit cast to text, which for historical reasons will + never suppress the netmask part.) + examples: [] + - name: ABS + category_id: numeric_math_functions + category_label: Numeric/Math Functions + signature: + display: ABS(numeric_type) + args: + - name: numeric_type + optional: false + type: any + tags: [] + aliases: [] + summary: Absolute value + description: Absolute value + examples: [] + - name: ACLDEFAULT + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: ACLDEFAULT(type "char", ownerId oid) + args: + - name: type "char" + optional: false + type: any + - name: ownerId oid + optional: false + type: any + tags: [] + aliases: [] + summary: Constructs an aclitem array holding the default access privileges for + an + description: Constructs an aclitem array holding the default access privileges + for an object of type type belonging to the role with OID ownerId. This represents + the access privileges that will be assumed when an object's ACL entry is null. + (The default access privileges are described in Section 5.8.) The type parameter + must be one of 'c' for COLUMN, 'r' for TABLE and table-like objects, 's' for + SEQUENCE, 'd' for DATABASE, 'f' for FUNCTION or PROCEDURE, 'l' for LANGUAGE, + 'L' for LARGE OBJECT, 'n' for SCHEMA, 'p' for PARAMETER, 't' for TABLESPACE, + 'F' for FOREIGN DATA WRAPPER, 'S' for FOREIGN SERVER, or 'T' for TYPE or DOMAIN. + examples: [] + - name: ACLEXPLODE + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: ACLEXPLODE(aclitem[]) + args: + - name: aclitem[] + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the aclitem array as a set of rows. + description: Returns the aclitem array as a set of rows. If the grantee is the + pseudo-role PUBLIC, it is represented by zero in the grantee column. Each granted + privilege is represented as SELECT, INSERT, etc (see Table 5.1 for a full list). + Note that each privilege is broken out as a separate row, so only one keyword + appears in the privilege_type column. + examples: [] + - name: ACOS + category_id: numeric_math_functions + category_label: Numeric/Math Functions + signature: + display: ACOS(double precision) + args: + - name: double precision + optional: false + type: any + tags: [] + aliases: [] + summary: Inverse cosine, result in radians + description: Inverse cosine, result in radians + examples: [] + - name: ACOSD + category_id: numeric_math_functions + category_label: Numeric/Math Functions + signature: + display: ACOSD(double precision) + args: + - name: double precision + optional: false + type: any + tags: [] + aliases: [] + summary: Inverse cosine, result in degrees + description: Inverse cosine, result in degrees + examples: [] + - name: ACOSH + category_id: numeric_math_functions + category_label: Numeric/Math Functions + signature: + display: ACOSH(double precision) + args: + - name: double precision + optional: false + type: any + tags: [] + aliases: [] + summary: Inverse hyperbolic cosine + description: Inverse hyperbolic cosine + examples: [] + - name: AGE1 + category_id: date_time_functions + category_label: Date/Time Functions + signature: + display: AGE1(timestamp, timestamp) + args: + - name: timestamp + optional: false + type: any + - name: timestamp + optional: false + type: any + tags: [] + aliases: [] + summary: Subtract arguments, producing a "symbolic" result that uses years and + description: Subtract arguments, producing a "symbolic" result that uses years + and months, rather than just days + examples: [] + - name: AGE2 + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: AGE2(xid) + args: + - name: xid + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the number of transactions between the supplied transaction id + and + description: Returns the number of transactions between the supplied transaction + id and the current transaction counter. + examples: [] + - name: ANY_VALUE + category_id: aggregate_functions + category_label: Aggregate Functions + signature: + display: ANY_VALUE(anyelement) + args: + - name: anyelement + optional: false + type: any + tags: [] + aliases: [] + summary: Returns an arbitrary value from the non-null input values. + description: Returns an arbitrary value from the non-null input values. + examples: [] + - name: AREA + category_id: geometric_functions + category_label: Geometric Functions + signature: + display: AREA(geometric_type) + args: + - name: geometric_type + optional: false + type: any + tags: [] + aliases: [] + summary: Computes area. + description: Computes area. Available for box, path, circle. A path input must + be closed, else NULL is returned. Also, if the path is self-intersecting, the + result may be meaningless. + examples: [] + - name: ARRAY_AGG + category_id: aggregate_functions + category_label: Aggregate Functions + signature: + display: ARRAY_AGG(anynonarray ORDER BY input_sort_columns) + args: + - name: anynonarray ORDER BY input_sort_columns + optional: false + type: any + tags: [] + aliases: [] + summary: Collects all the input values, including nulls, into an array. + description: Collects all the input values, including nulls, into an array. + examples: [] + - name: ARRAY_APPEND + category_id: array_functions + category_label: Array Functions + signature: + display: ARRAY_APPEND(anycompatiblearray, anycompatible) + args: + - name: anycompatiblearray + optional: false + type: any + - name: anycompatible + optional: false + type: any + tags: [] + aliases: [] + summary: Appends an element to the end of an array (same as the anycompatiblearray + description: Appends an element to the end of an array (same as the anycompatiblearray + || anycompatible operator). + examples: [] + - name: ARRAY_CAT + category_id: array_functions + category_label: Array Functions + signature: + display: ARRAY_CAT(anycompatiblearray, anycompatiblearray) + args: + - name: anycompatiblearray + optional: false + type: any + - name: anycompatiblearray + optional: false + type: any + tags: [] + aliases: [] + summary: Concatenates two arrays (same as the anycompatiblearray || + description: Concatenates two arrays (same as the anycompatiblearray || anycompatiblearray + operator). + examples: [] + - name: ARRAY_DIMS + category_id: array_functions + category_label: Array Functions + signature: + display: ARRAY_DIMS(anyarray) + args: + - name: anyarray + optional: false + type: any + tags: [] + aliases: [] + summary: Returns a text representation of the array's dimensions. + description: Returns a text representation of the array's dimensions. + examples: [] + - name: ARRAY_FILL + category_id: array_functions + category_label: Array Functions + signature: + display: ARRAY_FILL(anyelement, integer[] [, integer[] ]) + args: + - name: anyelement + optional: false + type: any + - name: integer[] [ + optional: false + type: any + - name: integer[] ] + optional: false + type: any + tags: [] + aliases: [] + summary: Returns an array filled with copies of the given value, having dimensions + description: Returns an array filled with copies of the given value, having dimensions + of the lengths specified by the second argument. The optional third argument + supplies lower-bound values for each dimension (which default to all 1). + examples: [] + - name: ARRAY_LENGTH + category_id: array_functions + category_label: Array Functions + signature: + display: ARRAY_LENGTH(anyarray, integer) + args: + - name: anyarray + optional: false + type: any + - name: integer + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the length of the requested array dimension. + description: Returns the length of the requested array dimension. (Produces NULL + instead of 0 for empty or missing array dimensions.) + examples: [] + - name: ARRAY_LOWER + category_id: array_functions + category_label: Array Functions + signature: + display: ARRAY_LOWER(anyarray, integer) + args: + - name: anyarray + optional: false + type: any + - name: integer + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the lower bound of the requested array dimension. + description: Returns the lower bound of the requested array dimension. + examples: [] + - name: ARRAY_NDIMS + category_id: array_functions + category_label: Array Functions + signature: + display: ARRAY_NDIMS(anyarray) + args: + - name: anyarray + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the number of dimensions of the array. + description: Returns the number of dimensions of the array. + examples: [] + - name: ARRAY_POSITION + category_id: array_functions + category_label: Array Functions + signature: + display: ARRAY_POSITION(anycompatiblearray, anycompatible [, integer ]) + args: + - name: anycompatiblearray + optional: false + type: any + - name: anycompatible [ + optional: false + type: any + - name: integer ] + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the subscript of the first occurrence of the second argument + in the + description: Returns the subscript of the first occurrence of the second argument + in the array, or NULL if it's not present. If the third argument is given, the + search begins at that subscript. The array must be one-dimensional. Comparisons + are done using IS NOT DISTINCT FROM semantics, so it is possible to search for + NULL. + examples: [] + - name: ARRAY_POSITIONS + category_id: array_functions + category_label: Array Functions + signature: + display: ARRAY_POSITIONS(anycompatiblearray, anycompatible) + args: + - name: anycompatiblearray + optional: false + type: any + - name: anycompatible + optional: false + type: any + tags: [] + aliases: [] + summary: Returns an array of the subscripts of all occurrences of the second + description: Returns an array of the subscripts of all occurrences of the second + argument in the array given as first argument. The array must be one-dimensional. + Comparisons are done using IS NOT DISTINCT FROM semantics, so it is possible + to search for NULL. NULL is returned only if the array is NULL; if the value + is not found in the array, an empty array is returned. + examples: [] + - name: ARRAY_PREPEND + category_id: array_functions + category_label: Array Functions + signature: + display: ARRAY_PREPEND(anycompatible, anycompatiblearray) + args: + - name: anycompatible + optional: false + type: any + - name: anycompatiblearray + optional: false + type: any + tags: [] + aliases: [] + summary: Prepends an element to the beginning of an array (same as the anycompatible + description: Prepends an element to the beginning of an array (same as the anycompatible + || anycompatiblearray operator). + examples: [] + - name: ARRAY_REMOVE + category_id: array_functions + category_label: Array Functions + signature: + display: ARRAY_REMOVE(anycompatiblearray, anycompatible) + args: + - name: anycompatiblearray + optional: false + type: any + - name: anycompatible + optional: false + type: any + tags: [] + aliases: [] + summary: Removes all elements equal to the given value from the array. + description: Removes all elements equal to the given value from the array. The + array must be one-dimensional. Comparisons are done using IS NOT DISTINCT FROM + semantics, so it is possible to remove NULLs. + examples: [] + - name: ARRAY_REPLACE + category_id: array_functions + category_label: Array Functions + signature: + display: ARRAY_REPLACE(anycompatiblearray, anycompatible, anycompatible) + args: + - name: anycompatiblearray + optional: false + type: any + - name: anycompatible + optional: false + type: any + - name: anycompatible + optional: false + type: any + tags: [] + aliases: [] + summary: Replaces each array element equal to the second argument with the third + description: Replaces each array element equal to the second argument with the + third argument. + examples: [] + - name: ARRAY_SAMPLE + category_id: array_functions + category_label: Array Functions + signature: + display: ARRAY_SAMPLE(array anyarray, n integer) + args: + - name: array anyarray + optional: false + type: any + - name: n integer + optional: false + type: any + tags: [] + aliases: [] + summary: Returns an array of n items randomly selected from array. + description: Returns an array of n items randomly selected from array. n may not + exceed the length of array's first dimension. If array is multi-dimensional, + an "item" is a slice having a given first subscript. + examples: [] + - name: ARRAY_SHUFFLE + category_id: array_functions + category_label: Array Functions + signature: + display: ARRAY_SHUFFLE(anyarray) + args: + - name: anyarray + optional: false + type: any + tags: [] + aliases: [] + summary: Randomly shuffles the first dimension of the array. + description: Randomly shuffles the first dimension of the array. + examples: [] + - name: ARRAY_TO_JSON + category_id: json_functions + category_label: JSON Functions + signature: + display: ARRAY_TO_JSON(anyarray [, boolean ]) + args: + - name: anyarray [ + optional: false + type: any + - name: boolean ] + optional: false + type: any + tags: [] + aliases: [] + summary: Converts an SQL array to a JSON array. + description: Converts an SQL array to a JSON array. The behavior is the same as + to_json except that line feeds will be added between top-level array elements + if the optional boolean parameter is true. + examples: [] + - name: ARRAY_TO_STRING + category_id: array_functions + category_label: Array Functions + signature: + display: ARRAY_TO_STRING(array anyarray, delimiter text [, null_string text + ]) + args: + - name: array anyarray + optional: false + type: any + - name: delimiter text [ + optional: false + type: any + - name: null_string text ] + optional: false + type: any + tags: [] + aliases: [] + summary: Converts each array element to its text representation, and concatenates + description: Converts each array element to its text representation, and concatenates + those separated by the delimiter string. If null_string is given and is not + NULL, then NULL array entries are represented by that string; otherwise, they + are omitted. See also string_to_array. + examples: [] + - name: ARRAY_TO_TSVECTOR + category_id: text_search_functions + category_label: Text Search Functions + signature: + display: ARRAY_TO_TSVECTOR(text[]) + args: + - name: text[] + optional: false + type: any + tags: [] + aliases: [] + summary: Converts an array of text strings to a tsvector. + description: Converts an array of text strings to a tsvector. The given strings + are used as lexemes as-is, without further processing. Array elements must not + be empty strings or NULL. + examples: [] + - name: ARRAY_UPPER + category_id: array_functions + category_label: Array Functions + signature: + display: ARRAY_UPPER(anyarray, integer) + args: + - name: anyarray + optional: false + type: any + - name: integer + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the upper bound of the requested array dimension. + description: Returns the upper bound of the requested array dimension. + examples: [] + - name: ASCII + category_id: string_functions + category_label: String Functions + signature: + display: ASCII(text) + args: + - name: text + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the numeric code of the first character of the argument. + description: Returns the numeric code of the first character of the argument. + In UTF8 encoding, returns the Unicode code point of the character. In other + multibyte encodings, the argument must be an ASCII character. + examples: [] + - name: ASIN + category_id: numeric_math_functions + category_label: Numeric/Math Functions + signature: + display: ASIN(double precision) + args: + - name: double precision + optional: false + type: any + tags: [] + aliases: [] + summary: Inverse sine, result in radians + description: Inverse sine, result in radians + examples: [] + - name: ASIND + category_id: numeric_math_functions + category_label: Numeric/Math Functions + signature: + display: ASIND(double precision) + args: + - name: double precision + optional: false + type: any + tags: [] + aliases: [] + summary: Inverse sine, result in degrees + description: Inverse sine, result in degrees + examples: [] + - name: ASINH + category_id: numeric_math_functions + category_label: Numeric/Math Functions + signature: + display: ASINH(double precision) + args: + - name: double precision + optional: false + type: any + tags: [] + aliases: [] + summary: Inverse hyperbolic sine + description: Inverse hyperbolic sine + examples: [] + - name: ATAN + category_id: numeric_math_functions + category_label: Numeric/Math Functions + signature: + display: ATAN(double precision) + args: + - name: double precision + optional: false + type: any + tags: [] + aliases: [] + summary: Inverse tangent, result in radians + description: Inverse tangent, result in radians + examples: [] + - name: ATAN2 + category_id: numeric_math_functions + category_label: Numeric/Math Functions + signature: + display: ATAN2(y double precision, x double precision) + args: + - name: y double precision + optional: false + type: any + - name: x double precision + optional: false + type: any + tags: [] + aliases: [] + summary: Inverse tangent of y/x, result in radians + description: Inverse tangent of y/x, result in radians + examples: [] + - name: ATAN2D + category_id: numeric_math_functions + category_label: Numeric/Math Functions + signature: + display: ATAN2D(y double precision, x double precision) + args: + - name: y double precision + optional: false + type: any + - name: x double precision + optional: false + type: any + tags: [] + aliases: [] + summary: Inverse tangent of y/x, result in degrees + description: Inverse tangent of y/x, result in degrees + examples: [] + - name: ATAND + category_id: numeric_math_functions + category_label: Numeric/Math Functions + signature: + display: ATAND(double precision) + args: + - name: double precision + optional: false + type: any + tags: [] + aliases: [] + summary: Inverse tangent, result in degrees + description: Inverse tangent, result in degrees + examples: [] + - name: ATANH + category_id: numeric_math_functions + category_label: Numeric/Math Functions + signature: + display: ATANH(double precision) + args: + - name: double precision + optional: false + type: any + tags: [] + aliases: [] + summary: Inverse hyperbolic tangent + description: Inverse hyperbolic tangent + examples: [] + - name: BIT_COUNT + category_id: bit_string_functions + category_label: Bit String Functions + signature: + display: BIT_COUNT(bit) + args: + - name: bit + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the number of bits set in the bit string (also known as + description: Returns the number of bits set in the bit string (also known as "popcount"). + examples: [] + - name: BIT_LENGTH1 + category_id: string_functions + category_label: String Functions + signature: + display: BIT_LENGTH1(text) + args: + - name: text + optional: false + type: any + tags: [] + aliases: [] + summary: Returns number of bits in the string (8 times the octet_length). + description: Returns number of bits in the string (8 times the octet_length). + examples: [] + - name: BIT_LENGTH2 + category_id: binary_string_functions + category_label: Binary String Functions + signature: + display: BIT_LENGTH2(bytea) + args: + - name: bytea + optional: false + type: any + tags: [] + aliases: [] + summary: Returns number of bits in the binary string (8 times the octet_length). + description: Returns number of bits in the binary string (8 times the octet_length). + examples: [] + - name: BIT_LENGTH3 + category_id: bit_string_functions + category_label: Bit String Functions + signature: + display: BIT_LENGTH3(bit) + args: + - name: bit + optional: false + type: any + tags: [] + aliases: [] + summary: Returns number of bits in the bit string. + description: Returns number of bits in the bit string. + examples: [] + - name: BOOL_AND + category_id: aggregate_functions + category_label: Aggregate Functions + signature: + display: BOOL_AND(boolean) + args: + - name: boolean + optional: false + type: any + tags: [] + aliases: [] + summary: Returns true if all non-null input values are true, otherwise false. + description: Returns true if all non-null input values are true, otherwise false. + examples: [] + - name: BOOL_OR + category_id: aggregate_functions + category_label: Aggregate Functions + signature: + display: BOOL_OR(boolean) + args: + - name: boolean + optional: false + type: any + tags: [] + aliases: [] + summary: Returns true if any non-null input value is true, otherwise false. + description: Returns true if any non-null input value is true, otherwise false. + examples: [] + - name: BOUND_BOX + category_id: geometric_functions + category_label: Geometric Functions + signature: + display: BOUND_BOX(box, box) + args: + - name: box + optional: false + type: any + - name: box + optional: false + type: any + tags: [] + aliases: [] + summary: Computes bounding box of two boxes. + description: Computes bounding box of two boxes. + examples: [] + - name: BOX + category_id: geometric_functions + category_label: Geometric Functions + signature: + display: BOX(circle) + args: + - name: circle + optional: false + type: any + tags: [] + aliases: [] + summary: Computes box inscribed within the circle. + description: Computes box inscribed within the circle. + examples: [] + - name: BRIN_DESUMMARIZE_RANGE + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: BRIN_DESUMMARIZE_RANGE(index regclass, blockNumber bigint) + args: + - name: index regclass + optional: false + type: any + - name: blockNumber bigint + optional: false + type: any + tags: [] + aliases: [] + summary: Removes the BRIN index tuple that summarizes the page range covering + the + description: Removes the BRIN index tuple that summarizes the page range covering + the given table block, if there is one. + examples: [] + - name: BRIN_SUMMARIZE_NEW_VALUES + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: BRIN_SUMMARIZE_NEW_VALUES(index regclass) + args: + - name: index regclass + optional: false + type: any + tags: [] + aliases: [] + summary: Scans the specified BRIN index to find page ranges in the base table + that + description: Scans the specified BRIN index to find page ranges in the base table + that are not currently summarized by the index; for any such range it creates + a new summary index tuple by scanning those table pages. Returns the number + of new page range summaries that were inserted into the index. + examples: [] + - name: BRIN_SUMMARIZE_RANGE + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: BRIN_SUMMARIZE_RANGE(index regclass, blockNumber bigint) + args: + - name: index regclass + optional: false + type: any + - name: blockNumber bigint + optional: false + type: any + tags: [] + aliases: [] + summary: Summarizes the page range covering the given block, if not already + description: Summarizes the page range covering the given block, if not already + summarized. This is like brin_summarize_new_values except that it only processes + the page range that covers the given table block number. + examples: [] + - name: BROADCAST + category_id: network_address_functions + category_label: Network Address Functions + signature: + display: BROADCAST(inet) + args: + - name: inet + optional: false + type: any + tags: [] + aliases: [] + summary: Computes the broadcast address for the address's network. + description: Computes the broadcast address for the address's network. + examples: [] + - name: BTRIM1 + category_id: string_functions + category_label: String Functions + signature: + display: BTRIM1(string text [, characters text ]) + args: + - name: string text [ + optional: false + type: any + - name: characters text ] + optional: false + type: any + tags: [] + aliases: [] + summary: Removes the longest string containing only characters in characters (a + description: Removes the longest string containing only characters in characters + (a space by default) from the start and end of string. + examples: [] + - name: BTRIM2 + category_id: binary_string_functions + category_label: Binary String Functions + signature: + display: BTRIM2(bytes bytea, bytesremoved bytea) + args: + - name: bytes bytea + optional: false + type: any + - name: bytesremoved bytea + optional: false + type: any + tags: [] + aliases: [] + summary: Removes the longest string containing only bytes appearing in bytesremoved + description: Removes the longest string containing only bytes appearing in bytesremoved + from the start and end of bytes. + examples: [] + - name: CARDINALITY + category_id: array_functions + category_label: Array Functions + signature: + display: CARDINALITY(anyarray) + args: + - name: anyarray + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the total number of elements in the array, or 0 if the array + is + description: Returns the total number of elements in the array, or 0 if the array + is empty. + examples: [] + - name: CBRT + category_id: numeric_math_functions + category_label: Numeric/Math Functions + signature: + display: CBRT(double precision) + args: + - name: double precision + optional: false + type: any + tags: [] + aliases: [] + summary: Cube root + description: Cube root + examples: [] + - name: CENTER + category_id: geometric_functions + category_label: Geometric Functions + signature: + display: CENTER(geometric_type) + args: + - name: geometric_type + optional: false + type: any + tags: [] + aliases: [] + summary: Computes center point. + description: Computes center point. Available for box, circle. + examples: [] + - name: CHARACTER_LENGTH + category_id: string_functions + category_label: String Functions + signature: + display: CHARACTER_LENGTH(text) + args: + - name: text + optional: false + type: any + tags: [] + aliases: [] + summary: Returns number of characters in the string. + description: Returns number of characters in the string. + examples: [] + - name: CHR + category_id: string_functions + category_label: String Functions + signature: + display: CHR(integer) + args: + - name: integer + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the character with the given code. + description: Returns the character with the given code. In UTF8 encoding the argument + is treated as a Unicode code point. In other multibyte encodings the argument + must designate an ASCII character. chr(0) is disallowed because text data types + cannot store that character. + examples: [] + - name: CIRCLE + category_id: geometric_functions + category_label: Geometric Functions + signature: + display: CIRCLE(box) + args: + - name: box + optional: false + type: any + tags: [] + aliases: [] + summary: Computes smallest circle enclosing box. + description: Computes smallest circle enclosing box. + examples: [] + - name: CLOCK_TIMESTAMP + category_id: date_time_functions + category_label: Date/Time Functions + signature: + display: CLOCK_TIMESTAMP + args: [] + tags: [] + aliases: [] + summary: Current date and time (changes during statement execution); see Section + description: Current date and time (changes during statement execution); see Section + 9.9.5 + examples: [] + - name: COL_DESCRIPTION + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: COL_DESCRIPTION(table oid, column integer) + args: + - name: table oid + optional: false + type: any + - name: column integer + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the comment for a table column, which is specified by the OID + of + description: Returns the comment for a table column, which is specified by the + OID of its table and its column number. (obj_description cannot be used for + table columns, since columns do not have OIDs of their own.) + examples: [] + - name: CONCAT + category_id: string_functions + category_label: String Functions + signature: + display: CONCAT(val1 "any" [, val2 "any" [, ...] ]) + args: + - name: val1 "any" [ + optional: false + type: any + - name: val2 "any" [ + optional: false + type: any + - name: '...] ]' + optional: false + type: any + tags: [] + aliases: [] + summary: Concatenates the text representations of all the arguments. + description: Concatenates the text representations of all the arguments. NULL + arguments are ignored. + examples: [] + - name: CONCAT_WS + category_id: string_functions + category_label: String Functions + signature: + display: CONCAT_WS(sep text, val1 "any" [, val2 "any" [, ...] ]) + args: + - name: sep text + optional: false + type: any + - name: val1 "any" [ + optional: false + type: any + - name: val2 "any" [ + optional: false + type: any + - name: '...] ]' + optional: false + type: any + tags: [] + aliases: [] + summary: Concatenates all but the first argument, with separators. + description: Concatenates all but the first argument, with separators. The first + argument is used as the separator string, and should not be NULL. Other NULL + arguments are ignored. + examples: [] + - name: CONVERT + category_id: binary_string_functions + category_label: Binary String Functions + signature: + display: CONVERT(bytes bytea, src_encoding name, dest_encoding name) + args: + - name: bytes bytea + optional: false + type: any + - name: src_encoding name + optional: false + type: any + - name: dest_encoding name + optional: false + type: any + tags: [] + aliases: [] + summary: Converts a binary string representing text in encoding src_encoding to + a + description: Converts a binary string representing text in encoding src_encoding + to a binary string in encoding dest_encoding (see Section 23.3.4 for available + conversions). + examples: [] + - name: CONVERT_FROM + category_id: binary_string_functions + category_label: Binary String Functions + signature: + display: CONVERT_FROM(bytes bytea, src_encoding name) + args: + - name: bytes bytea + optional: false + type: any + - name: src_encoding name + optional: false + type: any + tags: [] + aliases: [] + summary: Converts a binary string representing text in encoding src_encoding to + text + description: Converts a binary string representing text in encoding src_encoding + to text in the database encoding (see Section 23.3.4 for available conversions). + examples: [] + - name: CONVERT_TO + category_id: binary_string_functions + category_label: Binary String Functions + signature: + display: CONVERT_TO(string text, dest_encoding name) + args: + - name: string text + optional: false + type: any + - name: dest_encoding name + optional: false + type: any + tags: [] + aliases: [] + summary: Converts a text string (in the database encoding) to a binary string + description: Converts a text string (in the database encoding) to a binary string + encoded in encoding dest_encoding (see Section 23.3.4 for available conversions). + examples: [] + - name: COS + category_id: numeric_math_functions + category_label: Numeric/Math Functions + signature: + display: COS(double precision) + args: + - name: double precision + optional: false + type: any + tags: [] + aliases: [] + summary: Cosine, argument in radians + description: Cosine, argument in radians + examples: [] + - name: COSD + category_id: numeric_math_functions + category_label: Numeric/Math Functions + signature: + display: COSD(double precision) + args: + - name: double precision + optional: false + type: any + tags: [] + aliases: [] + summary: Cosine, argument in degrees + description: Cosine, argument in degrees + examples: [] + - name: COSH + category_id: numeric_math_functions + category_label: Numeric/Math Functions + signature: + display: COSH(double precision) + args: + - name: double precision + optional: false + type: any + tags: [] + aliases: [] + summary: Hyperbolic cosine + description: Hyperbolic cosine + examples: [] + - name: COT + category_id: numeric_math_functions + category_label: Numeric/Math Functions + signature: + display: COT(double precision) + args: + - name: double precision + optional: false + type: any + tags: [] + aliases: [] + summary: Cotangent, argument in radians + description: Cotangent, argument in radians + examples: [] + - name: COTD + category_id: numeric_math_functions + category_label: Numeric/Math Functions + signature: + display: COTD(double precision) + args: + - name: double precision + optional: false + type: any + tags: [] + aliases: [] + summary: Cotangent, argument in degrees + description: Cotangent, argument in degrees + examples: [] + - name: COUNT + category_id: aggregate_functions + category_label: Aggregate Functions + signature: + display: COUNT(*) + args: + - name: '*' + optional: false + type: any + tags: [] + aliases: [] + summary: Computes the number of input rows. + description: Computes the number of input rows. + examples: [] + - name: CUME_DIST1 + category_id: aggregate_functions + category_label: Aggregate Functions + signature: + display: CUME_DIST1(args) + args: + - name: args + optional: false + type: any + tags: [] + aliases: [] + summary: Computes the cumulative distribution, that is (number of rows preceding + or + description: Computes the cumulative distribution, that is (number of rows preceding + or peers with hypothetical row) / (total rows). The value thus ranges from 1/N + to 1. + examples: [] + - name: CUME_DIST2 + category_id: window_functions + category_label: Window Functions + signature: + display: CUME_DIST2 + args: [] + tags: [] + aliases: [] + summary: Returns the cumulative distribution, that is (number of partition rows + description: Returns the cumulative distribution, that is (number of partition + rows preceding or peers with current row) / (total partition rows). The value + thus ranges from 1/N to 1. + examples: [] + - name: CURRENT_DATABASE + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: CURRENT_DATABASE + args: [] + tags: [] + aliases: [] + summary: Returns the name of the current database. + description: Returns the name of the current database. (Databases are called "catalogs" + in the SQL standard, so current_catalog is the standard's spelling.) + examples: [] + - name: CURRENT_QUERY + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: CURRENT_QUERY + args: [] + tags: [] + aliases: [] + summary: Returns the text of the currently executing query, as submitted by the + description: Returns the text of the currently executing query, as submitted by + the client (which might contain more than one statement). + examples: [] + - name: CURRENT_SETTING + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: CURRENT_SETTING(setting_name text [, missing_ok boolean ]) + args: + - name: setting_name text [ + optional: false + type: any + - name: missing_ok boolean ] + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the current value of the setting setting_name. + description: Returns the current value of the setting setting_name. If there is + no such setting, current_setting throws an error unless missing_ok is supplied + and is true (in which case NULL is returned). This function corresponds to the + SQL command SHOW. + examples: [] + - name: CURRVAL + category_id: sequence_manipulation_functions + category_label: Sequence Manipulation Functions + signature: + display: CURRVAL(regclass) + args: + - name: regclass + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the value most recently obtained by nextval for this sequence + in + description: Returns the value most recently obtained by nextval for this sequence + in the current session. (An error is reported if nextval has never been called + for this sequence in this session.) Because this is returning a session-local + value, it gives a predictable answer whether or not other sessions have executed + nextval since the current session did. + examples: [] + - name: DATE_ADD + category_id: date_time_functions + category_label: Date/Time Functions + signature: + display: DATE_ADD(timestamp with time zone, interval [, text ]) + args: + - name: timestamp with time zone + optional: false + type: any + - name: interval [ + optional: false + type: any + - name: text ] + optional: false + type: any + tags: [] + aliases: [] + summary: Add an interval to a timestamp with time zone, computing times of day + and + description: Add an interval to a timestamp with time zone, computing times of + day and daylight-savings adjustments according to the time zone named by the + third argument, or the current TimeZone setting if that is omitted. The form + with two arguments is equivalent to the timestamp with time zone + interval + operator. + examples: [] + - name: DATE_PART + category_id: date_time_functions + category_label: Date/Time Functions + signature: + display: DATE_PART(text, timestamp) + args: + - name: text + optional: false + type: any + - name: timestamp + optional: false + type: any + tags: [] + aliases: [] + summary: Get timestamp subfield (equivalent to extract); see Section 9.9.1 + description: Get timestamp subfield (equivalent to extract); see Section 9.9.1 + examples: [] + - name: DATE_SUBTRACT + category_id: date_time_functions + category_label: Date/Time Functions + signature: + display: DATE_SUBTRACT(timestamp with time zone, interval [, text ]) + args: + - name: timestamp with time zone + optional: false + type: any + - name: interval [ + optional: false + type: any + - name: text ] + optional: false + type: any + tags: [] + aliases: [] + summary: Subtract an interval from a timestamp with time zone, computing times + of + description: Subtract an interval from a timestamp with time zone, computing times + of day and daylight-savings adjustments according to the time zone named by + the third argument, or the current TimeZone setting if that is omitted. The + form with two arguments is equivalent to the timestamp with time zone - interval + operator. + examples: [] + - name: DATE_TRUNC + category_id: date_time_functions + category_label: Date/Time Functions + signature: + display: DATE_TRUNC(text, timestamp) + args: + - name: text + optional: false + type: any + - name: timestamp + optional: false + type: any + tags: [] + aliases: [] + summary: Truncate to specified precision; see Section 9.9.2 + description: Truncate to specified precision; see Section 9.9.2 + examples: [] + - name: DECODE + category_id: binary_string_functions + category_label: Binary String Functions + signature: + display: DECODE(string text, format text) + args: + - name: string text + optional: false + type: any + - name: format text + optional: false + type: any + tags: [] + aliases: [] + summary: Decodes binary data from a textual representation; supported format values + description: Decodes binary data from a textual representation; supported format + values are the same as for encode. + examples: [] + - name: DEGREES + category_id: numeric_math_functions + category_label: Numeric/Math Functions + signature: + display: DEGREES(double precision) + args: + - name: double precision + optional: false + type: any + tags: [] + aliases: [] + summary: Converts radians to degrees + description: Converts radians to degrees + examples: [] + - name: DENSE_RANK1 + category_id: aggregate_functions + category_label: Aggregate Functions + signature: + display: DENSE_RANK1(args) + args: + - name: args + optional: false + type: any + tags: [] + aliases: [] + summary: Computes the rank of the hypothetical row, without gaps; this function + description: Computes the rank of the hypothetical row, without gaps; this function + effectively counts peer groups. + examples: [] + - name: DENSE_RANK2 + category_id: window_functions + category_label: Window Functions + signature: + display: DENSE_RANK2 + args: [] + tags: [] + aliases: [] + summary: Returns the rank of the current row, without gaps; this function + description: Returns the rank of the current row, without gaps; this function + effectively counts peer groups. + examples: [] + - name: DIAGONAL + category_id: geometric_functions + category_label: Geometric Functions + signature: + display: DIAGONAL(box) + args: + - name: box + optional: false + type: any + tags: [] + aliases: [] + summary: Extracts box's diagonal as a line segment (same as lseg(box)). + description: Extracts box's diagonal as a line segment (same as lseg(box)). + examples: [] + - name: DIAMETER + category_id: geometric_functions + category_label: Geometric Functions + signature: + display: DIAMETER(circle) + args: + - name: circle + optional: false + type: any + tags: [] + aliases: [] + summary: Computes diameter of circle. + description: Computes diameter of circle. + examples: [] + - name: DIV + category_id: numeric_math_functions + category_label: Numeric/Math Functions + signature: + display: DIV(y numeric, x numeric) + args: + - name: y numeric + optional: false + type: any + - name: x numeric + optional: false + type: any + tags: [] + aliases: [] + summary: Integer quotient of y/x (truncates towards zero) + description: Integer quotient of y/x (truncates towards zero) + examples: [] + - name: ENCODE + category_id: binary_string_functions + category_label: Binary String Functions + signature: + display: ENCODE(bytes bytea, format text) + args: + - name: bytes bytea + optional: false + type: any + - name: format text + optional: false + type: any + tags: [] + aliases: [] + summary: Encodes binary data into a textual representation; supported format values + description: 'Encodes binary data into a textual representation; supported format + values are: base64, escape, hex.' + examples: [] + - name: ENUM_FIRST + category_id: enum_support_functions + category_label: Enum Support Functions + signature: + display: ENUM_FIRST(anyenum) + args: + - name: anyenum + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the first value of the input enum type. + description: Returns the first value of the input enum type. + examples: [] + - name: ENUM_LAST + category_id: enum_support_functions + category_label: Enum Support Functions + signature: + display: ENUM_LAST(anyenum) + args: + - name: anyenum + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the last value of the input enum type. + description: Returns the last value of the input enum type. + examples: [] + - name: ENUM_RANGE + category_id: enum_support_functions + category_label: Enum Support Functions + signature: + display: ENUM_RANGE(anyenum) + args: + - name: anyenum + optional: false + type: any + tags: [] + aliases: [] + summary: Returns all values of the input enum type in an ordered array. + description: Returns all values of the input enum type in an ordered array. + examples: [] + - name: ERF + category_id: numeric_math_functions + category_label: Numeric/Math Functions + signature: + display: ERF(double precision) + args: + - name: double precision + optional: false + type: any + tags: [] + aliases: [] + summary: Error function + description: Error function + examples: [] + - name: ERFC + category_id: numeric_math_functions + category_label: Numeric/Math Functions + signature: + display: ERFC(double precision) + args: + - name: double precision + optional: false + type: any + tags: [] + aliases: [] + summary: Complementary error function (1 - erf(x), without loss of precision for + description: Complementary error function (1 - erf(x), without loss of precision + for large inputs) + examples: [] + - name: EVERY + category_id: aggregate_functions + category_label: Aggregate Functions + signature: + display: EVERY(boolean) + args: + - name: boolean + optional: false + type: any + tags: [] + aliases: [] + summary: This is the SQL standard's equivalent to bool_and. + description: This is the SQL standard's equivalent to bool_and. + examples: [] + - name: EXTRACT + category_id: date_time_functions + category_label: Date/Time Functions + signature: + display: EXTRACT(field from timestamp) + args: + - name: field from timestamp + optional: false + type: any + tags: [] + aliases: [] + summary: Get timestamp subfield; see Section 9.9.1 + description: Get timestamp subfield; see Section 9.9.1 + examples: [] + - name: FACTORIAL + category_id: numeric_math_functions + category_label: Numeric/Math Functions + signature: + display: FACTORIAL(bigint) + args: + - name: bigint + optional: false + type: any + tags: [] + aliases: [] + summary: Factorial + description: Factorial + examples: [] + - name: FAMILY + category_id: network_address_functions + category_label: Network Address Functions + signature: + display: FAMILY(inet) + args: + - name: inet + optional: false + type: any + tags: [] + aliases: [] + summary: 'Returns the address''s family: 4 for IPv4, 6 for IPv6.' + description: 'Returns the address''s family: 4 for IPv4, 6 for IPv6.' + examples: [] + - name: FIRST_VALUE + category_id: window_functions + category_label: Window Functions + signature: + display: FIRST_VALUE(value anyelement) + args: + - name: value anyelement + optional: false + type: any + tags: [] + aliases: [] + summary: Returns value evaluated at the row that is the first row of the window + description: Returns value evaluated at the row that is the first row of the window + frame. + examples: [] + - name: FORMAT + category_id: string_functions + category_label: String Functions + signature: + display: FORMAT(formatstr text [, formatarg "any" [, ...] ]) + args: + - name: formatstr text [ + optional: false + type: any + - name: formatarg "any" [ + optional: false + type: any + - name: '...] ]' + optional: false + type: any + tags: [] + aliases: [] + summary: Formats arguments according to a format string; see Section 9.4.1. + description: Formats arguments according to a format string; see Section 9.4.1. + This function is similar to the C function sprintf. + examples: [] + - name: FORMAT_TYPE + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: FORMAT_TYPE(type oid, typemod integer) + args: + - name: type oid + optional: false + type: any + - name: typemod integer + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the SQL name for a data type that is identified by its type OID + and + description: Returns the SQL name for a data type that is identified by its type + OID and possibly a type modifier. Pass NULL for the type modifier if no specific + modifier is known. + examples: [] + - name: GCD + category_id: numeric_math_functions + category_label: Numeric/Math Functions + signature: + display: GCD(numeric_type, numeric_type) + args: + - name: numeric_type + optional: false + type: any + - name: numeric_type + optional: false + type: any + tags: [] + aliases: [] + summary: Greatest common divisor (the largest positive number that divides both + description: Greatest common divisor (the largest positive number that divides + both inputs with no remainder); returns 0 if both inputs are zero; available + for integer, bigint, and numeric + examples: [] + - name: GET_BIT1 + category_id: binary_string_functions + category_label: Binary String Functions + signature: + display: GET_BIT1(bytes bytea, n bigint) + args: + - name: bytes bytea + optional: false + type: any + - name: n bigint + optional: false + type: any + tags: [] + aliases: [] + summary: Extracts n'th bit from binary string. + description: Extracts n'th bit from binary string. + examples: [] + - name: GET_BIT2 + category_id: bit_string_functions + category_label: Bit String Functions + signature: + display: GET_BIT2(bits bit, n integer) + args: + - name: bits bit + optional: false + type: any + - name: n integer + optional: false + type: any + tags: [] + aliases: [] + summary: Extracts n'th bit from bit string; the first (leftmost) bit is bit 0. + description: Extracts n'th bit from bit string; the first (leftmost) bit is bit + 0. + examples: [] + - name: GET_BYTE + category_id: binary_string_functions + category_label: Binary String Functions + signature: + display: GET_BYTE(bytes bytea, n integer) + args: + - name: bytes bytea + optional: false + type: any + - name: n integer + optional: false + type: any + tags: [] + aliases: [] + summary: Extracts n'th byte from binary string. + description: Extracts n'th byte from binary string. + examples: [] + - name: GET_CURRENT_TS_CONFIG + category_id: text_search_functions + category_label: Text Search Functions + signature: + display: GET_CURRENT_TS_CONFIG + args: [] + tags: [] + aliases: [] + summary: Returns the OID of the current default text search configuration (as + set by + description: Returns the OID of the current default text search configuration + (as set by default_text_search_config). + examples: [] + - name: GIN_CLEAN_PENDING_LIST + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: GIN_CLEAN_PENDING_LIST(index regclass) + args: + - name: index regclass + optional: false + type: any + tags: [] + aliases: [] + summary: Cleans up the "pending" list of the specified GIN index by moving entries + description: Cleans up the "pending" list of the specified GIN index by moving + entries in it, in bulk, to the main GIN data structure. Returns the number of + pages removed from the pending list. If the argument is a GIN index built with + the fastupdate option disabled, no cleanup happens and the result is zero, because + the index doesn't have a pending list. See Section 64.4.4.1 and Section 64.4.5 + for details about the pending list and fastupdate option. + examples: [] + - name: GROUPING + category_id: aggregate_functions + category_label: Aggregate Functions + signature: + display: GROUPING(group_by_expression(s) + args: + - name: group_by_expression(s + optional: false + type: any + tags: [] + aliases: [] + summary: Returns a bit mask indicating which GROUP BY expressions are not included + description: Returns a bit mask indicating which GROUP BY expressions are not + included in the current grouping set. Bits are assigned with the rightmost argument + corresponding to the least-significant bit; each bit is 0 if the corresponding + expression is included in the grouping criteria of the grouping set generating + the current result row, and 1 if it is not included. + examples: [] + - name: HAS_ANY_COLUMN_PRIVILEGE + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: HAS_ANY_COLUMN_PRIVILEGE([ user name or oid, ] table text or oid, privilege + text) + args: + - name: '[ user name or oid' + optional: false + type: any + - name: '] table text or oid' + optional: false + type: any + - name: privilege text + optional: false + type: any + tags: [] + aliases: [] + summary: Does user have privilege for any column of table? + description: Does user have privilege for any column of table? This succeeds either + if the privilege is held for the whole table, or if there is a column-level + grant of the privilege for at least one column. Allowable privilege types are + SELECT, INSERT, UPDATE, and REFERENCES. + examples: [] + - name: HAS_COLUMN_PRIVILEGE + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: HAS_COLUMN_PRIVILEGE([ user name or oid, ] table text or oid, column + text or smallint, privilege text) + args: + - name: '[ user name or oid' + optional: false + type: any + - name: '] table text or oid' + optional: false + type: any + - name: column text or smallint + optional: false + type: any + - name: privilege text + optional: false + type: any + tags: [] + aliases: [] + summary: Does user have privilege for the specified table column? + description: Does user have privilege for the specified table column? This succeeds + either if the privilege is held for the whole table, or if there is a column-level + grant of the privilege for the column. The column can be specified by name or + by attribute number (pg_attribute.attnum). Allowable privilege types are SELECT, + INSERT, UPDATE, and REFERENCES. + examples: [] + - name: HAS_DATABASE_PRIVILEGE + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: HAS_DATABASE_PRIVILEGE([ user name or oid, ] database text or oid, + privilege text) + args: + - name: '[ user name or oid' + optional: false + type: any + - name: '] database text or oid' + optional: false + type: any + - name: privilege text + optional: false + type: any + tags: [] + aliases: [] + summary: Does user have privilege for database? + description: Does user have privilege for database? Allowable privilege types + are CREATE, CONNECT, TEMPORARY, and TEMP (which is equivalent to TEMPORARY). + examples: [] + - name: HAS_FOREIGN_DATA_WRAPPER_PRIVILEGE + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: HAS_FOREIGN_DATA_WRAPPER_PRIVILEGE([ user name or oid, ] fdw text or + oid, privilege text) + args: + - name: '[ user name or oid' + optional: false + type: any + - name: '] fdw text or oid' + optional: false + type: any + - name: privilege text + optional: false + type: any + tags: [] + aliases: [] + summary: Does user have privilege for foreign-data wrapper? + description: Does user have privilege for foreign-data wrapper? The only allowable + privilege type is USAGE. + examples: [] + - name: HAS_FUNCTION_PRIVILEGE + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: HAS_FUNCTION_PRIVILEGE([ user name or oid, ] function text or oid, + privilege text) + args: + - name: '[ user name or oid' + optional: false + type: any + - name: '] function text or oid' + optional: false + type: any + - name: privilege text + optional: false + type: any + tags: [] + aliases: [] + summary: Does user have privilege for function? + description: Does user have privilege for function? The only allowable privilege + type is EXECUTE. + examples: [] + - name: HAS_LANGUAGE_PRIVILEGE + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: HAS_LANGUAGE_PRIVILEGE([ user name or oid, ] language text or oid, + privilege text) + args: + - name: '[ user name or oid' + optional: false + type: any + - name: '] language text or oid' + optional: false + type: any + - name: privilege text + optional: false + type: any + tags: [] + aliases: [] + summary: Does user have privilege for language? + description: Does user have privilege for language? The only allowable privilege + type is USAGE. + examples: [] + - name: HAS_PARAMETER_PRIVILEGE + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: HAS_PARAMETER_PRIVILEGE([ user name or oid, ] parameter text, privilege + text) + args: + - name: '[ user name or oid' + optional: false + type: any + - name: '] parameter text' + optional: false + type: any + - name: privilege text + optional: false + type: any + tags: [] + aliases: [] + summary: Does user have privilege for configuration parameter? + description: Does user have privilege for configuration parameter? The parameter + name is case-insensitive. Allowable privilege types are SET and ALTER SYSTEM. + examples: [] + - name: HAS_SCHEMA_PRIVILEGE + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: HAS_SCHEMA_PRIVILEGE([ user name or oid, ] schema text or oid, privilege + text) + args: + - name: '[ user name or oid' + optional: false + type: any + - name: '] schema text or oid' + optional: false + type: any + - name: privilege text + optional: false + type: any + tags: [] + aliases: [] + summary: Does user have privilege for schema? + description: Does user have privilege for schema? Allowable privilege types are + CREATE and USAGE. + examples: [] + - name: HAS_SEQUENCE_PRIVILEGE + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: HAS_SEQUENCE_PRIVILEGE([ user name or oid, ] sequence text or oid, + privilege text) + args: + - name: '[ user name or oid' + optional: false + type: any + - name: '] sequence text or oid' + optional: false + type: any + - name: privilege text + optional: false + type: any + tags: [] + aliases: [] + summary: Does user have privilege for sequence? + description: Does user have privilege for sequence? Allowable privilege types + are USAGE, SELECT, and UPDATE. + examples: [] + - name: HAS_SERVER_PRIVILEGE + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: HAS_SERVER_PRIVILEGE([ user name or oid, ] server text or oid, privilege + text) + args: + - name: '[ user name or oid' + optional: false + type: any + - name: '] server text or oid' + optional: false + type: any + - name: privilege text + optional: false + type: any + tags: [] + aliases: [] + summary: Does user have privilege for foreign server? + description: Does user have privilege for foreign server? The only allowable privilege + type is USAGE. + examples: [] + - name: HAS_TABLESPACE_PRIVILEGE + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: HAS_TABLESPACE_PRIVILEGE([ user name or oid, ] tablespace text or oid, + privilege text) + args: + - name: '[ user name or oid' + optional: false + type: any + - name: '] tablespace text or oid' + optional: false + type: any + - name: privilege text + optional: false + type: any + tags: [] + aliases: [] + summary: Does user have privilege for tablespace? + description: Does user have privilege for tablespace? The only allowable privilege + type is CREATE. + examples: [] + - name: HAS_TABLE_PRIVILEGE + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: HAS_TABLE_PRIVILEGE([ user name or oid, ] table text or oid, privilege + text) + args: + - name: '[ user name or oid' + optional: false + type: any + - name: '] table text or oid' + optional: false + type: any + - name: privilege text + optional: false + type: any + tags: [] + aliases: [] + summary: Does user have privilege for table? + description: Does user have privilege for table? Allowable privilege types are + SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER, and MAINTAIN. + examples: [] + - name: HAS_TYPE_PRIVILEGE + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: HAS_TYPE_PRIVILEGE([ user name or oid, ] type text or oid, privilege + text) + args: + - name: '[ user name or oid' + optional: false + type: any + - name: '] type text or oid' + optional: false + type: any + - name: privilege text + optional: false + type: any + tags: [] + aliases: [] + summary: Does user have privilege for data type? + description: Does user have privilege for data type? The only allowable privilege + type is USAGE. When specifying a type by name rather than by OID, the allowed + input is the same as for the regtype data type (see Section 8.19). + examples: [] + - name: HEIGHT + category_id: geometric_functions + category_label: Geometric Functions + signature: + display: HEIGHT(box) + args: + - name: box + optional: false + type: any + tags: [] + aliases: [] + summary: Computes vertical size of box. + description: Computes vertical size of box. + examples: [] + - name: HOST + category_id: network_address_functions + category_label: Network Address Functions + signature: + display: HOST(inet) + args: + - name: inet + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the IP address as text, ignoring the netmask. + description: Returns the IP address as text, ignoring the netmask. + examples: [] + - name: HOSTMASK + category_id: network_address_functions + category_label: Network Address Functions + signature: + display: HOSTMASK(inet) + args: + - name: inet + optional: false + type: any + tags: [] + aliases: [] + summary: Computes the host mask for the address's network. + description: Computes the host mask for the address's network. + examples: [] + - name: ICU_UNICODE_VERSION + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: ICU_UNICODE_VERSION + args: [] + tags: [] + aliases: [] + summary: Returns a string representing the version of Unicode used by ICU, if + the + description: Returns a string representing the version of Unicode used by ICU, + if the server was built with ICU support; otherwise returns NULL + examples: [] + - name: INET_CLIENT_ADDR + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: INET_CLIENT_ADDR + args: [] + tags: [] + aliases: [] + summary: Returns the IP address of the current client, or NULL if the current + description: Returns the IP address of the current client, or NULL if the current + connection is via a Unix-domain socket. + examples: [] + - name: INET_CLIENT_PORT + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: INET_CLIENT_PORT + args: [] + tags: [] + aliases: [] + summary: Returns the IP port number of the current client, or NULL if the current + description: Returns the IP port number of the current client, or NULL if the + current connection is via a Unix-domain socket. + examples: [] + - name: INET_MERGE + category_id: network_address_functions + category_label: Network Address Functions + signature: + display: INET_MERGE(inet, inet) + args: + - name: inet + optional: false + type: any + - name: inet + optional: false + type: any + tags: [] + aliases: [] + summary: Computes the smallest network that includes both of the given networks. + description: Computes the smallest network that includes both of the given networks. + examples: [] + - name: INET_SAME_FAMILY + category_id: network_address_functions + category_label: Network Address Functions + signature: + display: INET_SAME_FAMILY(inet, inet) + args: + - name: inet + optional: false + type: any + - name: inet + optional: false + type: any + tags: [] + aliases: [] + summary: Tests whether the addresses belong to the same IP family. + description: Tests whether the addresses belong to the same IP family. + examples: [] + - name: INET_SERVER_ADDR + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: INET_SERVER_ADDR + args: [] + tags: [] + aliases: [] + summary: Returns the IP address on which the server accepted the current connection, + description: Returns the IP address on which the server accepted the current connection, + or NULL if the current connection is via a Unix-domain socket. + examples: [] + - name: INET_SERVER_PORT + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: INET_SERVER_PORT + args: [] + tags: [] + aliases: [] + summary: Returns the IP port number on which the server accepted the current + description: Returns the IP port number on which the server accepted the current + connection, or NULL if the current connection is via a Unix-domain socket. + examples: [] + - name: INITCAP + category_id: string_functions + category_label: String Functions + signature: + display: INITCAP(text) + args: + - name: text + optional: false + type: any + tags: [] + aliases: [] + summary: Converts the first letter of each word to upper case and the rest to + lower + description: Converts the first letter of each word to upper case and the rest + to lower case. Words are sequences of alphanumeric characters separated by non-alphanumeric + characters. + examples: [] + - name: ISCLOSED + category_id: geometric_functions + category_label: Geometric Functions + signature: + display: ISCLOSED(path) + args: + - name: path + optional: false + type: any + tags: [] + aliases: [] + summary: Is path closed? + description: Is path closed? + examples: [] + - name: ISEMPTY1 + category_id: range_functions + category_label: Range Functions + signature: + display: ISEMPTY1(anyrange) + args: + - name: anyrange + optional: false + type: any + tags: [] + aliases: [] + summary: Is the range empty? + description: Is the range empty? + examples: [] + - name: ISEMPTY2 + category_id: range_functions + category_label: Range Functions + signature: + display: ISEMPTY2(anymultirange) + args: + - name: anymultirange + optional: false + type: any + tags: [] + aliases: [] + summary: Is the multirange empty? + description: Is the multirange empty? + examples: [] + - name: ISFINITE + category_id: date_time_functions + category_label: Date/Time Functions + signature: + display: ISFINITE(date) + args: + - name: date + optional: false + type: any + tags: [] + aliases: [] + summary: Test for finite date (not +/-infinity) + description: Test for finite date (not +/-infinity) + examples: [] + - name: ISOPEN + category_id: geometric_functions + category_label: Geometric Functions + signature: + display: ISOPEN(path) + args: + - name: path + optional: false + type: any + tags: [] + aliases: [] + summary: Is path open? + description: Is path open? + examples: [] + - name: JSON + category_id: json_functions + category_label: JSON Functions + signature: + display: JSON(expression [ FORMAT JSON [ ENCODING UTF8 ]] [ { WITH | WITHOUT + } UNIQUE [ KEYS ]]) + args: + - name: expression [ FORMAT JSON [ ENCODING UTF8 ]] [ { WITH | WITHOUT } UNIQUE + [ KEYS ]] + optional: false + type: any + tags: [] + aliases: [] + summary: Converts a given expression specified as text or bytea string (in UTF8 + description: Converts a given expression specified as text or bytea string (in + UTF8 encoding) into a JSON value. If expression is NULL, an SQL null value is + returned. If WITH UNIQUE is specified, the expression must not contain any duplicate + object keys. + examples: [] + - name: JSONB_AGG + category_id: aggregate_functions + category_label: Aggregate Functions + signature: + display: JSONB_AGG(anyelement ORDER BY input_sort_columns) + args: + - name: anyelement ORDER BY input_sort_columns + optional: false + type: any + tags: [] + aliases: [] + summary: Collects all the input values, including nulls, into a JSON array. + description: Collects all the input values, including nulls, into a JSON array. + Values are converted to JSON as per to_json or to_jsonb. + examples: [] + - name: JSONB_AGG_STRICT + category_id: aggregate_functions + category_label: Aggregate Functions + signature: + display: JSONB_AGG_STRICT(anyelement) + args: + - name: anyelement + optional: false + type: any + tags: [] + aliases: [] + summary: Collects all the input values, skipping nulls, into a JSON array. + description: Collects all the input values, skipping nulls, into a JSON array. + Values are converted to JSON as per to_json or to_jsonb. + examples: [] + - name: JSONB_ARRAY_ELEMENTS + category_id: json_functions + category_label: JSON Functions + signature: + display: JSONB_ARRAY_ELEMENTS(jsonb) + args: + - name: jsonb + optional: false + type: any + tags: [] + aliases: [] + summary: Expands the top-level JSON array into a set of JSON values. + description: Expands the top-level JSON array into a set of JSON values. + examples: [] + - name: JSONB_ARRAY_ELEMENTS_TEXT + category_id: json_functions + category_label: JSON Functions + signature: + display: JSONB_ARRAY_ELEMENTS_TEXT(jsonb) + args: + - name: jsonb + optional: false + type: any + tags: [] + aliases: [] + summary: Expands the top-level JSON array into a set of text values. + description: Expands the top-level JSON array into a set of text values. + examples: [] + - name: JSONB_ARRAY_LENGTH + category_id: json_functions + category_label: JSON Functions + signature: + display: JSONB_ARRAY_LENGTH(jsonb) + args: + - name: jsonb + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the number of elements in the top-level JSON array. + description: Returns the number of elements in the top-level JSON array. + examples: [] + - name: JSONB_BUILD_ARRAY + category_id: json_functions + category_label: JSON Functions + signature: + display: JSONB_BUILD_ARRAY(VARIADIC "any") + args: + - name: VARIADIC "any" + optional: false + type: any + tags: [] + aliases: [] + summary: Builds a possibly-heterogeneously-typed JSON array out of a variadic + description: Builds a possibly-heterogeneously-typed JSON array out of a variadic + argument list. Each argument is converted as per to_json or to_jsonb. + examples: [] + - name: JSONB_BUILD_OBJECT + category_id: json_functions + category_label: JSON Functions + signature: + display: JSONB_BUILD_OBJECT(VARIADIC "any") + args: + - name: VARIADIC "any" + optional: false + type: any + tags: [] + aliases: [] + summary: Builds a JSON object out of a variadic argument list. + description: Builds a JSON object out of a variadic argument list. By convention, + the argument list consists of alternating keys and values. Key arguments are + coerced to text; value arguments are converted as per to_json or to_jsonb. + examples: [] + - name: JSONB_EACH + category_id: json_functions + category_label: JSON Functions + signature: + display: JSONB_EACH(jsonb) + args: + - name: jsonb + optional: false + type: any + tags: [] + aliases: [] + summary: Expands the top-level JSON object into a set of key/value pairs. + description: Expands the top-level JSON object into a set of key/value pairs. + examples: [] + - name: JSONB_EACH_TEXT + category_id: json_functions + category_label: JSON Functions + signature: + display: JSONB_EACH_TEXT(jsonb) + args: + - name: jsonb + optional: false + type: any + tags: [] + aliases: [] + summary: Expands the top-level JSON object into a set of key/value pairs. + description: Expands the top-level JSON object into a set of key/value pairs. + The returned values will be of type text. + examples: [] + - name: JSONB_EXTRACT_PATH + category_id: json_functions + category_label: JSON Functions + signature: + display: JSONB_EXTRACT_PATH(from_json jsonb, VARIADIC path_elems text[]) + args: + - name: from_json jsonb + optional: false + type: any + - name: VARIADIC path_elems text[] + optional: false + type: any + tags: [] + aliases: [] + summary: Extracts JSON sub-object at the specified path. + description: 'Extracts JSON sub-object at the specified path. (This is functionally + equivalent to the #> operator, but writing the path out as a variadic list can + be more convenient in some cases.)' + examples: [] + - name: JSONB_EXTRACT_PATH_TEXT + category_id: json_functions + category_label: JSON Functions + signature: + display: JSONB_EXTRACT_PATH_TEXT(from_json jsonb, VARIADIC path_elems text[]) + args: + - name: from_json jsonb + optional: false + type: any + - name: VARIADIC path_elems text[] + optional: false + type: any + tags: [] + aliases: [] + summary: Extracts JSON sub-object at the specified path as text. + description: 'Extracts JSON sub-object at the specified path as text. (This is + functionally equivalent to the #>> operator.)' + examples: [] + - name: JSONB_INSERT + category_id: json_functions + category_label: JSON Functions + signature: + display: JSONB_INSERT(target jsonb, path text[], new_value jsonb [, insert_after + boolean ]) + args: + - name: target jsonb + optional: false + type: any + - name: path text[] + optional: false + type: any + - name: new_value jsonb [ + optional: false + type: any + - name: insert_after boolean ] + optional: false + type: any + tags: [] + aliases: [] + summary: Returns target with new_value inserted. + description: Returns target with new_value inserted. If the item designated by + the path is an array element, new_value will be inserted before that item if + insert_after is false (which is the default), or after it if insert_after is + true. If the item designated by the path is an object field, new_value will + be inserted only if the object does not already contain that key. All earlier + steps in the path must exist, or the target is returned unchanged. As with the + path oriented operators, negative integers that appear in the path count from + the end of JSON arrays. If the last path step is an array index that is out + of range, the new value is added at the beginning of the array if the index + is negative, or at the end of the array if it is positive. + examples: [] + - name: JSONB_OBJECT + category_id: json_functions + category_label: JSON Functions + signature: + display: JSONB_OBJECT(text[]) + args: + - name: text[] + optional: false + type: any + tags: [] + aliases: [] + summary: Builds a JSON object out of a text array. + description: Builds a JSON object out of a text array. The array must have either + exactly one dimension with an even number of members, in which case they are + taken as alternating key/value pairs, or two dimensions such that each inner + array has exactly two elements, which are taken as a key/value pair. All values + are converted to JSON strings. + examples: [] + - name: JSONB_OBJECT_AGG + category_id: aggregate_functions + category_label: Aggregate Functions + signature: + display: JSONB_OBJECT_AGG(key "any", value "any" ORDER BY input_sort_columns) + args: + - name: key "any" + optional: false + type: any + - name: value "any" ORDER BY input_sort_columns + optional: false + type: any + tags: [] + aliases: [] + summary: Collects all the key/value pairs into a JSON object. + description: Collects all the key/value pairs into a JSON object. Key arguments + are coerced to text; value arguments are converted as per to_json or to_jsonb. + Values can be null, but keys cannot. + examples: [] + - name: JSONB_OBJECT_AGG_STRICT + category_id: aggregate_functions + category_label: Aggregate Functions + signature: + display: JSONB_OBJECT_AGG_STRICT(key "any", value "any") + args: + - name: key "any" + optional: false + type: any + - name: value "any" + optional: false + type: any + tags: [] + aliases: [] + summary: Collects all the key/value pairs into a JSON object. + description: Collects all the key/value pairs into a JSON object. Key arguments + are coerced to text; value arguments are converted as per to_json or to_jsonb. + The key can not be null. If the value is null then the entry is skipped, + examples: [] + - name: JSONB_OBJECT_AGG_UNIQUE + category_id: aggregate_functions + category_label: Aggregate Functions + signature: + display: JSONB_OBJECT_AGG_UNIQUE(key "any", value "any") + args: + - name: key "any" + optional: false + type: any + - name: value "any" + optional: false + type: any + tags: [] + aliases: [] + summary: Collects all the key/value pairs into a JSON object. + description: Collects all the key/value pairs into a JSON object. Key arguments + are coerced to text; value arguments are converted as per to_json or to_jsonb. + Values can be null, but keys cannot. If there is a duplicate key an error is + thrown. + examples: [] + - name: JSONB_OBJECT_AGG_UNIQUE_STRICT + category_id: aggregate_functions + category_label: Aggregate Functions + signature: + display: JSONB_OBJECT_AGG_UNIQUE_STRICT(key "any", value "any") + args: + - name: key "any" + optional: false + type: any + - name: value "any" + optional: false + type: any + tags: [] + aliases: [] + summary: Collects all the key/value pairs into a JSON object. + description: Collects all the key/value pairs into a JSON object. Key arguments + are coerced to text; value arguments are converted as per to_json or to_jsonb. + The key can not be null. If the value is null then the entry is skipped. If + there is a duplicate key an error is thrown. + examples: [] + - name: JSONB_OBJECT_KEYS + category_id: json_functions + category_label: JSON Functions + signature: + display: JSONB_OBJECT_KEYS(jsonb) + args: + - name: jsonb + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the set of keys in the top-level JSON object. + description: Returns the set of keys in the top-level JSON object. + examples: [] + - name: JSONB_PATH_EXISTS + category_id: json_functions + category_label: JSON Functions + signature: + display: JSONB_PATH_EXISTS(target jsonb, path jsonpath [, vars jsonb [, silent + boolean ]]) + args: + - name: target jsonb + optional: false + type: any + - name: path jsonpath [ + optional: false + type: any + - name: vars jsonb [ + optional: false + type: any + - name: silent boolean ]] + optional: false + type: any + tags: [] + aliases: [] + summary: Checks whether the JSON path returns any item for the specified JSON + value. + description: Checks whether the JSON path returns any item for the specified JSON + value. (This is useful only with SQL-standard JSON path expressions, not predicate + check expressions, since those always return a value.) If the vars argument + is specified, it must be a JSON object, and its fields provide named values + to be substituted into the jsonpath expression. If the silent argument is specified + and is true, the function suppresses the same errors as the @? and @@ operators + do. + examples: [] + - name: JSONB_PATH_MATCH + category_id: json_functions + category_label: JSON Functions + signature: + display: JSONB_PATH_MATCH(target jsonb, path jsonpath [, vars jsonb [, silent + boolean ]]) + args: + - name: target jsonb + optional: false + type: any + - name: path jsonpath [ + optional: false + type: any + - name: vars jsonb [ + optional: false + type: any + - name: silent boolean ]] + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the result of a JSON path predicate check for the specified JSON + description: Returns the result of a JSON path predicate check for the specified + JSON value. (This is useful only with predicate check expressions, not SQL-standard + JSON path expressions, since it will either fail or return NULL if the path + result is not a single boolean value.) The optional vars and silent arguments + act the same as for jsonb_path_exists. + examples: [] + - name: JSONB_PATH_QUERY + category_id: json_functions + category_label: JSON Functions + signature: + display: JSONB_PATH_QUERY(target jsonb, path jsonpath [, vars jsonb [, silent + boolean ]]) + args: + - name: target jsonb + optional: false + type: any + - name: path jsonpath [ + optional: false + type: any + - name: vars jsonb [ + optional: false + type: any + - name: silent boolean ]] + optional: false + type: any + tags: [] + aliases: [] + summary: Returns all JSON items returned by the JSON path for the specified JSON + description: 'Returns all JSON items returned by the JSON path for the specified + JSON value. For SQL-standard JSON path expressions it returns the JSON values + selected from target. For predicate check expressions it returns the result + of the predicate check: true, false, or null. The optional vars and silent arguments + act the same as for jsonb_path_exists.' + examples: [] + - name: JSONB_PATH_QUERY_ARRAY + category_id: json_functions + category_label: JSON Functions + signature: + display: JSONB_PATH_QUERY_ARRAY(target jsonb, path jsonpath [, vars jsonb [, + silent boolean ]]) + args: + - name: target jsonb + optional: false + type: any + - name: path jsonpath [ + optional: false + type: any + - name: vars jsonb [ + optional: false + type: any + - name: silent boolean ]] + optional: false + type: any + tags: [] + aliases: [] + summary: Returns all JSON items returned by the JSON path for the specified JSON + description: Returns all JSON items returned by the JSON path for the specified + JSON value, as a JSON array. The parameters are the same as for jsonb_path_query. + examples: [] + - name: JSONB_PATH_QUERY_FIRST + category_id: json_functions + category_label: JSON Functions + signature: + display: JSONB_PATH_QUERY_FIRST(target jsonb, path jsonpath [, vars jsonb [, + silent boolean ]]) + args: + - name: target jsonb + optional: false + type: any + - name: path jsonpath [ + optional: false + type: any + - name: vars jsonb [ + optional: false + type: any + - name: silent boolean ]] + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the first JSON item returned by the JSON path for the specified + description: Returns the first JSON item returned by the JSON path for the specified + JSON value, or NULL if there are no results. The parameters are the same as + for jsonb_path_query. + examples: [] + - name: JSONB_PATH_QUERY_FIRST_TZ + category_id: json_functions + category_label: JSON Functions + signature: + display: JSONB_PATH_QUERY_FIRST_TZ(target jsonb, path jsonpath [, vars jsonb + [, silent boolean ]]) + args: + - name: target jsonb + optional: false + type: any + - name: path jsonpath [ + optional: false + type: any + - name: vars jsonb [ + optional: false + type: any + - name: silent boolean ]] + optional: false + type: any + tags: [] + aliases: [] + summary: These functions act like their counterparts described above without the + _tz + description: These functions act like their counterparts described above without + the _tz suffix, except that these functions support comparisons of date/time + values that require timezone-aware conversions. The example below requires interpretation + of the date-only value 2015-08-02 as a timestamp with time zone, so the result + depends on the current TimeZone setting. Due to this dependency, these functions + are marked as stable, which means these functions cannot be used in indexes. + Their counterparts are immutable, and so can be used in indexes; but they will + throw errors if asked to make such comparisons. + examples: [] + - name: JSONB_POPULATE_RECORD + category_id: json_functions + category_label: JSON Functions + signature: + display: JSONB_POPULATE_RECORD(base anyelement, from_json jsonb) + args: + - name: base anyelement + optional: false + type: any + - name: from_json jsonb + optional: false + type: any + tags: [] + aliases: [] + summary: Expands the top-level JSON object to a row having the composite type + of the + description: Expands the top-level JSON object to a row having the composite type + of the base argument. The JSON object is scanned for fields whose names match + column names of the output row type, and their values are inserted into those + columns of the output. (Fields that do not correspond to any output column name + are ignored.) In typical use, the value of base is just NULL, which means that + any output columns that do not match any object field will be filled with nulls. + However, if base isn't NULL then the values it contains will be used for unmatched + columns. + examples: [] + - name: JSONB_POPULATE_RECORDSET + category_id: json_functions + category_label: JSON Functions + signature: + display: JSONB_POPULATE_RECORDSET(base anyelement, from_json jsonb) + args: + - name: base anyelement + optional: false + type: any + - name: from_json jsonb + optional: false + type: any + tags: [] + aliases: [] + summary: Expands the top-level JSON array of objects to a set of rows having the + description: Expands the top-level JSON array of objects to a set of rows having + the composite type of the base argument. Each element of the JSON array is processed + as described above for json[b]_populate_record. + examples: [] + - name: JSONB_POPULATE_RECORD_VALID + category_id: json_functions + category_label: JSON Functions + signature: + display: JSONB_POPULATE_RECORD_VALID(base anyelement, from_json json) + args: + - name: base anyelement + optional: false + type: any + - name: from_json json + optional: false + type: any + tags: [] + aliases: [] + summary: Function for testing jsonb_populate_record. + description: Function for testing jsonb_populate_record. Returns true if the input + jsonb_populate_record would finish without an error for the given input JSON + object; that is, it's valid input, false otherwise. + examples: [] + - name: JSONB_PRETTY + category_id: json_functions + category_label: JSON Functions + signature: + display: JSONB_PRETTY(jsonb) + args: + - name: jsonb + optional: false + type: any + tags: [] + aliases: [] + summary: Converts the given JSON value to pretty-printed, indented text. + description: Converts the given JSON value to pretty-printed, indented text. + examples: [] + - name: JSONB_SET + category_id: json_functions + category_label: JSON Functions + signature: + display: JSONB_SET(target jsonb, path text[], new_value jsonb [, create_if_missing + boolean ]) + args: + - name: target jsonb + optional: false + type: any + - name: path text[] + optional: false + type: any + - name: new_value jsonb [ + optional: false + type: any + - name: create_if_missing boolean ] + optional: false + type: any + tags: [] + aliases: [] + summary: Returns target with the item designated by path replaced by new_value, + or + description: Returns target with the item designated by path replaced by new_value, + or with new_value added if create_if_missing is true (which is the default) + and the item designated by path does not exist. All earlier steps in the path + must exist, or the target is returned unchanged. As with the path oriented operators, + negative integers that appear in the path count from the end of JSON arrays. + If the last path step is an array index that is out of range, and create_if_missing + is true, the new value is added at the beginning of the array if the index is + negative, or at the end of the array if it is positive. + examples: [] + - name: JSONB_SET_LAX + category_id: json_functions + category_label: JSON Functions + signature: + display: JSONB_SET_LAX(target jsonb, path text[], new_value jsonb [, create_if_missing + boolean [, null_value_treatment text ]]) + args: + - name: target jsonb + optional: false + type: any + - name: path text[] + optional: false + type: any + - name: new_value jsonb [ + optional: false + type: any + - name: create_if_missing boolean [ + optional: false + type: any + - name: null_value_treatment text ]] + optional: false + type: any + tags: [] + aliases: [] + summary: If new_value is not NULL, behaves identically to jsonb_set. + description: If new_value is not NULL, behaves identically to jsonb_set. Otherwise + behaves according to the value of null_value_treatment which must be one of + 'raise_exception', 'use_json_null', 'delete_key', or 'return_target'. The default + is 'use_json_null'. + examples: [] + - name: JSONB_STRIP_NULLS + category_id: json_functions + category_label: JSON Functions + signature: + display: JSONB_STRIP_NULLS(jsonb) + args: + - name: jsonb + optional: false + type: any + tags: [] + aliases: [] + summary: Deletes all object fields that have null values from the given JSON value, + description: Deletes all object fields that have null values from the given JSON + value, recursively. Null values that are not object fields are untouched. + examples: [] + - name: JSONB_TO_RECORD + category_id: json_functions + category_label: JSON Functions + signature: + display: JSONB_TO_RECORD(jsonb) + args: + - name: jsonb + optional: false + type: any + tags: [] + aliases: [] + summary: Expands the top-level JSON object to a row having the composite type + description: Expands the top-level JSON object to a row having the composite type + defined by an AS clause. (As with all functions returning record, the calling + query must explicitly define the structure of the record with an AS clause.) + The output record is filled from fields of the JSON object, in the same way + as described above for json[b]_populate_record. Since there is no input record + value, unmatched columns are always filled with nulls. + examples: [] + - name: JSONB_TO_RECORDSET + category_id: json_functions + category_label: JSON Functions + signature: + display: JSONB_TO_RECORDSET(jsonb) + args: + - name: jsonb + optional: false + type: any + tags: [] + aliases: [] + summary: Expands the top-level JSON array of objects to a set of rows having the + description: Expands the top-level JSON array of objects to a set of rows having + the composite type defined by an AS clause. (As with all functions returning + record, the calling query must explicitly define the structure of the record + with an AS clause.) Each element of the JSON array is processed as described + above for json[b]_populate_record. + examples: [] + - name: JSONB_TO_TSVECTOR + category_id: text_search_functions + category_label: Text Search Functions + signature: + display: JSONB_TO_TSVECTOR([ config regconfig, ] document jsonb, filter jsonb) + args: + - name: '[ config regconfig' + optional: false + type: any + - name: '] document jsonb' + optional: false + type: any + - name: filter jsonb + optional: false + type: any + tags: [] + aliases: [] + summary: Selects each item in the JSON document that is requested by the filter + and + description: 'Selects each item in the JSON document that is requested by the + filter and converts each one to a tsvector, normalizing words according to the + specified or default configuration. The results are then concatenated in document + order to produce the output. Position information is generated as though one + stopword exists between each pair of selected items. (Beware that "document + order" of the fields of a JSON object is implementation-dependent when the input + is jsonb.) The filter must be a jsonb array containing zero or more of these + keywords: "string" (to include all string values), "numeric" (to include all + numeric values), "boolean" (to include all boolean values), "key" (to include + all keys), or "all" (to include all the above). As a special case, the filter + can also be a simple JSON value that is one of these keywords.' + examples: [] + - name: JSONB_TYPEOF + category_id: json_functions + category_label: JSON Functions + signature: + display: JSONB_TYPEOF(jsonb) + args: + - name: jsonb + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the type of the top-level JSON value as a text string. + description: Returns the type of the top-level JSON value as a text string. Possible + types are object, array, string, number, boolean, and null. (The null result + should not be confused with an SQL NULL; see the examples.) + examples: [] + - name: JSON_ARRAYAGG + category_id: aggregate_functions + category_label: Aggregate Functions + signature: + display: JSON_ARRAYAGG([ value_expression ] [ ORDER BY sort_expression ] [ { + NULL | ABSENT } ON NULL ] [ RETURNING data_type [ FORMAT JSON [ ENCODING UTF8 + ] ] ]) + args: + - name: '[ value_expression ] [ ORDER BY sort_expression ] [ { NULL | ABSENT + } ON NULL ] [ RETURNING data_type [ FORMAT JSON [ ENCODING UTF8 ] ] ]' + optional: false + type: any + tags: [] + aliases: [] + summary: Behaves in the same way as json_array but as an aggregate function so + it + description: Behaves in the same way as json_array but as an aggregate function + so it only takes one value_expression parameter. If ABSENT ON NULL is specified, + any NULL values are omitted. If ORDER BY is specified, the elements will appear + in the array in that order rather than in the input order. + examples: [] + - name: JSON_OBJECT + category_id: json_functions + category_label: JSON Functions + signature: + display: JSON_OBJECT([ { key_expression { VALUE | ':' } value_expression [ FORMAT + JSON [ ENCODING UTF8 ] ] }[, ...] ] [ { NULL | ABSENT } ON NULL ] [ { WITH + | WITHOUT } UNIQUE [ KEYS ] ] [ RETURNING data_type [ FORMAT JSON [ ENCODING + UTF8 ] ] ]) + args: + - name: '[ { key_expression { VALUE | '':'' } value_expression [ FORMAT JSON + [ ENCODING UTF8 ] ] }[' + optional: false + type: any + - name: '...] ] [ { NULL | ABSENT } ON NULL ] [ { WITH | WITHOUT } UNIQUE [ + KEYS ] ] [ RETURNING data_type [ FORMAT JSON [ ENCODING UTF8 ] ] ]' + optional: false + type: any + tags: [] + aliases: [] + summary: Constructs a JSON object of all the key/value pairs given, or an empty + description: Constructs a JSON object of all the key/value pairs given, or an + empty object if none are given. key_expression is a scalar expression defining + the JSON key, which is converted to the text type. It cannot be NULL nor can + it belong to a type that has a cast to the json type. If WITH UNIQUE KEYS is + specified, there must not be any duplicate key_expression. Any pair for which + the value_expression evaluates to NULL is omitted from the output if ABSENT + ON NULL is specified; if NULL ON NULL is specified or the clause omitted, the + key is included with value NULL. + examples: [] + - name: JSON_OBJECTAGG + category_id: aggregate_functions + category_label: Aggregate Functions + signature: + display: JSON_OBJECTAGG([ { key_expression { VALUE | ':' } value_expression + } ] [ { NULL | ABSENT } ON NULL ] [ { WITH | WITHOUT } UNIQUE [ KEYS ] ] [ + RETURNING data_type [ FORMAT JSON [ ENCODING UTF8 ] ] ]) + args: + - name: '[ { key_expression { VALUE | '':'' } value_expression } ] [ { NULL + | ABSENT } ON NULL ] [ { WITH | WITHOUT } UNIQUE [ KEYS ] ] [ RETURNING + data_type [ FORMAT JSON [ ENCODING UTF8 ] ] ]' + optional: false + type: any + tags: [] + aliases: [] + summary: Behaves like json_object, but as an aggregate function, so it only takes + description: Behaves like json_object, but as an aggregate function, so it only + takes one key_expression and one value_expression parameter. + examples: [] + - name: JSON_SCALAR + category_id: json_functions + category_label: JSON Functions + signature: + display: JSON_SCALAR(expression) + args: + - name: expression + optional: false + type: any + tags: [] + aliases: [] + summary: Converts a given SQL scalar value into a JSON scalar value. + description: Converts a given SQL scalar value into a JSON scalar value. If the + input is NULL, an SQL null is returned. If the input is number or a boolean + value, a corresponding JSON number or boolean value is returned. For any other + value, a JSON string is returned. + examples: [] + - name: JUSTIFY_DAYS + category_id: date_time_functions + category_label: Date/Time Functions + signature: + display: JUSTIFY_DAYS(interval) + args: + - name: interval + optional: false + type: any + tags: [] + aliases: [] + summary: Adjust interval, converting 30-day time periods to months + description: Adjust interval, converting 30-day time periods to months + examples: [] + - name: JUSTIFY_HOURS + category_id: date_time_functions + category_label: Date/Time Functions + signature: + display: JUSTIFY_HOURS(interval) + args: + - name: interval + optional: false + type: any + tags: [] + aliases: [] + summary: Adjust interval, converting 24-hour time periods to days + description: Adjust interval, converting 24-hour time periods to days + examples: [] + - name: JUSTIFY_INTERVAL + category_id: date_time_functions + category_label: Date/Time Functions + signature: + display: JUSTIFY_INTERVAL(interval) + args: + - name: interval + optional: false + type: any + tags: [] + aliases: [] + summary: Adjust interval using justify_days and justify_hours, with additional + sign + description: Adjust interval using justify_days and justify_hours, with additional + sign adjustments + examples: [] + - name: LAG + category_id: window_functions + category_label: Window Functions + signature: + display: LAG(value anycompatible [, offset integer [, default anycompatible + ]]) + args: + - name: value anycompatible [ + optional: false + type: any + - name: offset integer [ + optional: false + type: any + - name: default anycompatible ]] + optional: false + type: any + tags: [] + aliases: [] + summary: Returns value evaluated at the row that is offset rows before the current + description: Returns value evaluated at the row that is offset rows before the + current row within the partition; if there is no such row, instead returns default + (which must be of a type compatible with value). Both offset and default are + evaluated with respect to the current row. If omitted, offset defaults to 1 + and default to NULL. + examples: [] + - name: LASTVAL + category_id: sequence_manipulation_functions + category_label: Sequence Manipulation Functions + signature: + display: LASTVAL + args: [] + tags: [] + aliases: [] + summary: Returns the value most recently returned by nextval in the current session. + description: Returns the value most recently returned by nextval in the current + session. This function is identical to currval, except that instead of taking + the sequence name as an argument it refers to whichever sequence nextval was + most recently applied to in the current session. It is an error to call lastval + if nextval has not yet been called in the current session. + examples: [] + - name: LAST_VALUE + category_id: window_functions + category_label: Window Functions + signature: + display: LAST_VALUE(value anyelement) + args: + - name: value anyelement + optional: false + type: any + tags: [] + aliases: [] + summary: Returns value evaluated at the row that is the last row of the window + description: Returns value evaluated at the row that is the last row of the window + frame. + examples: [] + - name: LCM + category_id: numeric_math_functions + category_label: Numeric/Math Functions + signature: + display: LCM(numeric_type, numeric_type) + args: + - name: numeric_type + optional: false + type: any + - name: numeric_type + optional: false + type: any + tags: [] + aliases: [] + summary: Least common multiple (the smallest strictly positive number that is + an + description: Least common multiple (the smallest strictly positive number that + is an integral multiple of both inputs); returns 0 if either input is zero; + available for integer, bigint, and numeric + examples: [] + - name: LEAD + category_id: window_functions + category_label: Window Functions + signature: + display: LEAD(value anycompatible [, offset integer [, default anycompatible + ]]) + args: + - name: value anycompatible [ + optional: false + type: any + - name: offset integer [ + optional: false + type: any + - name: default anycompatible ]] + optional: false + type: any + tags: [] + aliases: [] + summary: Returns value evaluated at the row that is offset rows after the current + description: Returns value evaluated at the row that is offset rows after the + current row within the partition; if there is no such row, instead returns default + (which must be of a type compatible with value). Both offset and default are + evaluated with respect to the current row. If omitted, offset defaults to 1 + and default to NULL. + examples: [] + - name: LEFT + category_id: string_functions + category_label: String Functions + signature: + display: LEFT(string text, n integer) + args: + - name: string text + optional: false + type: any + - name: n integer + optional: false + type: any + tags: [] + aliases: [] + summary: Returns first n characters in the string, or when n is negative, returns + description: Returns first n characters in the string, or when n is negative, + returns all but last |n| characters. + examples: [] + - name: LENGTH1 + category_id: string_functions + category_label: String Functions + signature: + display: LENGTH1(text) + args: + - name: text + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the number of characters in the string. + description: Returns the number of characters in the string. + examples: [] + - name: LENGTH2 + category_id: geometric_functions + category_label: Geometric Functions + signature: + display: LENGTH2(geometric_type) + args: + - name: geometric_type + optional: false + type: any + tags: [] + aliases: [] + summary: Computes the total length. + description: Computes the total length. Available for lseg, path. + examples: [] + - name: LENGTH3 + category_id: text_search_functions + category_label: Text Search Functions + signature: + display: LENGTH3(tsvector) + args: + - name: tsvector + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the number of lexemes in the tsvector. + description: Returns the number of lexemes in the tsvector. + examples: [] + - name: LINE + category_id: geometric_functions + category_label: Geometric Functions + signature: + display: LINE(point, point) + args: + - name: point + optional: false + type: any + - name: point + optional: false + type: any + tags: [] + aliases: [] + summary: Converts two points to the line through them. + description: Converts two points to the line through them. + examples: [] + - name: LOWER1 + category_id: string_functions + category_label: String Functions + signature: + display: LOWER1(text) + args: + - name: text + optional: false + type: any + tags: [] + aliases: [] + summary: Converts the string to all lower case, according to the rules of the + description: Converts the string to all lower case, according to the rules of + the database's locale. + examples: [] + - name: LOWER2 + category_id: range_functions + category_label: Range Functions + signature: + display: LOWER2(anyrange) + args: + - name: anyrange + optional: false + type: any + tags: [] + aliases: [] + summary: Extracts the lower bound of the range (NULL if the range is empty or + has no + description: Extracts the lower bound of the range (NULL if the range is empty + or has no lower bound). + examples: [] + - name: LOWER3 + category_id: range_functions + category_label: Range Functions + signature: + display: LOWER3(anymultirange) + args: + - name: anymultirange + optional: false + type: any + tags: [] + aliases: [] + summary: Extracts the lower bound of the multirange (NULL if the multirange is + empty + description: Extracts the lower bound of the multirange (NULL if the multirange + is empty has no lower bound). + examples: [] + - name: LOWER_INC1 + category_id: range_functions + category_label: Range Functions + signature: + display: LOWER_INC1(anyrange) + args: + - name: anyrange + optional: false + type: any + tags: [] + aliases: [] + summary: Is the range's lower bound inclusive? + description: Is the range's lower bound inclusive? + examples: [] + - name: LOWER_INC2 + category_id: range_functions + category_label: Range Functions + signature: + display: LOWER_INC2(anymultirange) + args: + - name: anymultirange + optional: false + type: any + tags: [] + aliases: [] + summary: Is the multirange's lower bound inclusive? + description: Is the multirange's lower bound inclusive? + examples: [] + - name: LOWER_INF1 + category_id: range_functions + category_label: Range Functions + signature: + display: LOWER_INF1(anyrange) + args: + - name: anyrange + optional: false + type: any + tags: [] + aliases: [] + summary: Does the range have no lower bound? + description: Does the range have no lower bound? (A lower bound of -Infinity returns + false.) + examples: [] + - name: LOWER_INF2 + category_id: range_functions + category_label: Range Functions + signature: + display: LOWER_INF2(anymultirange) + args: + - name: anymultirange + optional: false + type: any + tags: [] + aliases: [] + summary: Does the multirange have no lower bound? + description: Does the multirange have no lower bound? (A lower bound of -Infinity + returns false.) + examples: [] + - name: LPAD + category_id: string_functions + category_label: String Functions + signature: + display: LPAD(string text, length integer [, fill text ]) + args: + - name: string text + optional: false + type: any + - name: length integer [ + optional: false + type: any + - name: fill text ] + optional: false + type: any + tags: [] + aliases: [] + summary: Extends the string to length length by prepending the characters fill + (a + description: Extends the string to length length by prepending the characters + fill (a space by default). If the string is already longer than length then + it is truncated (on the right). + examples: [] + - name: LSEG + category_id: geometric_functions + category_label: Geometric Functions + signature: + display: LSEG(box) + args: + - name: box + optional: false + type: any + tags: [] + aliases: [] + summary: Extracts box's diagonal as a line segment. + description: Extracts box's diagonal as a line segment. + examples: [] + - name: LTRIM1 + category_id: string_functions + category_label: String Functions + signature: + display: LTRIM1(string text [, characters text ]) + args: + - name: string text [ + optional: false + type: any + - name: characters text ] + optional: false + type: any + tags: [] + aliases: [] + summary: Removes the longest string containing only characters in characters (a + description: Removes the longest string containing only characters in characters + (a space by default) from the start of string. + examples: [] + - name: LTRIM2 + category_id: binary_string_functions + category_label: Binary String Functions + signature: + display: LTRIM2(bytes bytea, bytesremoved bytea) + args: + - name: bytes bytea + optional: false + type: any + - name: bytesremoved bytea + optional: false + type: any + tags: [] + aliases: [] + summary: Removes the longest string containing only bytes appearing in bytesremoved + description: Removes the longest string containing only bytes appearing in bytesremoved + from the start of bytes. + examples: [] + - name: MACADDR8_SET7BIT + category_id: network_address_functions + category_label: Network Address Functions + signature: + display: MACADDR8_SET7BIT(macaddr8) + args: + - name: macaddr8 + optional: false + type: any + tags: [] + aliases: [] + summary: Sets the 7th bit of the address to one, creating what is known as modified + description: Sets the 7th bit of the address to one, creating what is known as + modified EUI-64, for inclusion in an IPv6 address. + examples: [] + - name: MAKEACLITEM + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: MAKEACLITEM(grantee oid, grantor oid, privileges text, is_grantable + boolean) + args: + - name: grantee oid + optional: false + type: any + - name: grantor oid + optional: false + type: any + - name: privileges text + optional: false + type: any + - name: is_grantable boolean + optional: false + type: any + tags: [] + aliases: [] + summary: Constructs an aclitem with the given properties. + description: Constructs an aclitem with the given properties. privileges is a + comma-separated list of privilege names such as SELECT, INSERT, etc, all of + which are set in the result. (Case of the privilege string is not significant, + and extra whitespace is allowed between but not within privilege names.) + examples: [] + - name: MAKE_DATE + category_id: date_time_functions + category_label: Date/Time Functions + signature: + display: MAKE_DATE(year int, month int, day int) + args: + - name: year int + optional: false + type: any + - name: month int + optional: false + type: any + - name: day int + optional: false + type: any + tags: [] + aliases: [] + summary: Create date from year, month and day fields (negative years signify BC) + description: Create date from year, month and day fields (negative years signify + BC) + examples: [] + - name: MAKE_INTERVAL + category_id: date_time_functions + category_label: Date/Time Functions + signature: + display: MAKE_INTERVAL([ years int [, months int [, weeks int [, days int [, + hours int [, mins int [, secs double precision ]]]]]]]) + args: + - name: '[ years int [' + optional: false + type: any + - name: months int [ + optional: false + type: any + - name: weeks int [ + optional: false + type: any + - name: days int [ + optional: false + type: any + - name: hours int [ + optional: false + type: any + - name: mins int [ + optional: false + type: any + - name: secs double precision ]]]]]]] + optional: false + type: any + tags: [] + aliases: [] + summary: Create interval from years, months, weeks, days, hours, minutes and seconds + description: Create interval from years, months, weeks, days, hours, minutes and + seconds fields, each of which can default to zero + examples: [] + - name: MAKE_TIME + category_id: date_time_functions + category_label: Date/Time Functions + signature: + display: MAKE_TIME(hour int, min int, sec double precision) + args: + - name: hour int + optional: false + type: any + - name: min int + optional: false + type: any + - name: sec double precision + optional: false + type: any + tags: [] + aliases: [] + summary: Create time from hour, minute and seconds fields + description: Create time from hour, minute and seconds fields + examples: [] + - name: MAKE_TIMESTAMP + category_id: date_time_functions + category_label: Date/Time Functions + signature: + display: MAKE_TIMESTAMP(year int, month int, day int, hour int, min int, sec + double precision) + args: + - name: year int + optional: false + type: any + - name: month int + optional: false + type: any + - name: day int + optional: false + type: any + - name: hour int + optional: false + type: any + - name: min int + optional: false + type: any + - name: sec double precision + optional: false + type: any + tags: [] + aliases: [] + summary: Create timestamp from year, month, day, hour, minute and seconds fields + description: Create timestamp from year, month, day, hour, minute and seconds + fields (negative years signify BC) + examples: [] + - name: MAKE_TIMESTAMPTZ + category_id: date_time_functions + category_label: Date/Time Functions + signature: + display: MAKE_TIMESTAMPTZ(year int, month int, day int, hour int, min int, sec + double precision [, timezone text ]) + args: + - name: year int + optional: false + type: any + - name: month int + optional: false + type: any + - name: day int + optional: false + type: any + - name: hour int + optional: false + type: any + - name: min int + optional: false + type: any + - name: sec double precision [ + optional: false + type: any + - name: timezone text ] + optional: false + type: any + tags: [] + aliases: [] + summary: Create timestamp with time zone from year, month, day, hour, minute and + description: Create timestamp with time zone from year, month, day, hour, minute + and seconds fields (negative years signify BC). If timezone is not specified, + the current time zone is used; the examples assume the session time zone is + Europe/London + examples: [] + - name: MASKLEN + category_id: network_address_functions + category_label: Network Address Functions + signature: + display: MASKLEN(inet) + args: + - name: inet + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the netmask length in bits. + description: Returns the netmask length in bits. + examples: [] + - name: MAX + category_id: aggregate_functions + category_label: Aggregate Functions + signature: + display: MAX(see text) + args: + - name: see text + optional: false + type: any + tags: [] + aliases: [] + summary: Computes the maximum of the non-null input values. + description: Computes the maximum of the non-null input values. Available for + any numeric, string, date/time, or enum type, as well as inet, interval, money, + oid, pg_lsn, tid, xid8, and arrays of any of these types. + examples: [] + - name: MD51 + category_id: string_functions + category_label: String Functions + signature: + display: MD51(text) + args: + - name: text + optional: false + type: any + tags: [] + aliases: [] + summary: Computes the MD5 hash of the argument, with the result written in + description: Computes the MD5 hash of the argument, with the result written in + hexadecimal. + examples: [] + - name: MD52 + category_id: binary_string_functions + category_label: Binary String Functions + signature: + display: MD52(bytea) + args: + - name: bytea + optional: false + type: any + tags: [] + aliases: [] + summary: Computes the MD5 hash of the binary string, with the result written in + description: Computes the MD5 hash of the binary string, with the result written + in hexadecimal. + examples: [] + - name: MERGE_ACTION + category_id: merge_support_functions + category_label: Merge Support Functions + signature: + display: MERGE_ACTION + args: [] + tags: [] + aliases: [] + summary: Returns the merge action command executed for the current row. + description: Returns the merge action command executed for the current row. This + will be 'INSERT', 'UPDATE', or 'DELETE'. + examples: [] + - name: MIN + category_id: aggregate_functions + category_label: Aggregate Functions + signature: + display: MIN(see text) + args: + - name: see text + optional: false + type: any + tags: [] + aliases: [] + summary: Computes the minimum of the non-null input values. + description: Computes the minimum of the non-null input values. Available for + any numeric, string, date/time, or enum type, as well as inet, interval, money, + oid, pg_lsn, tid, xid8, and arrays of any of these types. + examples: [] + - name: MIN_SCALE + category_id: numeric_math_functions + category_label: Numeric/Math Functions + signature: + display: MIN_SCALE(numeric) + args: + - name: numeric + optional: false + type: any + tags: [] + aliases: [] + summary: Minimum scale (number of fractional decimal digits) needed to represent + the + description: Minimum scale (number of fractional decimal digits) needed to represent + the supplied value precisely + examples: [] + - name: MOD + category_id: numeric_math_functions + category_label: Numeric/Math Functions + signature: + display: MOD(y numeric_type, x numeric_type) + args: + - name: y numeric_type + optional: false + type: any + - name: x numeric_type + optional: false + type: any + tags: [] + aliases: [] + summary: Remainder of y/x; available for smallint, integer, bigint, and numeric + description: Remainder of y/x; available for smallint, integer, bigint, and numeric + examples: [] + - name: MODE + category_id: aggregate_functions + category_label: Aggregate Functions + signature: + display: MODE + args: [] + tags: [] + aliases: [] + summary: Computes the mode, the most frequent value of the aggregated argument + description: Computes the mode, the most frequent value of the aggregated argument + (arbitrarily choosing the first one if there are multiple equally-frequent values). + The aggregated argument must be of a sortable type. + examples: [] + - name: MULTIRANGE + category_id: range_functions + category_label: Range Functions + signature: + display: MULTIRANGE(anyrange) + args: + - name: anyrange + optional: false + type: any + tags: [] + aliases: [] + summary: Returns a multirange containing just the given range. + description: Returns a multirange containing just the given range. + examples: [] + - name: MXID_AGE + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: MXID_AGE(xid) + args: + - name: xid + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the number of multixacts IDs between the supplied multixact ID + and + description: Returns the number of multixacts IDs between the supplied multixact + ID and the current multixacts counter. + examples: [] + - name: NETMASK + category_id: network_address_functions + category_label: Network Address Functions + signature: + display: NETMASK(inet) + args: + - name: inet + optional: false + type: any + tags: [] + aliases: [] + summary: Computes the network mask for the address's network. + description: Computes the network mask for the address's network. + examples: [] + - name: NETWORK + category_id: network_address_functions + category_label: Network Address Functions + signature: + display: NETWORK(inet) + args: + - name: inet + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the network part of the address, zeroing out whatever is to the + description: Returns the network part of the address, zeroing out whatever is + to the right of the netmask. (This is equivalent to casting the value to cidr.) + examples: [] + - name: NEXTVAL + category_id: sequence_manipulation_functions + category_label: Sequence Manipulation Functions + signature: + display: NEXTVAL(regclass) + args: + - name: regclass + optional: false + type: any + tags: [] + aliases: [] + summary: Advances the sequence object to its next value and returns that value. + description: 'Advances the sequence object to its next value and returns that + value. This is done atomically: even if multiple sessions execute nextval concurrently, + each will safely receive a distinct sequence value. If the sequence object has + been created with default parameters, successive nextval calls will return successive + values beginning with 1. Other behaviors can be obtained by using appropriate + parameters in the CREATE SEQUENCE command.' + examples: [] + - name: NOW + category_id: date_time_functions + category_label: Date/Time Functions + signature: + display: NOW + args: [] + tags: [] + aliases: [] + summary: Current date and time (start of current transaction); see Section 9.9.5 + description: Current date and time (start of current transaction); see Section + 9.9.5 + examples: [] + - name: NPOINTS + category_id: geometric_functions + category_label: Geometric Functions + signature: + display: NPOINTS(geometric_type) + args: + - name: geometric_type + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the number of points. + description: Returns the number of points. Available for path, polygon. + examples: [] + - name: NTH_VALUE + category_id: window_functions + category_label: Window Functions + signature: + display: NTH_VALUE(value anyelement, n integer) + args: + - name: value anyelement + optional: false + type: any + - name: n integer + optional: false + type: any + tags: [] + aliases: [] + summary: Returns value evaluated at the row that is the n'th row of the window + frame + description: Returns value evaluated at the row that is the n'th row of the window + frame (counting from 1); returns NULL if there is no such row. + examples: [] + - name: NTILE + category_id: window_functions + category_label: Window Functions + signature: + display: NTILE(num_buckets integer) + args: + - name: num_buckets integer + optional: false + type: any + tags: [] + aliases: [] + summary: Returns an integer ranging from 1 to the argument value, dividing the + description: Returns an integer ranging from 1 to the argument value, dividing + the partition as equally as possible. + examples: [] + - name: NUMNODE + category_id: text_search_functions + category_label: Text Search Functions + signature: + display: NUMNODE(tsquery) + args: + - name: tsquery + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the number of lexemes plus operators in the tsquery. + description: Returns the number of lexemes plus operators in the tsquery. + examples: [] + - name: OBJ_DESCRIPTION + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: OBJ_DESCRIPTION(object oid, catalog name) + args: + - name: object oid + optional: false + type: any + - name: catalog name + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the comment for a database object specified by its OID and the + name + description: Returns the comment for a database object specified by its OID and + the name of the containing system catalog. For example, obj_description(123456, + 'pg_class') would retrieve the comment for the table with OID 123456. + examples: [] + - name: OCTET_LENGTH1 + category_id: string_functions + category_label: String Functions + signature: + display: OCTET_LENGTH1(text) + args: + - name: text + optional: false + type: any + tags: [] + aliases: [] + summary: Returns number of bytes in the string. + description: Returns number of bytes in the string. + examples: [] + - name: OCTET_LENGTH2 + category_id: string_functions + category_label: String Functions + signature: + display: OCTET_LENGTH2(character) + args: + - name: character + optional: false + type: any + tags: [] + aliases: [] + summary: Returns number of bytes in the string. + description: Returns number of bytes in the string. Since this version of the + function accepts type character directly, it will not strip trailing spaces. + examples: [] + - name: OCTET_LENGTH3 + category_id: binary_string_functions + category_label: Binary String Functions + signature: + display: OCTET_LENGTH3(bytea) + args: + - name: bytea + optional: false + type: any + tags: [] + aliases: [] + summary: Returns number of bytes in the binary string. + description: Returns number of bytes in the binary string. + examples: [] + - name: OCTET_LENGTH4 + category_id: bit_string_functions + category_label: Bit String Functions + signature: + display: OCTET_LENGTH4(bit) + args: + - name: bit + optional: false + type: any + tags: [] + aliases: [] + summary: Returns number of bytes in the bit string. + description: Returns number of bytes in the bit string. + examples: [] + - name: OVERLAY1 + category_id: string_functions + category_label: String Functions + signature: + display: OVERLAY1(string text PLACING newsubstring text FROM start integer [ + FOR count integer ]) + args: + - name: string text PLACING newsubstring text FROM start integer [ FOR count + integer ] + optional: false + type: any + tags: [] + aliases: [] + summary: Replaces the substring of string that starts at the start'th character + and + description: Replaces the substring of string that starts at the start'th character + and extends for count characters with newsubstring. If count is omitted, it + defaults to the length of newsubstring. + examples: [] + - name: OVERLAY2 + category_id: binary_string_functions + category_label: Binary String Functions + signature: + display: OVERLAY2(bytes bytea PLACING newsubstring bytea FROM start integer + [ FOR count integer ]) + args: + - name: bytes bytea PLACING newsubstring bytea FROM start integer [ FOR count + integer ] + optional: false + type: any + tags: [] + aliases: [] + summary: Replaces the substring of bytes that starts at the start'th byte and + description: Replaces the substring of bytes that starts at the start'th byte + and extends for count bytes with newsubstring. If count is omitted, it defaults + to the length of newsubstring. + examples: [] + - name: OVERLAY3 + category_id: bit_string_functions + category_label: Bit String Functions + signature: + display: OVERLAY3(bits bit PLACING newsubstring bit FROM start integer [ FOR + count integer ]) + args: + - name: bits bit PLACING newsubstring bit FROM start integer [ FOR count integer + ] + optional: false + type: any + tags: [] + aliases: [] + summary: Replaces the substring of bits that starts at the start'th bit and extends + description: Replaces the substring of bits that starts at the start'th bit and + extends for count bits with newsubstring. If count is omitted, it defaults to + the length of newsubstring. + examples: [] + - name: PARSE_IDENT + category_id: string_functions + category_label: String Functions + signature: + display: PARSE_IDENT(qualified_identifier text [, strict_mode boolean DEFAULT + true ]) + args: + - name: qualified_identifier text [ + optional: false + type: any + - name: strict_mode boolean DEFAULT true ] + optional: false + type: any + tags: [] + aliases: [] + summary: Splits qualified_identifier into an array of identifiers, removing any + description: Splits qualified_identifier into an array of identifiers, removing + any quoting of individual identifiers. By default, extra characters after the + last identifier are considered an error; but if the second parameter is false, + then such extra characters are ignored. (This behavior is useful for parsing + names for objects like functions.) Note that this function does not truncate + over-length identifiers. If you want truncation you can cast the result to name[]. + examples: [] + - name: PATH + category_id: geometric_functions + category_label: Geometric Functions + signature: + display: PATH(polygon) + args: + - name: polygon + optional: false + type: any + tags: [] + aliases: [] + summary: Converts polygon to a closed path with the same list of points. + description: Converts polygon to a closed path with the same list of points. + examples: [] + - name: PCLOSE + category_id: geometric_functions + category_label: Geometric Functions + signature: + display: PCLOSE(path) + args: + - name: path + optional: false + type: any + tags: [] + aliases: [] + summary: Converts path to closed form. + description: Converts path to closed form. + examples: [] + - name: PERCENTILE_DISC + category_id: aggregate_functions + category_label: Aggregate Functions + signature: + display: PERCENTILE_DISC(fraction double precision) + args: + - name: fraction double precision + optional: false + type: any + tags: [] + aliases: [] + summary: Computes the discrete percentile, the first value within the ordered + set of + description: Computes the discrete percentile, the first value within the ordered + set of aggregated argument values whose position in the ordering equals or exceeds + the specified fraction. The aggregated argument must be of a sortable type. + examples: [] + - name: PERCENT_RANK1 + category_id: aggregate_functions + category_label: Aggregate Functions + signature: + display: PERCENT_RANK1(args) + args: + - name: args + optional: false + type: any + tags: [] + aliases: [] + summary: Computes the relative rank of the hypothetical row, that is (rank - 1) + / + description: Computes the relative rank of the hypothetical row, that is (rank + - 1) / (total rows - 1). The value thus ranges from 0 to 1 inclusive. + examples: [] + - name: PERCENT_RANK2 + category_id: window_functions + category_label: Window Functions + signature: + display: PERCENT_RANK2 + args: [] + tags: [] + aliases: [] + summary: Returns the relative rank of the current row, that is (rank - 1) / (total + description: Returns the relative rank of the current row, that is (rank - 1) + / (total partition rows - 1). The value thus ranges from 0 to 1 inclusive. + examples: [] + - name: PG_ADVISORY_UNLOCK_ALL + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_ADVISORY_UNLOCK_ALL + args: [] + tags: [] + aliases: [] + summary: Releases all session-level advisory locks held by the current session. + description: Releases all session-level advisory locks held by the current session. + (This function is implicitly invoked at session end, even if the client disconnects + ungracefully.) + examples: [] + - name: PG_AVAILABLE_WAL_SUMMARIES + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_AVAILABLE_WAL_SUMMARIES + args: [] + tags: [] + aliases: [] + summary: Returns information about the WAL summary files present in the data + description: Returns information about the WAL summary files present in the data + directory, under pg_wal/summaries. One row will be returned per WAL summary + file. Each file summarizes WAL on the indicated TLI within the indicated LSN + range. This function might be useful to determine whether enough WAL summaries + are present on the server to take an incremental backup based on some prior + backup whose start LSN is known. + examples: [] + - name: PG_BACKEND_PID + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_BACKEND_PID + args: [] + tags: [] + aliases: [] + summary: Returns the process ID of the server process attached to the current + description: Returns the process ID of the server process attached to the current + session. + examples: [] + - name: PG_BACKUP_START + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_BACKUP_START(label text [, fast boolean ]) + args: + - name: label text [ + optional: false + type: any + - name: fast boolean ] + optional: false + type: any + tags: [] + aliases: [] + summary: Prepares the server to begin an on-line backup. + description: Prepares the server to begin an on-line backup. The only required + parameter is an arbitrary user-defined label for the backup. (Typically this + would be the name under which the backup dump file will be stored.) If the optional + second parameter is given as true, it specifies executing pg_backup_start as + quickly as possible. This forces an immediate checkpoint which will cause a + spike in I/O operations, slowing any concurrently executing queries. + examples: [] + - name: PG_BACKUP_STOP + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_BACKUP_STOP([wait_for_archive boolean ]) + args: + - name: '[wait_for_archive boolean ]' + optional: false + type: any + tags: [] + aliases: [] + summary: Finishes performing an on-line backup. + description: Finishes performing an on-line backup. The desired contents of the + backup label file and the tablespace map file are returned as part of the result + of the function and must be written to files in the backup area. These files + must not be written to the live data directory (doing so will cause PostgreSQL + to fail to restart in the event of a crash). + examples: [] + - name: PG_BASETYPE + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_BASETYPE(regtype) + args: + - name: regtype + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the OID of the base type of a domain identified by its type OID. + description: Returns the OID of the base type of a domain identified by its type + OID. If the argument is the OID of a non-domain type, returns the argument as-is. + Returns NULL if the argument is not a valid type OID. If there's a chain of + domain dependencies, it will recurse until finding the base type. + examples: [] + - name: PG_BLOCKING_PIDS + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_BLOCKING_PIDS(integer) + args: + - name: integer + optional: false + type: any + tags: [] + aliases: [] + summary: Returns an array of the process ID(s) of the sessions that are blocking + the + description: Returns an array of the process ID(s) of the sessions that are blocking + the server process with the specified process ID from acquiring a lock, or an + empty array if there is no such server process or it is not blocked. + examples: [] + - name: PG_CANCEL_BACKEND + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_CANCEL_BACKEND(pid integer) + args: + - name: pid integer + optional: false + type: any + tags: [] + aliases: [] + summary: Cancels the current query of the session whose backend process has the + description: Cancels the current query of the session whose backend process has + the specified process ID. This is also allowed if the calling role is a member + of the role whose backend is being canceled or the calling role has privileges + of pg_signal_backend, however only superusers can cancel superuser backends. + examples: [] + - name: PG_CHAR_TO_ENCODING + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_CHAR_TO_ENCODING(encoding name) + args: + - name: encoding name + optional: false + type: any + tags: [] + aliases: [] + summary: Converts the supplied encoding name into an integer representing the + description: Converts the supplied encoding name into an integer representing + the internal identifier used in some system catalog tables. Returns -1 if an + unknown encoding name is provided. + examples: [] + - name: PG_CLIENT_ENCODING + category_id: string_functions + category_label: String Functions + signature: + display: PG_CLIENT_ENCODING + args: [] + tags: [] + aliases: [] + summary: Returns current client encoding name. + description: Returns current client encoding name. + examples: [] + - name: PG_COLLATION_ACTUAL_VERSION + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_COLLATION_ACTUAL_VERSION(oid) + args: + - name: oid + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the actual version of the collation object as it is currently + description: Returns the actual version of the collation object as it is currently + installed in the operating system. If this is different from the value in pg_collation.collversion, + then objects depending on the collation might need to be rebuilt. See also ALTER + COLLATION. + examples: [] + - name: PG_COLLATION_IS_VISIBLE + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_COLLATION_IS_VISIBLE(collation oid) + args: + - name: collation oid + optional: false + type: any + tags: [] + aliases: [] + summary: Is collation visible in search path? + description: Is collation visible in search path? + examples: [] + - name: PG_COLUMN_COMPRESSION + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_COLUMN_COMPRESSION("any") + args: + - name: '"any"' + optional: false + type: any + tags: [] + aliases: [] + summary: Shows the compression algorithm that was used to compress an individual + description: Shows the compression algorithm that was used to compress an individual + variable-length value. Returns NULL if the value is not compressed. + examples: [] + - name: PG_COLUMN_SIZE + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_COLUMN_SIZE("any") + args: + - name: '"any"' + optional: false + type: any + tags: [] + aliases: [] + summary: Shows the number of bytes used to store any individual data value. + description: Shows the number of bytes used to store any individual data value. + If applied directly to a table column value, this reflects any compression that + was done. + examples: [] + - name: PG_COLUMN_TOAST_CHUNK_ID + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_COLUMN_TOAST_CHUNK_ID("any") + args: + - name: '"any"' + optional: false + type: any + tags: [] + aliases: [] + summary: Shows the chunk_id of an on-disk TOASTed value. + description: Shows the chunk_id of an on-disk TOASTed value. Returns NULL if the + value is un-TOASTed or not on-disk. See Section 65.2 for more information about + TOAST. + examples: [] + - name: PG_CONF_LOAD_TIME + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_CONF_LOAD_TIME + args: [] + tags: [] + aliases: [] + summary: Returns the time when the server configuration files were last loaded. + description: Returns the time when the server configuration files were last loaded. + If the current session was alive at the time, this will be the time when the + session itself re-read the configuration files (so the reading will vary a little + in different sessions). Otherwise it is the time when the postmaster process + re-read the configuration files. + examples: [] + - name: PG_CONTROL_CHECKPOINT + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_CONTROL_CHECKPOINT + args: [] + tags: [] + aliases: [] + summary: Returns information about current checkpoint state, as shown in Table + 9.87. + description: Returns information about current checkpoint state, as shown in Table + 9.87. + examples: [] + - name: PG_CONTROL_INIT + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_CONTROL_INIT + args: [] + tags: [] + aliases: [] + summary: Returns information about cluster initialization state, as shown in Table + description: Returns information about cluster initialization state, as shown + in Table 9.89. + examples: [] + - name: PG_CONTROL_RECOVERY + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_CONTROL_RECOVERY + args: [] + tags: [] + aliases: [] + summary: Returns information about recovery state, as shown in Table 9.90. + description: Returns information about recovery state, as shown in Table 9.90. + examples: [] + - name: PG_CONTROL_SYSTEM + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_CONTROL_SYSTEM + args: [] + tags: [] + aliases: [] + summary: Returns information about current control file state, as shown in Table + description: Returns information about current control file state, as shown in + Table 9.88. + examples: [] + - name: PG_CONVERSION_IS_VISIBLE + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_CONVERSION_IS_VISIBLE(conversion oid) + args: + - name: conversion oid + optional: false + type: any + tags: [] + aliases: [] + summary: Is conversion visible in search path? + description: Is conversion visible in search path? + examples: [] + - name: PG_COPY_LOGICAL_REPLICATION_SLOT + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_COPY_LOGICAL_REPLICATION_SLOT(src_slot_name name, dst_slot_name + name [, temporary boolean [, plugin name ]]) + args: + - name: src_slot_name name + optional: false + type: any + - name: dst_slot_name name [ + optional: false + type: any + - name: temporary boolean [ + optional: false + type: any + - name: plugin name ]] + optional: false + type: any + tags: [] + aliases: [] + summary: Copies an existing logical replication slot named src_slot_name to a + description: Copies an existing logical replication slot named src_slot_name to + a logical replication slot named dst_slot_name, optionally changing the output + plugin and persistence. The copied logical slot starts from the same LSN as + the source logical slot. Both temporary and plugin are optional; if they are + omitted, the values of the source slot are used. + examples: [] + - name: PG_COPY_PHYSICAL_REPLICATION_SLOT + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_COPY_PHYSICAL_REPLICATION_SLOT(src_slot_name name, dst_slot_name + name [, temporary boolean ]) + args: + - name: src_slot_name name + optional: false + type: any + - name: dst_slot_name name [ + optional: false + type: any + - name: temporary boolean ] + optional: false + type: any + tags: [] + aliases: [] + summary: Copies an existing physical replication slot named src_slot_name to a + description: Copies an existing physical replication slot named src_slot_name + to a physical replication slot named dst_slot_name. The copied physical slot + starts to reserve WAL from the same LSN as the source slot. temporary is optional. + If temporary is omitted, the same value as the source slot is used. + examples: [] + - name: PG_CREATE_LOGICAL_REPLICATION_SLOT + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_CREATE_LOGICAL_REPLICATION_SLOT(slot_name name, plugin name [, temporary + boolean, twophase boolean, failover boolean ]) + args: + - name: slot_name name + optional: false + type: any + - name: plugin name [ + optional: false + type: any + - name: temporary boolean + optional: false + type: any + - name: twophase boolean + optional: false + type: any + - name: failover boolean ] + optional: false + type: any + tags: [] + aliases: [] + summary: Creates a new logical (decoding) replication slot named slot_name using + the + description: Creates a new logical (decoding) replication slot named slot_name + using the output plugin plugin. The optional third parameter, temporary, when + set to true, specifies that the slot should not be permanently stored to disk + and is only meant for use by the current session. Temporary slots are also released + upon any error. The optional fourth parameter, twophase, when set to true, specifies + that the decoding of prepared transactions is enabled for this slot. The optional + fifth parameter, failover, when set to true, specifies that this slot is enabled + to be synced to the standbys so that logical replication can be resumed after + failover. A call to this function has the same effect as the replication protocol + command CREATE_REPLICATION_SLOT ... LOGICAL. + examples: [] + - name: PG_CREATE_PHYSICAL_REPLICATION_SLOT + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_CREATE_PHYSICAL_REPLICATION_SLOT(slot_name name [, immediately_reserve + boolean, temporary boolean ]) + args: + - name: slot_name name [ + optional: false + type: any + - name: immediately_reserve boolean + optional: false + type: any + - name: temporary boolean ] + optional: false + type: any + tags: [] + aliases: [] + summary: Creates a new physical replication slot named slot_name. + description: Creates a new physical replication slot named slot_name. The optional + second parameter, when true, specifies that the LSN for this replication slot + be reserved immediately; otherwise the LSN is reserved on first connection from + a streaming replication client. Streaming changes from a physical slot is only + possible with the streaming-replication protocol - see Section 53.4. The optional + third parameter, temporary, when set to true, specifies that the slot should + not be permanently stored to disk and is only meant for use by the current session. + Temporary slots are also released upon any error. This function corresponds + to the replication protocol command CREATE_REPLICATION_SLOT ... PHYSICAL. + examples: [] + - name: PG_CREATE_RESTORE_POINT + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_CREATE_RESTORE_POINT(name text) + args: + - name: name text + optional: false + type: any + tags: [] + aliases: [] + summary: Creates a named marker record in the write-ahead log that can later be + used + description: Creates a named marker record in the write-ahead log that can later + be used as a recovery target, and returns the corresponding write-ahead log + location. The given name can then be used with recovery_target_name to specify + the point up to which recovery will proceed. Avoid creating multiple restore + points with the same name, since recovery will stop at the first one whose name + matches the recovery target. + examples: [] + - name: PG_CURRENT_SNAPSHOT + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_CURRENT_SNAPSHOT + args: [] + tags: [] + aliases: [] + summary: Returns a current snapshot, a data structure showing which transaction + IDs + description: Returns a current snapshot, a data structure showing which transaction + IDs are now in-progress. Only top-level transaction IDs are included in the + snapshot; subtransaction IDs are not shown; see Section 66.3 for details. + examples: [] + - name: PG_CURRENT_WAL_FLUSH_LSN + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_CURRENT_WAL_FLUSH_LSN + args: [] + tags: [] + aliases: [] + summary: Returns the current write-ahead log flush location (see notes below). + description: Returns the current write-ahead log flush location (see notes below). + examples: [] + - name: PG_CURRENT_WAL_INSERT_LSN + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_CURRENT_WAL_INSERT_LSN + args: [] + tags: [] + aliases: [] + summary: Returns the current write-ahead log insert location (see notes below). + description: Returns the current write-ahead log insert location (see notes below). + examples: [] + - name: PG_CURRENT_WAL_LSN + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_CURRENT_WAL_LSN + args: [] + tags: [] + aliases: [] + summary: Returns the current write-ahead log write location (see notes below). + description: Returns the current write-ahead log write location (see notes below). + examples: [] + - name: PG_CURRENT_XACT_ID + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_CURRENT_XACT_ID + args: [] + tags: [] + aliases: [] + summary: Returns the current transaction's ID. + description: Returns the current transaction's ID. It will assign a new one if + the current transaction does not have one already (because it has not performed + any database updates); see Section 66.1 for details. If executed in a subtransaction, + this will return the top-level transaction ID; see Section 66.3 for details. + examples: [] + - name: PG_CURRENT_XACT_ID_IF_ASSIGNED + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_CURRENT_XACT_ID_IF_ASSIGNED + args: [] + tags: [] + aliases: [] + summary: Returns the current transaction's ID, or NULL if no ID is assigned yet. + description: Returns the current transaction's ID, or NULL if no ID is assigned + yet. (It's best to use this variant if the transaction might otherwise be read-only, + to avoid unnecessary consumption of an XID.) If executed in a subtransaction, + this will return the top-level transaction ID. + examples: [] + - name: PG_DATABASE_COLLATION_ACTUAL_VERSION + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_DATABASE_COLLATION_ACTUAL_VERSION(oid) + args: + - name: oid + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the actual version of the database's collation as it is currently + description: Returns the actual version of the database's collation as it is currently + installed in the operating system. If this is different from the value in pg_database.datcollversion, + then objects depending on the collation might need to be rebuilt. See also ALTER + DATABASE. + examples: [] + - name: PG_DESCRIBE_OBJECT + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_DESCRIBE_OBJECT(classid oid, objid oid, objsubid integer) + args: + - name: classid oid + optional: false + type: any + - name: objid oid + optional: false + type: any + - name: objsubid integer + optional: false + type: any + tags: [] + aliases: [] + summary: Returns a textual description of a database object identified by catalog + description: Returns a textual description of a database object identified by + catalog OID, object OID, and sub-object ID (such as a column number within a + table; the sub-object ID is zero when referring to a whole object). This description + is intended to be human-readable, and might be translated, depending on server + configuration. This is especially useful to determine the identity of an object + referenced in the pg_depend catalog. This function returns NULL values for undefined + objects. + examples: [] + - name: PG_DROP_REPLICATION_SLOT + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_DROP_REPLICATION_SLOT(slot_name name) + args: + - name: slot_name name + optional: false + type: any + tags: [] + aliases: [] + summary: Drops the physical or logical replication slot named slot_name. + description: Drops the physical or logical replication slot named slot_name. Same + as replication protocol command DROP_REPLICATION_SLOT. For logical slots, this + must be called while connected to the same database the slot was created on. + examples: [] + - name: PG_ENCODING_TO_CHAR + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_ENCODING_TO_CHAR(encoding integer) + args: + - name: encoding integer + optional: false + type: any + tags: [] + aliases: [] + summary: Converts the integer used as the internal identifier of an encoding in + some + description: Converts the integer used as the internal identifier of an encoding + in some system catalog tables into a human-readable string. Returns an empty + string if an invalid encoding number is provided. + examples: [] + - name: PG_EXPORT_SNAPSHOT + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_EXPORT_SNAPSHOT + args: [] + tags: [] + aliases: [] + summary: Saves the transaction's current snapshot and returns a text string + description: Saves the transaction's current snapshot and returns a text string + identifying the snapshot. This string must be passed (outside the database) + to clients that want to import the snapshot. The snapshot is available for import + only until the end of the transaction that exported it. + examples: [] + - name: PG_FILENODE_RELATION + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_FILENODE_RELATION(tablespace oid, filenode oid) + args: + - name: tablespace oid + optional: false + type: any + - name: filenode oid + optional: false + type: any + tags: [] + aliases: [] + summary: Returns a relation's OID given the tablespace OID and filenode it is + stored + description: Returns a relation's OID given the tablespace OID and filenode it + is stored under. This is essentially the inverse mapping of pg_relation_filepath. + For a relation in the database's default tablespace, the tablespace can be specified + as zero. Returns NULL if no relation in the current database is associated with + the given values. + examples: [] + - name: PG_FUNCTION_IS_VISIBLE + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_FUNCTION_IS_VISIBLE(function oid) + args: + - name: function oid + optional: false + type: any + tags: [] + aliases: [] + summary: Is function visible in search path? + description: Is function visible in search path? (This also works for procedures + and aggregates.) + examples: [] + - name: PG_GET_CATALOG_FOREIGN_KEYS + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_GET_CATALOG_FOREIGN_KEYS + args: [] + tags: [] + aliases: [] + summary: Returns a set of records describing the foreign key relationships that + description: Returns a set of records describing the foreign key relationships + that exist within the PostgreSQL system catalogs. The fktable column contains + the name of the referencing catalog, and the fkcols column contains the name(s) + of the referencing column(s). Similarly, the pktable column contains the name + of the referenced catalog, and the pkcols column contains the name(s) of the + referenced column(s). If is_array is true, the last referencing column is an + array, each of whose elements should match some entry in the referenced catalog. + If is_opt is true, the referencing column(s) are allowed to contain zeroes instead + of a valid reference. + examples: [] + - name: PG_GET_CONSTRAINTDEF + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_GET_CONSTRAINTDEF(constraint oid [, pretty boolean ]) + args: + - name: constraint oid [ + optional: false + type: any + - name: pretty boolean ] + optional: false + type: any + tags: [] + aliases: [] + summary: Reconstructs the creating command for a constraint. + description: Reconstructs the creating command for a constraint. (This is a decompiled + reconstruction, not the original text of the command.) + examples: [] + - name: PG_GET_EXPR + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_GET_EXPR(expr pg_node_tree, relation oid [, pretty boolean ]) + args: + - name: expr pg_node_tree + optional: false + type: any + - name: relation oid [ + optional: false + type: any + - name: pretty boolean ] + optional: false + type: any + tags: [] + aliases: [] + summary: Decompiles the internal form of an expression stored in the system + description: Decompiles the internal form of an expression stored in the system + catalogs, such as the default value for a column. If the expression might contain + Vars, specify the OID of the relation they refer to as the second parameter; + if no Vars are expected, passing zero is sufficient. + examples: [] + - name: PG_GET_FUNCTIONDEF + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_GET_FUNCTIONDEF(func oid) + args: + - name: func oid + optional: false + type: any + tags: [] + aliases: [] + summary: Reconstructs the creating command for a function or procedure. + description: Reconstructs the creating command for a function or procedure. (This + is a decompiled reconstruction, not the original text of the command.) The result + is a complete CREATE OR REPLACE FUNCTION or CREATE OR REPLACE PROCEDURE statement. + examples: [] + - name: PG_GET_FUNCTION_ARGUMENTS + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_GET_FUNCTION_ARGUMENTS(func oid) + args: + - name: func oid + optional: false + type: any + tags: [] + aliases: [] + summary: Reconstructs the argument list of a function or procedure, in the form + it + description: Reconstructs the argument list of a function or procedure, in the + form it would need to appear in within CREATE FUNCTION (including default values). + examples: [] + - name: PG_GET_FUNCTION_IDENTITY_ARGUMENTS + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_GET_FUNCTION_IDENTITY_ARGUMENTS(func oid) + args: + - name: func oid + optional: false + type: any + tags: [] + aliases: [] + summary: Reconstructs the argument list necessary to identify a function or + description: Reconstructs the argument list necessary to identify a function or + procedure, in the form it would need to appear in within commands such as ALTER + FUNCTION. This form omits default values. + examples: [] + - name: PG_GET_FUNCTION_RESULT + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_GET_FUNCTION_RESULT(func oid) + args: + - name: func oid + optional: false + type: any + tags: [] + aliases: [] + summary: Reconstructs the RETURNS clause of a function, in the form it would need + to + description: Reconstructs the RETURNS clause of a function, in the form it would + need to appear in within CREATE FUNCTION. Returns NULL for a procedure. + examples: [] + - name: PG_GET_INDEXDEF + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_GET_INDEXDEF(index oid [, column integer, pretty boolean ]) + args: + - name: index oid [ + optional: false + type: any + - name: column integer + optional: false + type: any + - name: pretty boolean ] + optional: false + type: any + tags: [] + aliases: [] + summary: Reconstructs the creating command for an index. + description: Reconstructs the creating command for an index. (This is a decompiled + reconstruction, not the original text of the command.) If column is supplied + and is not zero, only the definition of that column is reconstructed. + examples: [] + - name: PG_GET_KEYWORDS + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_GET_KEYWORDS + args: [] + tags: [] + aliases: [] + summary: Returns a set of records describing the SQL keywords recognized by the + description: 'Returns a set of records describing the SQL keywords recognized + by the server. The word column contains the keyword. The catcode column contains + a category code: U for an unreserved keyword, C for a keyword that can be a + column name, T for a keyword that can be a type or function name, or R for a + fully reserved keyword. The barelabel column contains true if the keyword can + be used as a "bare" column label in SELECT lists, or false if it can only be + used after AS. The catdesc column contains a possibly-localized string describing + the keyword''s category. The baredesc column contains a possibly-localized string + describing the keyword''s column label status.' + examples: [] + - name: PG_GET_OBJECT_ADDRESS + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_GET_OBJECT_ADDRESS(type text, object_names text[], object_args text[]) + args: + - name: type text + optional: false + type: any + - name: object_names text[] + optional: false + type: any + - name: object_args text[] + optional: false + type: any + tags: [] + aliases: [] + summary: Returns a row containing enough information to uniquely identify the + description: Returns a row containing enough information to uniquely identify + the database object specified by a type code and object name and argument arrays. + The returned values are the ones that would be used in system catalogs such + as pg_depend; they can be passed to other system functions such as pg_describe_object + or pg_identify_object. classid is the OID of the system catalog containing the + object; objid is the OID of the object itself, and objsubid is the sub-object + ID, or zero if none. This function is the inverse of pg_identify_object_as_address. + Undefined objects are identified with NULL values. + examples: [] + - name: PG_GET_PARTKEYDEF + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_GET_PARTKEYDEF(table oid) + args: + - name: table oid + optional: false + type: any + tags: [] + aliases: [] + summary: Reconstructs the definition of a partitioned table's partition key, in + the + description: Reconstructs the definition of a partitioned table's partition key, + in the form it would have in the PARTITION BY clause of CREATE TABLE. (This + is a decompiled reconstruction, not the original text of the command.) + examples: [] + - name: PG_GET_RULEDEF + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_GET_RULEDEF(rule oid [, pretty boolean ]) + args: + - name: rule oid [ + optional: false + type: any + - name: pretty boolean ] + optional: false + type: any + tags: [] + aliases: [] + summary: Reconstructs the creating command for a rule. + description: Reconstructs the creating command for a rule. (This is a decompiled + reconstruction, not the original text of the command.) + examples: [] + - name: PG_GET_SERIAL_SEQUENCE + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_GET_SERIAL_SEQUENCE(table text, column text) + args: + - name: table text + optional: false + type: any + - name: column text + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the name of the sequence associated with a column, or NULL if + no + description: Returns the name of the sequence associated with a column, or NULL + if no sequence is associated with the column. If the column is an identity column, + the associated sequence is the sequence internally created for that column. + For columns created using one of the serial types (serial, smallserial, bigserial), + it is the sequence created for that serial column definition. In the latter + case, the association can be modified or removed with ALTER SEQUENCE OWNED BY. + (This function probably should have been called pg_get_owned_sequence; its current + name reflects the fact that it has historically been used with serial-type columns.) + The first parameter is a table name with optional schema, and the second parameter + is a column name. Because the first parameter potentially contains both schema + and table names, it is parsed per usual SQL rules, meaning it is lower-cased + by default. The second parameter, being just a column name, is treated literally + and so has its case preserved. The result is suitably formatted for passing + to the sequence functions (see Section 9.17). + examples: [] + - name: PG_GET_STATISTICSOBJDEF + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_GET_STATISTICSOBJDEF(statobj oid) + args: + - name: statobj oid + optional: false + type: any + tags: [] + aliases: [] + summary: Reconstructs the creating command for an extended statistics object. + description: Reconstructs the creating command for an extended statistics object. + (This is a decompiled reconstruction, not the original text of the command.) + examples: [] + - name: PG_GET_TRIGGERDEF + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_GET_TRIGGERDEF(trigger oid [, pretty boolean ]) + args: + - name: trigger oid [ + optional: false + type: any + - name: pretty boolean ] + optional: false + type: any + tags: [] + aliases: [] + summary: Reconstructs the creating command for a trigger. + description: Reconstructs the creating command for a trigger. (This is a decompiled + reconstruction, not the original text of the command.) + examples: [] + - name: PG_GET_USERBYID + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_GET_USERBYID(role oid) + args: + - name: role oid + optional: false + type: any + tags: [] + aliases: [] + summary: Returns a role's name given its OID. + description: Returns a role's name given its OID. + examples: [] + - name: PG_GET_VIEWDEF + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_GET_VIEWDEF(view oid [, pretty boolean ]) + args: + - name: view oid [ + optional: false + type: any + - name: pretty boolean ] + optional: false + type: any + tags: [] + aliases: [] + summary: Reconstructs the underlying SELECT command for a view or materialized + view. + description: Reconstructs the underlying SELECT command for a view or materialized + view. (This is a decompiled reconstruction, not the original text of the command.) + examples: [] + - name: PG_GET_WAL_REPLAY_PAUSE_STATE + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_GET_WAL_REPLAY_PAUSE_STATE + args: [] + tags: [] + aliases: [] + summary: Returns recovery pause state. + description: Returns recovery pause state. The return values are not paused if + pause is not requested, pause requested if pause is requested but recovery is + not yet paused, and paused if the recovery is actually paused. + examples: [] + - name: PG_GET_WAL_RESOURCE_MANAGERS + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_GET_WAL_RESOURCE_MANAGERS + args: [] + tags: [] + aliases: [] + summary: Returns the currently-loaded WAL resource managers in the system. + description: Returns the currently-loaded WAL resource managers in the system. + The column rm_builtin indicates whether it's a built-in resource manager, or + a custom resource manager loaded by an extension. + examples: [] + - name: PG_GET_WAL_SUMMARIZER_STATE + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_GET_WAL_SUMMARIZER_STATE + args: [] + tags: [] + aliases: [] + summary: Returns information about the progress of the WAL summarizer. + description: Returns information about the progress of the WAL summarizer. If + the WAL summarizer has never run since the instance was started, then summarized_tli + and summarized_lsn will be 0 and 0/0 respectively; otherwise, they will be the + TLI and ending LSN of the last WAL summary file written to disk. If the WAL + summarizer is currently running, pending_lsn will be the ending LSN of the last + record that it has consumed, which must always be greater than or equal to summarized_lsn; + if the WAL summarizer is not running, it will be equal to summarized_lsn. summarizer_pid + is the PID of the WAL summarizer process, if it is running, and otherwise NULL. + examples: [] + - name: PG_HAS_ROLE + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_HAS_ROLE([ user name or oid, ] role text or oid, privilege text) + args: + - name: '[ user name or oid' + optional: false + type: any + - name: '] role text or oid' + optional: false + type: any + - name: privilege text + optional: false + type: any + tags: [] + aliases: [] + summary: Does user have privilege for role? + description: Does user have privilege for role? Allowable privilege types are + MEMBER, USAGE, and SET. MEMBER denotes direct or indirect membership in the + role without regard to what specific privileges may be conferred. USAGE denotes + whether the privileges of the role are immediately available without doing SET + ROLE, while SET denotes whether it is possible to change to the role using the + SET ROLE command. WITH ADMIN OPTION or WITH GRANT OPTION can be added to any + of these privilege types to test whether the ADMIN privilege is held (all six + spellings test the same thing). This function does not allow the special case + of setting user to public, because the PUBLIC pseudo-role can never be a member + of real roles. + examples: [] + - name: PG_IDENTIFY_OBJECT + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_IDENTIFY_OBJECT(classid oid, objid oid, objsubid integer) + args: + - name: classid oid + optional: false + type: any + - name: objid oid + optional: false + type: any + - name: objsubid integer + optional: false + type: any + tags: [] + aliases: [] + summary: Returns a row containing enough information to uniquely identify the + description: Returns a row containing enough information to uniquely identify + the database object specified by catalog OID, object OID and sub-object ID. + This information is intended to be machine-readable, and is never translated. + type identifies the type of database object; schema is the schema name that + the object belongs in, or NULL for object types that do not belong to schemas; + name is the name of the object, quoted if necessary, if the name (along with + schema name, if pertinent) is sufficient to uniquely identify the object, otherwise + NULL; identity is the complete object identity, with the precise format depending + on object type, and each name within the format being schema-qualified and quoted + as necessary. Undefined objects are identified with NULL values. + examples: [] + - name: PG_IDENTIFY_OBJECT_AS_ADDRESS + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_IDENTIFY_OBJECT_AS_ADDRESS(classid oid, objid oid, objsubid integer) + args: + - name: classid oid + optional: false + type: any + - name: objid oid + optional: false + type: any + - name: objsubid integer + optional: false + type: any + tags: [] + aliases: [] + summary: Returns a row containing enough information to uniquely identify the + description: Returns a row containing enough information to uniquely identify + the database object specified by catalog OID, object OID and sub-object ID. + The returned information is independent of the current server, that is, it could + be used to identify an identically named object in another server. type identifies + the type of database object; object_names and object_args are text arrays that + together form a reference to the object. These three values can be passed to + pg_get_object_address to obtain the internal address of the object. + examples: [] + - name: PG_IMPORT_SYSTEM_COLLATIONS + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_IMPORT_SYSTEM_COLLATIONS(schema regnamespace) + args: + - name: schema regnamespace + optional: false + type: any + tags: [] + aliases: [] + summary: Adds collations to the system catalog pg_collation based on all the locales + description: Adds collations to the system catalog pg_collation based on all the + locales it finds in the operating system. This is what initdb uses; see Section + 23.2.2 for more details. If additional locales are installed into the operating + system later on, this function can be run again to add collations for the new + locales. Locales that match existing entries in pg_collation will be skipped. + (But collation objects based on locales that are no longer present in the operating + system are not removed by this function.) The schema parameter would typically + be pg_catalog, but that is not a requirement; the collations could be installed + into some other schema as well. The function returns the number of new collation + objects it created. Use of this function is restricted to superusers. + examples: [] + - name: PG_INDEXAM_HAS_PROPERTY + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_INDEXAM_HAS_PROPERTY(am oid, property text) + args: + - name: am oid + optional: false + type: any + - name: property text + optional: false + type: any + tags: [] + aliases: [] + summary: Tests whether an index access method has the named property. + description: Tests whether an index access method has the named property. Access + method properties are listed in Table 9.77. NULL is returned if the property + name is not known or does not apply to the particular object, or if the OID + does not identify a valid object. + examples: [] + - name: PG_INDEXES_SIZE + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_INDEXES_SIZE(regclass) + args: + - name: regclass + optional: false + type: any + tags: [] + aliases: [] + summary: Computes the total disk space used by indexes attached to the specified + description: Computes the total disk space used by indexes attached to the specified + table. + examples: [] + - name: PG_INDEX_COLUMN_HAS_PROPERTY + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_INDEX_COLUMN_HAS_PROPERTY(index regclass, column integer, property + text) + args: + - name: index regclass + optional: false + type: any + - name: column integer + optional: false + type: any + - name: property text + optional: false + type: any + tags: [] + aliases: [] + summary: Tests whether an index column has the named property. + description: Tests whether an index column has the named property. Common index + column properties are listed in Table 9.75. (Note that extension access methods + can define additional property names for their indexes.) NULL is returned if + the property name is not known or does not apply to the particular object, or + if the OID or column number does not identify a valid object. + examples: [] + - name: PG_INDEX_HAS_PROPERTY + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_INDEX_HAS_PROPERTY(index regclass, property text) + args: + - name: index regclass + optional: false + type: any + - name: property text + optional: false + type: any + tags: [] + aliases: [] + summary: Tests whether an index has the named property. + description: Tests whether an index has the named property. Common index properties + are listed in Table 9.76. (Note that extension access methods can define additional + property names for their indexes.) NULL is returned if the property name is + not known or does not apply to the particular object, or if the OID does not + identify a valid object. + examples: [] + - name: PG_INPUT_ERROR_INFO + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_INPUT_ERROR_INFO(string text, type text) + args: + - name: string text + optional: false + type: any + - name: type text + optional: false + type: any + tags: [] + aliases: [] + summary: Tests whether the given string is valid input for the specified data + type; + description: Tests whether the given string is valid input for the specified data + type; if not, return the details of the error that would have been thrown. If + the input is valid, the results are NULL. The inputs are the same as for pg_input_is_valid. + examples: [] + - name: PG_INPUT_IS_VALID + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_INPUT_IS_VALID(string text, type text) + args: + - name: string text + optional: false + type: any + - name: type text + optional: false + type: any + tags: [] + aliases: [] + summary: Tests whether the given string is valid input for the specified data + type, + description: Tests whether the given string is valid input for the specified data + type, returning true or false. + examples: [] + - name: PG_IS_IN_RECOVERY + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_IS_IN_RECOVERY + args: [] + tags: [] + aliases: [] + summary: Returns true if recovery is still in progress. + description: Returns true if recovery is still in progress. + examples: [] + - name: PG_IS_OTHER_TEMP_SCHEMA + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_IS_OTHER_TEMP_SCHEMA(oid) + args: + - name: oid + optional: false + type: any + tags: [] + aliases: [] + summary: Returns true if the given OID is the OID of another session's temporary + description: Returns true if the given OID is the OID of another session's temporary + schema. (This can be useful, for example, to exclude other sessions' temporary + tables from a catalog display.) + examples: [] + - name: PG_IS_WAL_REPLAY_PAUSED + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_IS_WAL_REPLAY_PAUSED + args: [] + tags: [] + aliases: [] + summary: Returns true if recovery pause is requested. + description: Returns true if recovery pause is requested. + examples: [] + - name: PG_JIT_AVAILABLE + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_JIT_AVAILABLE + args: [] + tags: [] + aliases: [] + summary: Returns true if a JIT compiler extension is available (see Chapter 30) + and + description: Returns true if a JIT compiler extension is available (see Chapter + 30) and the jit configuration parameter is set to on. + examples: [] + - name: PG_LAST_COMMITTED_XACT + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_LAST_COMMITTED_XACT + args: [] + tags: [] + aliases: [] + summary: Returns the transaction ID, commit timestamp and replication origin of + the + description: Returns the transaction ID, commit timestamp and replication origin + of the latest committed transaction. + examples: [] + - name: PG_LAST_WAL_RECEIVE_LSN + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_LAST_WAL_RECEIVE_LSN + args: [] + tags: [] + aliases: [] + summary: Returns the last write-ahead log location that has been received and + synced + description: Returns the last write-ahead log location that has been received + and synced to disk by streaming replication. While streaming replication is + in progress this will increase monotonically. If recovery has completed then + this will remain static at the location of the last WAL record received and + synced to disk during recovery. If streaming replication is disabled, or if + it has not yet started, the function returns NULL. + examples: [] + - name: PG_LAST_WAL_REPLAY_LSN + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_LAST_WAL_REPLAY_LSN + args: [] + tags: [] + aliases: [] + summary: Returns the last write-ahead log location that has been replayed during + description: Returns the last write-ahead log location that has been replayed + during recovery. If recovery is still in progress this will increase monotonically. + If recovery has completed then this will remain static at the location of the + last WAL record applied during recovery. When the server has been started normally + without recovery, the function returns NULL. + examples: [] + - name: PG_LAST_XACT_REPLAY_TIMESTAMP + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_LAST_XACT_REPLAY_TIMESTAMP + args: [] + tags: [] + aliases: [] + summary: Returns the time stamp of the last transaction replayed during recovery. + description: Returns the time stamp of the last transaction replayed during recovery. + This is the time at which the commit or abort WAL record for that transaction + was generated on the primary. If no transactions have been replayed during recovery, + the function returns NULL. Otherwise, if recovery is still in progress this + will increase monotonically. If recovery has completed then this will remain + static at the time of the last transaction applied during recovery. When the + server has been started normally without recovery, the function returns NULL. + examples: [] + - name: PG_LISTENING_CHANNELS + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_LISTENING_CHANNELS + args: [] + tags: [] + aliases: [] + summary: Returns the set of names of asynchronous notification channels that the + description: Returns the set of names of asynchronous notification channels that + the current session is listening to. + examples: [] + - name: PG_LOGICAL_SLOT_GET_BINARY_CHANGES + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_LOGICAL_SLOT_GET_BINARY_CHANGES(slot_name name, upto_lsn pg_lsn, + upto_nchanges integer, VARIADIC options text[]) + args: + - name: slot_name name + optional: false + type: any + - name: upto_lsn pg_lsn + optional: false + type: any + - name: upto_nchanges integer + optional: false + type: any + - name: VARIADIC options text[] + optional: false + type: any + tags: [] + aliases: [] + summary: Behaves just like the pg_logical_slot_get_changes() function, except + that + description: Behaves just like the pg_logical_slot_get_changes() function, except + that changes are returned as bytea. + examples: [] + - name: PG_LOGICAL_SLOT_GET_CHANGES + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_LOGICAL_SLOT_GET_CHANGES(slot_name name, upto_lsn pg_lsn, upto_nchanges + integer, VARIADIC options text[]) + args: + - name: slot_name name + optional: false + type: any + - name: upto_lsn pg_lsn + optional: false + type: any + - name: upto_nchanges integer + optional: false + type: any + - name: VARIADIC options text[] + optional: false + type: any + tags: [] + aliases: [] + summary: Returns changes in the slot slot_name, starting from the point from which + description: Returns changes in the slot slot_name, starting from the point from + which changes have been consumed last. If upto_lsn and upto_nchanges are NULL, + logical decoding will continue until end of WAL. If upto_lsn is non-NULL, decoding + will include only those transactions which commit prior to the specified LSN. + If upto_nchanges is non-NULL, decoding will stop when the number of rows produced + by decoding exceeds the specified value. Note, however, that the actual number + of rows returned may be larger, since this limit is only checked after adding + the rows produced when decoding each new transaction commit. If the specified + slot is a logical failover slot then the function will not return until all + physical slots specified in synchronized_standby_slots have confirmed WAL receipt. + examples: [] + - name: PG_LOGICAL_SLOT_PEEK_BINARY_CHANGES + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_LOGICAL_SLOT_PEEK_BINARY_CHANGES(slot_name name, upto_lsn pg_lsn, + upto_nchanges integer, VARIADIC options text[]) + args: + - name: slot_name name + optional: false + type: any + - name: upto_lsn pg_lsn + optional: false + type: any + - name: upto_nchanges integer + optional: false + type: any + - name: VARIADIC options text[] + optional: false + type: any + tags: [] + aliases: [] + summary: Behaves just like the pg_logical_slot_peek_changes() function, except + that + description: Behaves just like the pg_logical_slot_peek_changes() function, except + that changes are returned as bytea. + examples: [] + - name: PG_LOGICAL_SLOT_PEEK_CHANGES + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_LOGICAL_SLOT_PEEK_CHANGES(slot_name name, upto_lsn pg_lsn, upto_nchanges + integer, VARIADIC options text[]) + args: + - name: slot_name name + optional: false + type: any + - name: upto_lsn pg_lsn + optional: false + type: any + - name: upto_nchanges integer + optional: false + type: any + - name: VARIADIC options text[] + optional: false + type: any + tags: [] + aliases: [] + summary: Behaves just like the pg_logical_slot_get_changes() function, except + that + description: Behaves just like the pg_logical_slot_get_changes() function, except + that changes are not consumed; that is, they will be returned again on future + calls. + examples: [] + - name: PG_LOG_BACKEND_MEMORY_CONTEXTS + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_LOG_BACKEND_MEMORY_CONTEXTS(pid integer) + args: + - name: pid integer + optional: false + type: any + tags: [] + aliases: [] + summary: Requests to log the memory contexts of the backend with the specified + description: Requests to log the memory contexts of the backend with the specified + process ID. This function can send the request to backends and auxiliary processes + except logger. These memory contexts will be logged at LOG message level. They + will appear in the server log based on the log configuration set (see Section + 19.8 for more information), but will not be sent to the client regardless of + client_min_messages. + examples: [] + - name: PG_LOG_STANDBY_SNAPSHOT + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_LOG_STANDBY_SNAPSHOT + args: [] + tags: [] + aliases: [] + summary: Take a snapshot of running transactions and write it to WAL, without + having + description: Take a snapshot of running transactions and write it to WAL, without + having to wait for bgwriter or checkpointer to log one. This is useful for logical + decoding on standby, as logical slot creation has to wait until such a record + is replayed on the standby. + examples: [] + - name: PG_LS_ARCHIVE_STATUSDIR + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_LS_ARCHIVE_STATUSDIR + args: [] + tags: [] + aliases: [] + summary: Returns the name, size, and last modification time (mtime) of each ordinary + description: Returns the name, size, and last modification time (mtime) of each + ordinary file in the server's WAL archive status directory (pg_wal/archive_status). + Filenames beginning with a dot, directories, and other special files are excluded. + examples: [] + - name: PG_LS_DIR + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_LS_DIR(dirname text [, missing_ok boolean, include_dot_dirs boolean + ]) + args: + - name: dirname text [ + optional: false + type: any + - name: missing_ok boolean + optional: false + type: any + - name: include_dot_dirs boolean ] + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the names of all files (and directories and other special files) + in + description: Returns the names of all files (and directories and other special + files) in the specified directory. The include_dot_dirs parameter indicates + whether "." and ".." are to be included in the result set; the default is to + exclude them. Including them can be useful when missing_ok is true, to distinguish + an empty directory from a non-existent directory. + examples: [] + - name: PG_LS_LOGDIR + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_LS_LOGDIR + args: [] + tags: [] + aliases: [] + summary: Returns the name, size, and last modification time (mtime) of each ordinary + description: Returns the name, size, and last modification time (mtime) of each + ordinary file in the server's log directory. Filenames beginning with a dot, + directories, and other special files are excluded. + examples: [] + - name: PG_LS_LOGICALMAPDIR + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_LS_LOGICALMAPDIR + args: [] + tags: [] + aliases: [] + summary: Returns the name, size, and last modification time (mtime) of each ordinary + description: Returns the name, size, and last modification time (mtime) of each + ordinary file in the server's pg_logical/mappings directory. Filenames beginning + with a dot, directories, and other special files are excluded. + examples: [] + - name: PG_LS_LOGICALSNAPDIR + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_LS_LOGICALSNAPDIR + args: [] + tags: [] + aliases: [] + summary: Returns the name, size, and last modification time (mtime) of each ordinary + description: Returns the name, size, and last modification time (mtime) of each + ordinary file in the server's pg_logical/snapshots directory. Filenames beginning + with a dot, directories, and other special files are excluded. + examples: [] + - name: PG_LS_REPLSLOTDIR + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_LS_REPLSLOTDIR(slot_name text) + args: + - name: slot_name text + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the name, size, and last modification time (mtime) of each ordinary + description: Returns the name, size, and last modification time (mtime) of each + ordinary file in the server's pg_replslot/slot_name directory, where slot_name + is the name of the replication slot provided as input of the function. Filenames + beginning with a dot, directories, and other special files are excluded. + examples: [] + - name: PG_LS_TMPDIR + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_LS_TMPDIR([ tablespace oid ]) + args: + - name: '[ tablespace oid ]' + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the name, size, and last modification time (mtime) of each ordinary + description: Returns the name, size, and last modification time (mtime) of each + ordinary file in the temporary file directory for the specified tablespace. + If tablespace is not provided, the pg_default tablespace is examined. Filenames + beginning with a dot, directories, and other special files are excluded. + examples: [] + - name: PG_LS_WALDIR + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_LS_WALDIR + args: [] + tags: [] + aliases: [] + summary: Returns the name, size, and last modification time (mtime) of each ordinary + description: Returns the name, size, and last modification time (mtime) of each + ordinary file in the server's write-ahead log (WAL) directory. Filenames beginning + with a dot, directories, and other special files are excluded. + examples: [] + - name: PG_MY_TEMP_SCHEMA + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_MY_TEMP_SCHEMA + args: [] + tags: [] + aliases: [] + summary: Returns the OID of the current session's temporary schema, or zero if + it + description: Returns the OID of the current session's temporary schema, or zero + if it has none (because it has not created any temporary tables). + examples: [] + - name: PG_NOTIFICATION_QUEUE_USAGE + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_NOTIFICATION_QUEUE_USAGE + args: [] + tags: [] + aliases: [] + summary: "Returns the fraction (0\u20131) of the asynchronous notification queue's" + description: "Returns the fraction (0\u20131) of the asynchronous notification\ + \ queue's\nmaximum size that is currently occupied by notifications that are\ + \ waiting\nto be processed. See LISTEN and NOTIFY for more information." + examples: [] + - name: PG_OPCLASS_IS_VISIBLE + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_OPCLASS_IS_VISIBLE(opclass oid) + args: + - name: opclass oid + optional: false + type: any + tags: [] + aliases: [] + summary: Is operator class visible in search path? + description: Is operator class visible in search path? + examples: [] + - name: PG_OPERATOR_IS_VISIBLE + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_OPERATOR_IS_VISIBLE(operator oid) + args: + - name: operator oid + optional: false + type: any + tags: [] + aliases: [] + summary: Is operator visible in search path? + description: Is operator visible in search path? + examples: [] + - name: PG_OPFAMILY_IS_VISIBLE + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_OPFAMILY_IS_VISIBLE(opclass oid) + args: + - name: opclass oid + optional: false + type: any + tags: [] + aliases: [] + summary: Is operator family visible in search path? + description: Is operator family visible in search path? + examples: [] + - name: PG_OPTIONS_TO_TABLE + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_OPTIONS_TO_TABLE(options_array text[]) + args: + - name: options_array text[] + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the set of storage options represented by a value from + description: Returns the set of storage options represented by a value from pg_class.reloptions + or pg_attribute.attoptions. + examples: [] + - name: PG_PARTITION_ANCESTORS + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_PARTITION_ANCESTORS(regclass) + args: + - name: regclass + optional: false + type: any + tags: [] + aliases: [] + summary: Lists the ancestor relations of the given partition, including the relation + description: Lists the ancestor relations of the given partition, including the + relation itself. Returns no rows if the relation does not exist or is not a + partition or partitioned table. + examples: [] + - name: PG_PARTITION_ROOT + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_PARTITION_ROOT(regclass) + args: + - name: regclass + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the top-most parent of the partition tree to which the given + description: Returns the top-most parent of the partition tree to which the given + relation belongs. Returns NULL if the relation does not exist or is not a partition + or partitioned table. + examples: [] + - name: PG_PARTITION_TREE + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_PARTITION_TREE(regclass) + args: + - name: regclass + optional: false + type: any + tags: [] + aliases: [] + summary: Lists the tables or indexes in the partition tree of the given partitioned + description: Lists the tables or indexes in the partition tree of the given partitioned + table or partitioned index, with one row for each partition. Information provided + includes the OID of the partition, the OID of its immediate parent, a boolean + value telling if the partition is a leaf, and an integer telling its level in + the hierarchy. The level value is 0 for the input table or index, 1 for its + immediate child partitions, 2 for their partitions, and so on. Returns no rows + if the relation does not exist or is not a partition or partitioned table. + examples: [] + - name: PG_POSTMASTER_START_TIME + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_POSTMASTER_START_TIME + args: [] + tags: [] + aliases: [] + summary: Returns the time when the server started. + description: Returns the time when the server started. + examples: [] + - name: PG_PROMOTE + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_PROMOTE(wait boolean DEFAULT true, wait_seconds integer DEFAULT + 60) + args: + - name: wait boolean DEFAULT true + optional: false + type: any + - name: wait_seconds integer DEFAULT 60 + optional: false + type: any + tags: [] + aliases: [] + summary: Promotes a standby server to primary status. + description: Promotes a standby server to primary status. With wait set to true + (the default), the function waits until promotion is completed or wait_seconds + seconds have passed, and returns true if promotion is successful and false otherwise. + If wait is set to false, the function returns true immediately after sending + a SIGUSR1 signal to the postmaster to trigger promotion. + examples: [] + - name: PG_READ_BINARY_FILE + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_READ_BINARY_FILE(filename text [, offset bigint, length bigint ] + [, missing_ok boolean ]) + args: + - name: filename text [ + optional: false + type: any + - name: offset bigint + optional: false + type: any + - name: length bigint ] [ + optional: false + type: any + - name: missing_ok boolean ] + optional: false + type: any + tags: [] + aliases: [] + summary: Returns all or part of a file. + description: Returns all or part of a file. This function is identical to pg_read_file + except that it can read arbitrary binary data, returning the result as bytea + not text; accordingly, no encoding checks are performed. + examples: [] + - name: PG_READ_FILE + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_READ_FILE(filename text [, offset bigint, length bigint ] [, missing_ok + boolean ]) + args: + - name: filename text [ + optional: false + type: any + - name: offset bigint + optional: false + type: any + - name: length bigint ] [ + optional: false + type: any + - name: missing_ok boolean ] + optional: false + type: any + tags: [] + aliases: [] + summary: Returns all or part of a text file, starting at the given byte offset, + description: Returns all or part of a text file, starting at the given byte offset, + returning at most length bytes (less if the end of file is reached first). If + offset is negative, it is relative to the end of the file. If offset and length + are omitted, the entire file is returned. The bytes read from the file are interpreted + as a string in the database's encoding; an error is thrown if they are not valid + in that encoding. + examples: [] + - name: PG_RELATION_FILENODE + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_RELATION_FILENODE(relation regclass) + args: + - name: relation regclass + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the "filenode" number currently assigned to the specified relation. + description: Returns the "filenode" number currently assigned to the specified + relation. The filenode is the base component of the file name(s) used for the + relation (see Section 65.1 for more information). For most relations the result + is the same as pg_class.relfilenode, but for certain system catalogs relfilenode + is zero and this function must be used to get the correct value. The function + returns NULL if passed a relation that does not have storage, such as a view. + examples: [] + - name: PG_RELATION_FILEPATH + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_RELATION_FILEPATH(relation regclass) + args: + - name: relation regclass + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the entire file path name (relative to the database cluster's + data + description: Returns the entire file path name (relative to the database cluster's + data directory, PGDATA) of the relation. + examples: [] + - name: PG_RELATION_SIZE + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_RELATION_SIZE(relation regclass [, fork text ]) + args: + - name: relation regclass [ + optional: false + type: any + - name: fork text ] + optional: false + type: any + tags: [] + aliases: [] + summary: Computes the disk space used by one "fork" of the specified relation. + description: 'Computes the disk space used by one "fork" of the specified relation. + (Note that for most purposes it is more convenient to use the higher-level functions + pg_total_relation_size or pg_table_size, which sum the sizes of all forks.) + With one argument, this returns the size of the main data fork of the relation. + The second argument can be provided to specify which fork to examine:' + examples: [] + - name: PG_RELOAD_CONF + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_RELOAD_CONF + args: [] + tags: [] + aliases: [] + summary: Causes all processes of the PostgreSQL server to reload their configuration + description: Causes all processes of the PostgreSQL server to reload their configuration + files. (This is initiated by sending a SIGHUP signal to the postmaster process, + which in turn sends SIGHUP to each of its children.) You can use the pg_file_settings, + pg_hba_file_rules and pg_ident_file_mappings views to check the configuration + files for possible errors, before reloading. + examples: [] + - name: PG_REPLICATION_ORIGIN_ADVANCE + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_REPLICATION_ORIGIN_ADVANCE(node_name text, lsn pg_lsn) + args: + - name: node_name text + optional: false + type: any + - name: lsn pg_lsn + optional: false + type: any + tags: [] + aliases: [] + summary: Sets replication progress for the given node to the given location. + description: Sets replication progress for the given node to the given location. + This is primarily useful for setting up the initial location, or setting a new + location after configuration changes and similar. Be aware that careless use + of this function can lead to inconsistently replicated data. + examples: [] + - name: PG_REPLICATION_ORIGIN_CREATE + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_REPLICATION_ORIGIN_CREATE(node_name text) + args: + - name: node_name text + optional: false + type: any + tags: [] + aliases: [] + summary: Creates a replication origin with the given external name, and returns + the + description: Creates a replication origin with the given external name, and returns + the internal ID assigned to it. + examples: [] + - name: PG_REPLICATION_ORIGIN_DROP + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_REPLICATION_ORIGIN_DROP(node_name text) + args: + - name: node_name text + optional: false + type: any + tags: [] + aliases: [] + summary: Deletes a previously-created replication origin, including any associated + description: Deletes a previously-created replication origin, including any associated + replay progress. + examples: [] + - name: PG_REPLICATION_ORIGIN_OID + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_REPLICATION_ORIGIN_OID(node_name text) + args: + - name: node_name text + optional: false + type: any + tags: [] + aliases: [] + summary: Looks up a replication origin by name and returns the internal ID. + description: Looks up a replication origin by name and returns the internal ID. + If no such replication origin is found, NULL is returned. + examples: [] + - name: PG_REPLICATION_ORIGIN_PROGRESS + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_REPLICATION_ORIGIN_PROGRESS(node_name text, flush boolean) + args: + - name: node_name text + optional: false + type: any + - name: flush boolean + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the replay location for the given replication origin. + description: Returns the replay location for the given replication origin. The + parameter flush determines whether the corresponding local transaction will + be guaranteed to have been flushed to disk or not. + examples: [] + - name: PG_REPLICATION_ORIGIN_SESSION_IS_SETUP + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_REPLICATION_ORIGIN_SESSION_IS_SETUP + args: [] + tags: [] + aliases: [] + summary: Returns true if a replication origin has been selected in the current + description: Returns true if a replication origin has been selected in the current + session. + examples: [] + - name: PG_REPLICATION_ORIGIN_SESSION_PROGRESS + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_REPLICATION_ORIGIN_SESSION_PROGRESS(flush boolean) + args: + - name: flush boolean + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the replay location for the replication origin selected in the + description: Returns the replay location for the replication origin selected in + the current session. The parameter flush determines whether the corresponding + local transaction will be guaranteed to have been flushed to disk or not. + examples: [] + - name: PG_REPLICATION_ORIGIN_SESSION_RESET + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_REPLICATION_ORIGIN_SESSION_RESET + args: [] + tags: [] + aliases: [] + summary: Cancels the effects of pg_replication_origin_session_setup(). + description: Cancels the effects of pg_replication_origin_session_setup(). + examples: [] + - name: PG_REPLICATION_ORIGIN_SESSION_SETUP + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_REPLICATION_ORIGIN_SESSION_SETUP(node_name text) + args: + - name: node_name text + optional: false + type: any + tags: [] + aliases: [] + summary: Marks the current session as replaying from the given origin, allowing + description: Marks the current session as replaying from the given origin, allowing + replay progress to be tracked. Can only be used if no origin is currently selected. + Use pg_replication_origin_session_reset to undo. + examples: [] + - name: PG_REPLICATION_ORIGIN_XACT_RESET + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_REPLICATION_ORIGIN_XACT_RESET + args: [] + tags: [] + aliases: [] + summary: Cancels the effects of pg_replication_origin_xact_setup(). + description: Cancels the effects of pg_replication_origin_xact_setup(). + examples: [] + - name: PG_REPLICATION_ORIGIN_XACT_SETUP + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_REPLICATION_ORIGIN_XACT_SETUP(origin_lsn pg_lsn, origin_timestamp + timestamp with time zone) + args: + - name: origin_lsn pg_lsn + optional: false + type: any + - name: origin_timestamp timestamp with time zone + optional: false + type: any + tags: [] + aliases: [] + summary: Marks the current transaction as replaying a transaction that has committed + description: Marks the current transaction as replaying a transaction that has + committed at the given LSN and timestamp. Can only be called when a replication + origin has been selected using pg_replication_origin_session_setup. + examples: [] + - name: PG_REPLICATION_SLOT_ADVANCE + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_REPLICATION_SLOT_ADVANCE(slot_name name, upto_lsn pg_lsn) + args: + - name: slot_name name + optional: false + type: any + - name: upto_lsn pg_lsn + optional: false + type: any + tags: [] + aliases: [] + summary: Advances the current confirmed position of a replication slot named + description: Advances the current confirmed position of a replication slot named + slot_name. The slot will not be moved backwards, and it will not be moved beyond + the current insert location. Returns the name of the slot and the actual position + that it was advanced to. The updated slot position information is written out + at the next checkpoint if any advancing is done. So in the event of a crash, + the slot may return to an earlier position. If the specified slot is a logical + failover slot then the function will not return until all physical slots specified + in synchronized_standby_slots have confirmed WAL receipt. + examples: [] + - name: PG_ROTATE_LOGFILE + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_ROTATE_LOGFILE + args: [] + tags: [] + aliases: [] + summary: Signals the log-file manager to switch to a new output file immediately. + description: Signals the log-file manager to switch to a new output file immediately. + This works only when the built-in log collector is running, since otherwise + there is no log-file manager subprocess. + examples: [] + - name: PG_SAFE_SNAPSHOT_BLOCKING_PIDS + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_SAFE_SNAPSHOT_BLOCKING_PIDS(integer) + args: + - name: integer + optional: false + type: any + tags: [] + aliases: [] + summary: Returns an array of the process ID(s) of the sessions that are blocking + the + description: Returns an array of the process ID(s) of the sessions that are blocking + the server process with the specified process ID from acquiring a safe snapshot, + or an empty array if there is no such server process or it is not blocked. + examples: [] + - name: PG_SETTINGS_GET_FLAGS + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_SETTINGS_GET_FLAGS(guc text) + args: + - name: guc text + optional: false + type: any + tags: [] + aliases: [] + summary: Returns an array of the flags associated with the given GUC, or NULL + if it + description: Returns an array of the flags associated with the given GUC, or NULL + if it does not exist. The result is an empty array if the GUC exists but there + are no flags to show. Only the most useful flags listed in Table 9.78 are exposed. + examples: [] + - name: PG_SIZE_BYTES + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_SIZE_BYTES(text) + args: + - name: text + optional: false + type: any + tags: [] + aliases: [] + summary: Converts a size in human-readable format (as returned by pg_size_pretty) + description: Converts a size in human-readable format (as returned by pg_size_pretty) + into bytes. Valid units are bytes, B, kB, MB, GB, TB, and PB. + examples: [] + - name: PG_SNAPSHOT_XIP + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_SNAPSHOT_XIP(pg_snapshot) + args: + - name: pg_snapshot + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the set of in-progress transaction IDs contained in a snapshot. + description: Returns the set of in-progress transaction IDs contained in a snapshot. + examples: [] + - name: PG_SNAPSHOT_XMAX + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_SNAPSHOT_XMAX(pg_snapshot) + args: + - name: pg_snapshot + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the xmax of a snapshot. + description: Returns the xmax of a snapshot. + examples: [] + - name: PG_SNAPSHOT_XMIN + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_SNAPSHOT_XMIN(pg_snapshot) + args: + - name: pg_snapshot + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the xmin of a snapshot. + description: Returns the xmin of a snapshot. + examples: [] + - name: PG_SPLIT_WALFILE_NAME + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_SPLIT_WALFILE_NAME(file_name text) + args: + - name: file_name text + optional: false + type: any + tags: [] + aliases: [] + summary: Extracts the sequence number and timeline ID from a WAL file name. + description: Extracts the sequence number and timeline ID from a WAL file name. + examples: [] + - name: PG_STATISTICS_OBJ_IS_VISIBLE + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_STATISTICS_OBJ_IS_VISIBLE(stat oid) + args: + - name: stat oid + optional: false + type: any + tags: [] + aliases: [] + summary: Is statistics object visible in search path? + description: Is statistics object visible in search path? + examples: [] + - name: PG_STAT_FILE + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_STAT_FILE(filename text [, missing_ok boolean ]) + args: + - name: filename text [ + optional: false + type: any + - name: missing_ok boolean ] + optional: false + type: any + tags: [] + aliases: [] + summary: Returns a record containing the file's size, last access time stamp, + last + description: Returns a record containing the file's size, last access time stamp, + last modification time stamp, last file status change time stamp (Unix platforms + only), file creation time stamp (Windows only), and a flag indicating if it + is a directory. + examples: [] + - name: PG_SWITCH_WAL + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_SWITCH_WAL + args: [] + tags: [] + aliases: [] + summary: Forces the server to switch to a new write-ahead log file, which allows + the + description: Forces the server to switch to a new write-ahead log file, which + allows the current file to be archived (assuming you are using continuous archiving). + The result is the ending write-ahead log location plus 1 within the just-completed + write-ahead log file. If there has been no write-ahead log activity since the + last write-ahead log switch, pg_switch_wal does nothing and returns the start + location of the write-ahead log file currently in use. + examples: [] + - name: PG_SYNC_REPLICATION_SLOTS + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_SYNC_REPLICATION_SLOTS + args: [] + tags: [] + aliases: [] + summary: Synchronize the logical failover replication slots from the primary server + description: Synchronize the logical failover replication slots from the primary + server to the standby server. This function can only be executed on the standby + server. Temporary synced slots, if any, cannot be used for logical decoding + and must be dropped after promotion. See Section 47.2.3 for details. Note that + this function cannot be executed if sync_replication_slots is enabled and the + slotsync worker is already running to perform the synchronization of slots. + examples: [] + - name: PG_TABLESPACE_DATABASES + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_TABLESPACE_DATABASES(tablespace oid) + args: + - name: tablespace oid + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the set of OIDs of databases that have objects stored in the + description: Returns the set of OIDs of databases that have objects stored in + the specified tablespace. If this function returns any rows, the tablespace + is not empty and cannot be dropped. To identify the specific objects populating + the tablespace, you will need to connect to the database(s) identified by pg_tablespace_databases + and query their pg_class catalogs. + examples: [] + - name: PG_TABLESPACE_LOCATION + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_TABLESPACE_LOCATION(tablespace oid) + args: + - name: tablespace oid + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the file system path that this tablespace is located in. + description: Returns the file system path that this tablespace is located in. + examples: [] + - name: PG_TABLE_IS_VISIBLE + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_TABLE_IS_VISIBLE(table oid) + args: + - name: table oid + optional: false + type: any + tags: [] + aliases: [] + summary: Is table visible in search path? + description: Is table visible in search path? (This works for all types of relations, + including views, materialized views, indexes, sequences and foreign tables.) + examples: [] + - name: PG_TABLE_SIZE + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_TABLE_SIZE(regclass) + args: + - name: regclass + optional: false + type: any + tags: [] + aliases: [] + summary: Computes the disk space used by the specified table, excluding indexes + (but + description: Computes the disk space used by the specified table, excluding indexes + (but including its TOAST table if any, free space map, and visibility map). + examples: [] + - name: PG_TERMINATE_BACKEND + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_TERMINATE_BACKEND(pid integer, timeout bigint DEFAULT 0) + args: + - name: pid integer + optional: false + type: any + - name: timeout bigint DEFAULT 0 + optional: false + type: any + tags: [] + aliases: [] + summary: Terminates the session whose backend process has the specified process + ID. + description: Terminates the session whose backend process has the specified process + ID. This is also allowed if the calling role is a member of the role whose backend + is being terminated or the calling role has privileges of pg_signal_backend, + however only superusers can terminate superuser backends. + examples: [] + - name: PG_TOTAL_RELATION_SIZE + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_TOTAL_RELATION_SIZE(regclass) + args: + - name: regclass + optional: false + type: any + tags: [] + aliases: [] + summary: Computes the total disk space used by the specified table, including + all + description: Computes the total disk space used by the specified table, including + all indexes and TOAST data. The result is equivalent to pg_table_size + pg_indexes_size. + examples: [] + - name: PG_TRIGGER_DEPTH + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_TRIGGER_DEPTH + args: [] + tags: [] + aliases: [] + summary: Returns the current nesting level of PostgreSQL triggers (0 if not called, + description: Returns the current nesting level of PostgreSQL triggers (0 if not + called, directly or indirectly, from inside a trigger). + examples: [] + - name: PG_TS_CONFIG_IS_VISIBLE + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_TS_CONFIG_IS_VISIBLE(config oid) + args: + - name: config oid + optional: false + type: any + tags: [] + aliases: [] + summary: Is text search configuration visible in search path? + description: Is text search configuration visible in search path? + examples: [] + - name: PG_TS_DICT_IS_VISIBLE + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_TS_DICT_IS_VISIBLE(dict oid) + args: + - name: dict oid + optional: false + type: any + tags: [] + aliases: [] + summary: Is text search dictionary visible in search path? + description: Is text search dictionary visible in search path? + examples: [] + - name: PG_TS_PARSER_IS_VISIBLE + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_TS_PARSER_IS_VISIBLE(parser oid) + args: + - name: parser oid + optional: false + type: any + tags: [] + aliases: [] + summary: Is text search parser visible in search path? + description: Is text search parser visible in search path? + examples: [] + - name: PG_TS_TEMPLATE_IS_VISIBLE + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_TS_TEMPLATE_IS_VISIBLE(template oid) + args: + - name: template oid + optional: false + type: any + tags: [] + aliases: [] + summary: Is text search template visible in search path? + description: Is text search template visible in search path? + examples: [] + - name: PG_TYPEOF + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_TYPEOF("any") + args: + - name: '"any"' + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the OID of the data type of the value that is passed to it. + description: Returns the OID of the data type of the value that is passed to it. + This can be helpful for troubleshooting or dynamically constructing SQL queries. + The function is declared as returning regtype, which is an OID alias type (see + Section 8.19); this means that it is the same as an OID for comparison purposes + but displays as a type name. + examples: [] + - name: PG_TYPE_IS_VISIBLE + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_TYPE_IS_VISIBLE(type oid) + args: + - name: type oid + optional: false + type: any + tags: [] + aliases: [] + summary: Is type (or domain) visible in search path? + description: Is type (or domain) visible in search path? + examples: [] + - name: PG_VISIBLE_IN_SNAPSHOT + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_VISIBLE_IN_SNAPSHOT(xid8, pg_snapshot) + args: + - name: xid8 + optional: false + type: any + - name: pg_snapshot + optional: false + type: any + tags: [] + aliases: [] + summary: Is the given transaction ID visible according to this snapshot (that + is, + description: Is the given transaction ID visible according to this snapshot (that + is, was it completed before the snapshot was taken)? Note that this function + will not give the correct answer for a subtransaction ID (subxid); see Section + 66.3 for details. + examples: [] + - name: PG_WALFILE_NAME + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_WALFILE_NAME(lsn pg_lsn) + args: + - name: lsn pg_lsn + optional: false + type: any + tags: [] + aliases: [] + summary: Converts a write-ahead log location to the name of the WAL file holding + description: Converts a write-ahead log location to the name of the WAL file holding + that location. + examples: [] + - name: PG_WALFILE_NAME_OFFSET + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_WALFILE_NAME_OFFSET(lsn pg_lsn) + args: + - name: lsn pg_lsn + optional: false + type: any + tags: [] + aliases: [] + summary: Converts a write-ahead log location to a WAL file name and byte offset + description: Converts a write-ahead log location to a WAL file name and byte offset + within that file. + examples: [] + - name: PG_WAL_LSN_DIFF + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_WAL_LSN_DIFF(lsn1 pg_lsn, lsn2 pg_lsn) + args: + - name: lsn1 pg_lsn + optional: false + type: any + - name: lsn2 pg_lsn + optional: false + type: any + tags: [] + aliases: [] + summary: Calculates the difference in bytes (lsn1 - lsn2) between two write-ahead + description: Calculates the difference in bytes (lsn1 - lsn2) between two write-ahead + log locations. This can be used with pg_stat_replication or some of the functions + shown in Table 9.95 to get the replication lag. + examples: [] + - name: PG_WAL_REPLAY_PAUSE + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_WAL_REPLAY_PAUSE + args: [] + tags: [] + aliases: [] + summary: Request to pause recovery. + description: Request to pause recovery. A request doesn't mean that recovery stops + right away. If you want a guarantee that recovery is actually paused, you need + to check for the recovery pause state returned by pg_get_wal_replay_pause_state(). + Note that pg_is_wal_replay_paused() returns whether a request is made. While + recovery is paused, no further database changes are applied. If hot standby + is active, all new queries will see the same consistent snapshot of the database, + and no further query conflicts will be generated until recovery is resumed. + examples: [] + - name: PG_WAL_REPLAY_RESUME + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: PG_WAL_REPLAY_RESUME + args: [] + tags: [] + aliases: [] + summary: Restarts recovery if it was paused. + description: Restarts recovery if it was paused. + examples: [] + - name: PG_WAL_SUMMARY_CONTENTS + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_WAL_SUMMARY_CONTENTS(tli bigint, start_lsn pg_lsn, end_lsn pg_lsn) + args: + - name: tli bigint + optional: false + type: any + - name: start_lsn pg_lsn + optional: false + type: any + - name: end_lsn pg_lsn + optional: false + type: any + tags: [] + aliases: [] + summary: Returns one information about the contents of a single WAL summary file + description: Returns one information about the contents of a single WAL summary + file identified by TLI and starting and ending LSNs. Each row with is_limit_block + false indicates that the block identified by the remaining output columns was + modified by at least one WAL record within the range of records summarized by + this file. Each row with is_limit_block true indicates either that (a) the relation + fork was truncated to the length given by relblocknumber within the relevant + range of WAL records or (b) that the relation fork was created or dropped within + the relevant range of WAL records; in such cases, relblocknumber will be zero. + examples: [] + - name: PG_XACT_COMMIT_TIMESTAMP + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_XACT_COMMIT_TIMESTAMP(xid) + args: + - name: xid + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the commit timestamp of a transaction. + description: Returns the commit timestamp of a transaction. + examples: [] + - name: PG_XACT_COMMIT_TIMESTAMP_ORIGIN + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_XACT_COMMIT_TIMESTAMP_ORIGIN(xid) + args: + - name: xid + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the commit timestamp and replication origin of a transaction. + description: Returns the commit timestamp and replication origin of a transaction. + examples: [] + - name: PG_XACT_STATUS + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: PG_XACT_STATUS(xid8) + args: + - name: xid8 + optional: false + type: any + tags: [] + aliases: [] + summary: Reports the commit status of a recent transaction. + description: Reports the commit status of a recent transaction. The result is + one of in progress, committed, or aborted, provided that the transaction is + recent enough that the system retains the commit status of that transaction. + If it is old enough that no references to the transaction survive in the system + and the commit status information has been discarded, the result is NULL. Applications + might use this function, for example, to determine whether their transaction + committed or aborted after the application and database server become disconnected + while a COMMIT is in progress. Note that prepared transactions are reported + as in progress; applications must check pg_prepared_xacts if they need to determine + whether a transaction ID belongs to a prepared transaction. + examples: [] + - name: PHRASETO_TSQUERY + category_id: text_search_functions + category_label: Text Search Functions + signature: + display: PHRASETO_TSQUERY([ config regconfig, ] query text) + args: + - name: '[ config regconfig' + optional: false + type: any + - name: '] query text' + optional: false + type: any + tags: [] + aliases: [] + summary: Converts text to a tsquery, normalizing words according to the specified + or + description: Converts text to a tsquery, normalizing words according to the specified + or default configuration. Any punctuation in the string is ignored (it does + not determine query operators). The resulting query matches phrases containing + all non-stopwords in the text. + examples: [] + - name: PI + category_id: numeric_math_functions + category_label: Numeric/Math Functions + signature: + display: PI + args: [] + tags: [] + aliases: [] + summary: "Approximate value of \u03C0" + description: "Approximate value of \u03C0" + examples: [] + - name: PLAINTO_TSQUERY + category_id: text_search_functions + category_label: Text Search Functions + signature: + display: PLAINTO_TSQUERY([ config regconfig, ] query text) + args: + - name: '[ config regconfig' + optional: false + type: any + - name: '] query text' + optional: false + type: any + tags: [] + aliases: [] + summary: Converts text to a tsquery, normalizing words according to the specified + or + description: Converts text to a tsquery, normalizing words according to the specified + or default configuration. Any punctuation in the string is ignored (it does + not determine query operators). The resulting query matches documents containing + all non-stopwords in the text. + examples: [] + - name: POINT + category_id: geometric_functions + category_label: Geometric Functions + signature: + display: POINT(double precision, double precision) + args: + - name: double precision + optional: false + type: any + - name: double precision + optional: false + type: any + tags: [] + aliases: [] + summary: Constructs point from its coordinates. + description: Constructs point from its coordinates. + examples: [] + - name: POLYGON + category_id: geometric_functions + category_label: Geometric Functions + signature: + display: POLYGON(box) + args: + - name: box + optional: false + type: any + tags: [] + aliases: [] + summary: Converts box to a 4-point polygon. + description: Converts box to a 4-point polygon. + examples: [] + - name: POPEN + category_id: geometric_functions + category_label: Geometric Functions + signature: + display: POPEN(path) + args: + - name: path + optional: false + type: any + tags: [] + aliases: [] + summary: Converts path to open form. + description: Converts path to open form. + examples: [] + - name: POSITION1 + category_id: string_functions + category_label: String Functions + signature: + display: POSITION1(substring text IN string text) + args: + - name: substring text IN string text + optional: false + type: any + tags: [] + aliases: [] + summary: Returns first starting index of the specified substring within string, + or + description: Returns first starting index of the specified substring within string, + or zero if it's not present. + examples: [] + - name: POSITION2 + category_id: binary_string_functions + category_label: Binary String Functions + signature: + display: POSITION2(substring bytea IN bytes bytea) + args: + - name: substring bytea IN bytes bytea + optional: false + type: any + tags: [] + aliases: [] + summary: Returns first starting index of the specified substring within bytes, + or + description: Returns first starting index of the specified substring within bytes, + or zero if it's not present. + examples: [] + - name: POSITION3 + category_id: bit_string_functions + category_label: Bit String Functions + signature: + display: POSITION3(substring bit IN bits bit) + args: + - name: substring bit IN bits bit + optional: false + type: any + tags: [] + aliases: [] + summary: Returns first starting index of the specified substring within bits, + or + description: Returns first starting index of the specified substring within bits, + or zero if it's not present. + examples: [] + - name: QUERYTREE + category_id: text_search_functions + category_label: Text Search Functions + signature: + display: QUERYTREE(tsquery) + args: + - name: tsquery + optional: false + type: any + tags: [] + aliases: [] + summary: Produces a representation of the indexable portion of a tsquery. + description: Produces a representation of the indexable portion of a tsquery. + A result that is empty or just T indicates a non-indexable query. + examples: [] + - name: QUOTE_IDENT + category_id: string_functions + category_label: String Functions + signature: + display: QUOTE_IDENT(text) + args: + - name: text + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the given string suitably quoted to be used as an identifier + in an + description: Returns the given string suitably quoted to be used as an identifier + in an SQL statement string. Quotes are added only if necessary (i.e., if the + string contains non-identifier characters or would be case-folded). Embedded + quotes are properly doubled. See also Example 41.1. + examples: [] + - name: QUOTE_LITERAL + category_id: string_functions + category_label: String Functions + signature: + display: QUOTE_LITERAL(text) + args: + - name: text + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the given string suitably quoted to be used as a string literal + in + description: Returns the given string suitably quoted to be used as a string literal + in an SQL statement string. Embedded single-quotes and backslashes are properly + doubled. Note that quote_literal returns null on null input; if the argument + might be null, quote_nullable is often more suitable. See also Example 41.1. + examples: [] + - name: QUOTE_NULLABLE + category_id: string_functions + category_label: String Functions + signature: + display: QUOTE_NULLABLE(text) + args: + - name: text + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the given string suitably quoted to be used as a string literal + in + description: Returns the given string suitably quoted to be used as a string literal + in an SQL statement string; or, if the argument is null, returns NULL. Embedded + single-quotes and backslashes are properly doubled. See also Example 41.1. + examples: [] + - name: RADIANS + category_id: numeric_math_functions + category_label: Numeric/Math Functions + signature: + display: RADIANS(double precision) + args: + - name: double precision + optional: false + type: any + tags: [] + aliases: [] + summary: Converts degrees to radians + description: Converts degrees to radians + examples: [] + - name: RADIUS + category_id: geometric_functions + category_label: Geometric Functions + signature: + display: RADIUS(circle) + args: + - name: circle + optional: false + type: any + tags: [] + aliases: [] + summary: Computes radius of circle. + description: Computes radius of circle. + examples: [] + - name: RANDOM + category_id: numeric_math_functions + category_label: Numeric/Math Functions + signature: + display: RANDOM + args: [] + tags: [] + aliases: [] + summary: Returns a random value in the range 0.0 <= x < 1.0 + description: Returns a random value in the range 0.0 <= x < 1.0 + examples: [] + - name: RANDOM_NORMAL + category_id: numeric_math_functions + category_label: Numeric/Math Functions + signature: + display: RANDOM_NORMAL([ mean double precision [, stddev double precision ]]) + args: + - name: '[ mean double precision [' + optional: false + type: any + - name: stddev double precision ]] + optional: false + type: any + tags: [] + aliases: [] + summary: Returns a random value from the normal distribution with the given + description: Returns a random value from the normal distribution with the given + parameters; mean defaults to 0.0 and stddev defaults to 1.0 + examples: [] + - name: RANGE_MERGE1 + category_id: range_functions + category_label: Range Functions + signature: + display: RANGE_MERGE1(anyrange, anyrange) + args: + - name: anyrange + optional: false + type: any + - name: anyrange + optional: false + type: any + tags: [] + aliases: [] + summary: Computes the smallest range that includes both of the given ranges. + description: Computes the smallest range that includes both of the given ranges. + examples: [] + - name: RANGE_MERGE2 + category_id: range_functions + category_label: Range Functions + signature: + display: RANGE_MERGE2(anymultirange) + args: + - name: anymultirange + optional: false + type: any + tags: [] + aliases: [] + summary: Computes the smallest range that includes the entire multirange. + description: Computes the smallest range that includes the entire multirange. + examples: [] + - name: RANK1 + category_id: aggregate_functions + category_label: Aggregate Functions + signature: + display: RANK1(args) + args: + - name: args + optional: false + type: any + tags: [] + aliases: [] + summary: Computes the rank of the hypothetical row, with gaps; that is, the row + description: Computes the rank of the hypothetical row, with gaps; that is, the + row number of the first row in its peer group. + examples: [] + - name: RANK2 + category_id: window_functions + category_label: Window Functions + signature: + display: RANK2 + args: [] + tags: [] + aliases: [] + summary: Returns the rank of the current row, with gaps; that is, the row_number + of + description: Returns the rank of the current row, with gaps; that is, the row_number + of the first row in its peer group. + examples: [] + - name: REGEXP_COUNT + category_id: string_functions + category_label: String Functions + signature: + display: REGEXP_COUNT(string text, pattern text [, start integer [, flags text + ] ]) + args: + - name: string text + optional: false + type: any + - name: pattern text [ + optional: false + type: any + - name: start integer [ + optional: false + type: any + - name: flags text ] ] + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the number of times the POSIX regular expression pattern matches + in + description: Returns the number of times the POSIX regular expression pattern + matches in the string; see Section 9.7.3. + examples: [] + - name: REGEXP_INSTR + category_id: string_functions + category_label: String Functions + signature: + display: REGEXP_INSTR(string text, pattern text [, start integer [, N integer + [, endoption integer [, flags text [, subexpr integer ] ] ] ] ]) + args: + - name: string text + optional: false + type: any + - name: pattern text [ + optional: false + type: any + - name: start integer [ + optional: false + type: any + - name: N integer [ + optional: false + type: any + - name: endoption integer [ + optional: false + type: any + - name: flags text [ + optional: false + type: any + - name: subexpr integer ] ] ] ] ] + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the position within string where the N'th match of the POSIX + description: Returns the position within string where the N'th match of the POSIX + regular expression pattern occurs, or zero if there is no such match; see Section + 9.7.3. + examples: [] + - name: REGEXP_LIKE + category_id: string_functions + category_label: String Functions + signature: + display: REGEXP_LIKE(string text, pattern text [, flags text ]) + args: + - name: string text + optional: false + type: any + - name: pattern text [ + optional: false + type: any + - name: flags text ] + optional: false + type: any + tags: [] + aliases: [] + summary: Checks whether a match of the POSIX regular expression pattern occurs + description: Checks whether a match of the POSIX regular expression pattern occurs + within string; see Section 9.7.3. + examples: [] + - name: REGEXP_MATCH + category_id: string_functions + category_label: String Functions + signature: + display: REGEXP_MATCH(string text, pattern text [, flags text ]) + args: + - name: string text + optional: false + type: any + - name: pattern text [ + optional: false + type: any + - name: flags text ] + optional: false + type: any + tags: [] + aliases: [] + summary: Returns substrings within the first match of the POSIX regular expression + description: Returns substrings within the first match of the POSIX regular expression + pattern to the string; see Section 9.7.3. + examples: [] + - name: REGEXP_MATCHES + category_id: string_functions + category_label: String Functions + signature: + display: REGEXP_MATCHES(string text, pattern text [, flags text ]) + args: + - name: string text + optional: false + type: any + - name: pattern text [ + optional: false + type: any + - name: flags text ] + optional: false + type: any + tags: [] + aliases: [] + summary: Returns substrings within the first match of the POSIX regular expression + description: Returns substrings within the first match of the POSIX regular expression + pattern to the string, or substrings within all such matches if the g flag is + used; see Section 9.7.3. + examples: [] + - name: REGEXP_REPLACE + category_id: string_functions + category_label: String Functions + signature: + display: REGEXP_REPLACE(string text, pattern text, replacement text [, start + integer ] [, flags text ]) + args: + - name: string text + optional: false + type: any + - name: pattern text + optional: false + type: any + - name: replacement text [ + optional: false + type: any + - name: start integer ] [ + optional: false + type: any + - name: flags text ] + optional: false + type: any + tags: [] + aliases: [] + summary: Replaces the substring that is the first match to the POSIX regular + description: Replaces the substring that is the first match to the POSIX regular + expression pattern, or all such matches if the g flag is used; see Section 9.7.3. + examples: [] + - name: REGEXP_SPLIT_TO_ARRAY + category_id: string_functions + category_label: String Functions + signature: + display: REGEXP_SPLIT_TO_ARRAY(string text, pattern text [, flags text ]) + args: + - name: string text + optional: false + type: any + - name: pattern text [ + optional: false + type: any + - name: flags text ] + optional: false + type: any + tags: [] + aliases: [] + summary: Splits string using a POSIX regular expression as the delimiter, producing + description: Splits string using a POSIX regular expression as the delimiter, + producing an array of results; see Section 9.7.3. + examples: [] + - name: REGEXP_SPLIT_TO_TABLE + category_id: string_functions + category_label: String Functions + signature: + display: REGEXP_SPLIT_TO_TABLE(string text, pattern text [, flags text ]) + args: + - name: string text + optional: false + type: any + - name: pattern text [ + optional: false + type: any + - name: flags text ] + optional: false + type: any + tags: [] + aliases: [] + summary: Splits string using a POSIX regular expression as the delimiter, producing + description: Splits string using a POSIX regular expression as the delimiter, + producing a set of results; see Section 9.7.3. + examples: [] + - name: REGEXP_SUBSTR + category_id: string_functions + category_label: String Functions + signature: + display: REGEXP_SUBSTR(string text, pattern text [, start integer [, N integer + [, flags text [, subexpr integer ] ] ] ]) + args: + - name: string text + optional: false + type: any + - name: pattern text [ + optional: false + type: any + - name: start integer [ + optional: false + type: any + - name: N integer [ + optional: false + type: any + - name: flags text [ + optional: false + type: any + - name: subexpr integer ] ] ] ] + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the substring within string that matches the N'th occurrence + of the + description: Returns the substring within string that matches the N'th occurrence + of the POSIX regular expression pattern, or NULL if there is no such match; + see Section 9.7.3. + examples: [] + - name: REGR_AVGX + category_id: aggregate_functions + category_label: Aggregate Functions + signature: + display: REGR_AVGX(Y double precision, X double precision) + args: + - name: Y double precision + optional: false + type: any + - name: X double precision + optional: false + type: any + tags: [] + aliases: [] + summary: Computes the average of the independent variable, sum(X)/N. + description: Computes the average of the independent variable, sum(X)/N. + examples: [] + - name: REGR_AVGY + category_id: aggregate_functions + category_label: Aggregate Functions + signature: + display: REGR_AVGY(Y double precision, X double precision) + args: + - name: Y double precision + optional: false + type: any + - name: X double precision + optional: false + type: any + tags: [] + aliases: [] + summary: Computes the average of the dependent variable, sum(Y)/N. + description: Computes the average of the dependent variable, sum(Y)/N. + examples: [] + - name: REGR_COUNT + category_id: aggregate_functions + category_label: Aggregate Functions + signature: + display: REGR_COUNT(Y double precision, X double precision) + args: + - name: Y double precision + optional: false + type: any + - name: X double precision + optional: false + type: any + tags: [] + aliases: [] + summary: Computes the number of rows in which both inputs are non-null. + description: Computes the number of rows in which both inputs are non-null. + examples: [] + - name: REGR_R2 + category_id: aggregate_functions + category_label: Aggregate Functions + signature: + display: REGR_R2(Y double precision, X double precision) + args: + - name: Y double precision + optional: false + type: any + - name: X double precision + optional: false + type: any + tags: [] + aliases: [] + summary: Computes the square of the correlation coefficient. + description: Computes the square of the correlation coefficient. + examples: [] + - name: REGR_SXX + category_id: aggregate_functions + category_label: Aggregate Functions + signature: + display: REGR_SXX(Y double precision, X double precision) + args: + - name: Y double precision + optional: false + type: any + - name: X double precision + optional: false + type: any + tags: [] + aliases: [] + summary: Computes the "sum of squares" of the independent variable, sum(X^2) - + description: Computes the "sum of squares" of the independent variable, sum(X^2) + - sum(X)^2/N. + examples: [] + - name: REGR_SXY + category_id: aggregate_functions + category_label: Aggregate Functions + signature: + display: REGR_SXY(Y double precision, X double precision) + args: + - name: Y double precision + optional: false + type: any + - name: X double precision + optional: false + type: any + tags: [] + aliases: [] + summary: Computes the "sum of products" of independent times dependent variables, + description: Computes the "sum of products" of independent times dependent variables, + sum(X*Y) - sum(X) * sum(Y)/N. + examples: [] + - name: REGR_SYY + category_id: aggregate_functions + category_label: Aggregate Functions + signature: + display: REGR_SYY(Y double precision, X double precision) + args: + - name: Y double precision + optional: false + type: any + - name: X double precision + optional: false + type: any + tags: [] + aliases: [] + summary: Computes the "sum of squares" of the dependent variable, sum(Y^2) - + description: Computes the "sum of squares" of the dependent variable, sum(Y^2) + - sum(Y)^2/N. + examples: [] + - name: REPEAT + category_id: string_functions + category_label: String Functions + signature: + display: REPEAT(string text, number integer) + args: + - name: string text + optional: false + type: any + - name: number integer + optional: false + type: any + tags: [] + aliases: [] + summary: Repeats string the specified number of times. + description: Repeats string the specified number of times. + examples: [] + - name: REPLACE + category_id: string_functions + category_label: String Functions + signature: + display: REPLACE(string text, from text, to text) + args: + - name: string text + optional: false + type: any + - name: from text + optional: false + type: any + - name: to text + optional: false + type: any + tags: [] + aliases: [] + summary: Replaces all occurrences in string of substring from with substring to. + description: Replaces all occurrences in string of substring from with substring + to. + examples: [] + - name: REVERSE + category_id: string_functions + category_label: String Functions + signature: + display: REVERSE(text) + args: + - name: text + optional: false + type: any + tags: [] + aliases: [] + summary: Reverses the order of the characters in the string. + description: Reverses the order of the characters in the string. + examples: [] + - name: RIGHT + category_id: string_functions + category_label: String Functions + signature: + display: RIGHT(string text, n integer) + args: + - name: string text + optional: false + type: any + - name: n integer + optional: false + type: any + tags: [] + aliases: [] + summary: Returns last n characters in the string, or when n is negative, returns + all + description: Returns last n characters in the string, or when n is negative, returns + all but first |n| characters. + examples: [] + - name: ROW_NUMBER + category_id: window_functions + category_label: Window Functions + signature: + display: ROW_NUMBER + args: [] + tags: [] + aliases: [] + summary: Returns the number of the current row within its partition, counting + from + description: Returns the number of the current row within its partition, counting + from 1. + examples: [] + - name: ROW_SECURITY_ACTIVE + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: ROW_SECURITY_ACTIVE(table text or oid) + args: + - name: table text or oid + optional: false + type: any + tags: [] + aliases: [] + summary: Is row-level security active for the specified table in the context of + the + description: Is row-level security active for the specified table in the context + of the current user and current environment? + examples: [] + - name: ROW_TO_JSON + category_id: json_functions + category_label: JSON Functions + signature: + display: ROW_TO_JSON(record [, boolean ]) + args: + - name: record [ + optional: false + type: any + - name: boolean ] + optional: false + type: any + tags: [] + aliases: [] + summary: Converts an SQL composite value to a JSON object. + description: Converts an SQL composite value to a JSON object. The behavior is + the same as to_json except that line feeds will be added between top-level elements + if the optional boolean parameter is true. + examples: [] + - name: RPAD + category_id: string_functions + category_label: String Functions + signature: + display: RPAD(string text, length integer [, fill text ]) + args: + - name: string text + optional: false + type: any + - name: length integer [ + optional: false + type: any + - name: fill text ] + optional: false + type: any + tags: [] + aliases: [] + summary: Extends the string to length length by appending the characters fill + (a + description: Extends the string to length length by appending the characters fill + (a space by default). If the string is already longer than length then it is + truncated. + examples: [] + - name: RTRIM1 + category_id: string_functions + category_label: String Functions + signature: + display: RTRIM1(string text [, characters text ]) + args: + - name: string text [ + optional: false + type: any + - name: characters text ] + optional: false + type: any + tags: [] + aliases: [] + summary: Removes the longest string containing only characters in characters (a + description: Removes the longest string containing only characters in characters + (a space by default) from the end of string. + examples: [] + - name: RTRIM2 + category_id: binary_string_functions + category_label: Binary String Functions + signature: + display: RTRIM2(bytes bytea, bytesremoved bytea) + args: + - name: bytes bytea + optional: false + type: any + - name: bytesremoved bytea + optional: false + type: any + tags: [] + aliases: [] + summary: Removes the longest string containing only bytes appearing in bytesremoved + description: Removes the longest string containing only bytes appearing in bytesremoved + from the end of bytes. + examples: [] + - name: SCALE + category_id: numeric_math_functions + category_label: Numeric/Math Functions + signature: + display: SCALE(numeric) + args: + - name: numeric + optional: false + type: any + tags: [] + aliases: [] + summary: Scale of the argument (the number of decimal digits in the fractional + part) + description: Scale of the argument (the number of decimal digits in the fractional + part) + examples: [] + - name: SETSEED + category_id: numeric_math_functions + category_label: Numeric/Math Functions + signature: + display: SETSEED(double precision) + args: + - name: double precision + optional: false + type: any + tags: [] + aliases: [] + summary: Sets the seed for subsequent random() and random_normal() calls; argument + description: Sets the seed for subsequent random() and random_normal() calls; + argument must be between -1.0 and 1.0, inclusive + examples: [] + - name: SETVAL + category_id: sequence_manipulation_functions + category_label: Sequence Manipulation Functions + signature: + display: SETVAL(regclass, bigint [, boolean ]) + args: + - name: regclass + optional: false + type: any + - name: bigint [ + optional: false + type: any + - name: boolean ] + optional: false + type: any + tags: [] + aliases: [] + summary: Sets the sequence object's current value, and optionally its is_called + description: Sets the sequence object's current value, and optionally its is_called + flag. The two-parameter form sets the sequence's last_value field to the specified + value and sets its is_called field to true, meaning that the next nextval will + advance the sequence before returning a value. The value that will be reported + by currval is also set to the specified value. In the three-parameter form, + is_called can be set to either true or false. true has the same effect as the + two-parameter form. If it is set to false, the next nextval will return exactly + the specified value, and sequence advancement commences with the following nextval. + Furthermore, the value reported by currval is not changed in this case. For + example, + examples: [] + - name: SETWEIGHT1 + category_id: text_search_functions + category_label: Text Search Functions + signature: + display: SETWEIGHT1(vector tsvector, weight "char") + args: + - name: vector tsvector + optional: false + type: any + - name: weight "char" + optional: false + type: any + tags: [] + aliases: [] + summary: Assigns the specified weight to each element of the vector. + description: Assigns the specified weight to each element of the vector. + examples: [] + - name: SETWEIGHT2 + category_id: text_search_functions + category_label: Text Search Functions + signature: + display: SETWEIGHT2(vector tsvector, weight "char", lexemes text[]) + args: + - name: vector tsvector + optional: false + type: any + - name: weight "char" + optional: false + type: any + - name: lexemes text[] + optional: false + type: any + tags: [] + aliases: [] + summary: Assigns the specified weight to elements of the vector that are listed + in + description: Assigns the specified weight to elements of the vector that are listed + in lexemes. The strings in lexemes are taken as lexemes as-is, without further + processing. Strings that do not match any lexeme in vector are ignored. + examples: [] + - name: SET_BIT1 + category_id: binary_string_functions + category_label: Binary String Functions + signature: + display: SET_BIT1(bytes bytea, n bigint, newvalue integer) + args: + - name: bytes bytea + optional: false + type: any + - name: n bigint + optional: false + type: any + - name: newvalue integer + optional: false + type: any + tags: [] + aliases: [] + summary: Sets n'th bit in binary string to newvalue. + description: Sets n'th bit in binary string to newvalue. + examples: [] + - name: SET_BIT2 + category_id: bit_string_functions + category_label: Bit String Functions + signature: + display: SET_BIT2(bits bit, n integer, newvalue integer) + args: + - name: bits bit + optional: false + type: any + - name: n integer + optional: false + type: any + - name: newvalue integer + optional: false + type: any + tags: [] + aliases: [] + summary: Sets n'th bit in bit string to newvalue; the first (leftmost) bit is + bit 0. + description: Sets n'th bit in bit string to newvalue; the first (leftmost) bit + is bit 0. + examples: [] + - name: SET_BYTE + category_id: binary_string_functions + category_label: Binary String Functions + signature: + display: SET_BYTE(bytes bytea, n integer, newvalue integer) + args: + - name: bytes bytea + optional: false + type: any + - name: n integer + optional: false + type: any + - name: newvalue integer + optional: false + type: any + tags: [] + aliases: [] + summary: Sets n'th byte in binary string to newvalue. + description: Sets n'th byte in binary string to newvalue. + examples: [] + - name: SET_CONFIG + category_id: system_administration_functions + category_label: System Administration Functions + signature: + display: SET_CONFIG(setting_name text, new_value text, is_local boolean) + args: + - name: setting_name text + optional: false + type: any + - name: new_value text + optional: false + type: any + - name: is_local boolean + optional: false + type: any + tags: [] + aliases: [] + summary: Sets the parameter setting_name to new_value, and returns that value. + description: Sets the parameter setting_name to new_value, and returns that value. + If is_local is true, the new value will only apply during the current transaction. + If you want the new value to apply for the rest of the current session, use + false instead. This function corresponds to the SQL command SET. + examples: [] + - name: SET_MASKLEN + category_id: network_address_functions + category_label: Network Address Functions + signature: + display: SET_MASKLEN(inet, integer) + args: + - name: inet + optional: false + type: any + - name: integer + optional: false + type: any + tags: [] + aliases: [] + summary: Sets the netmask length for an inet value. + description: Sets the netmask length for an inet value. The address part does + not change. + examples: [] + - name: SHA224 + category_id: binary_string_functions + category_label: Binary String Functions + signature: + display: SHA224(bytea) + args: + - name: bytea + optional: false + type: any + tags: [] + aliases: [] + summary: Computes the SHA-224 hash of the binary string. + description: Computes the SHA-224 hash of the binary string. + examples: [] + - name: SHA256 + category_id: binary_string_functions + category_label: Binary String Functions + signature: + display: SHA256(bytea) + args: + - name: bytea + optional: false + type: any + tags: [] + aliases: [] + summary: Computes the SHA-256 hash of the binary string. + description: Computes the SHA-256 hash of the binary string. + examples: [] + - name: SHA384 + category_id: binary_string_functions + category_label: Binary String Functions + signature: + display: SHA384(bytea) + args: + - name: bytea + optional: false + type: any + tags: [] + aliases: [] + summary: Computes the SHA-384 hash of the binary string. + description: Computes the SHA-384 hash of the binary string. + examples: [] + - name: SHA512 + category_id: binary_string_functions + category_label: Binary String Functions + signature: + display: SHA512(bytea) + args: + - name: bytea + optional: false + type: any + tags: [] + aliases: [] + summary: Computes the SHA-512 hash of the binary string. + description: Computes the SHA-512 hash of the binary string. + examples: [] + - name: SHOBJ_DESCRIPTION + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: SHOBJ_DESCRIPTION(object oid, catalog name) + args: + - name: object oid + optional: false + type: any + - name: catalog name + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the comment for a shared database object specified by its OID + and + description: Returns the comment for a shared database object specified by its + OID and the name of the containing system catalog. This is just like obj_description + except that it is used for retrieving comments on shared objects (that is, databases, + roles, and tablespaces). Some system catalogs are global to all databases within + each cluster, and the descriptions for objects in them are stored globally as + well. + examples: [] + - name: SIN + category_id: numeric_math_functions + category_label: Numeric/Math Functions + signature: + display: SIN(double precision) + args: + - name: double precision + optional: false + type: any + tags: [] + aliases: [] + summary: Sine, argument in radians + description: Sine, argument in radians + examples: [] + - name: SIND + category_id: numeric_math_functions + category_label: Numeric/Math Functions + signature: + display: SIND(double precision) + args: + - name: double precision + optional: false + type: any + tags: [] + aliases: [] + summary: Sine, argument in degrees + description: Sine, argument in degrees + examples: [] + - name: SINH + category_id: numeric_math_functions + category_label: Numeric/Math Functions + signature: + display: SINH(double precision) + args: + - name: double precision + optional: false + type: any + tags: [] + aliases: [] + summary: Hyperbolic sine + description: Hyperbolic sine + examples: [] + - name: SLOPE + category_id: geometric_functions + category_label: Geometric Functions + signature: + display: SLOPE(point, point) + args: + - name: point + optional: false + type: any + - name: point + optional: false + type: any + tags: [] + aliases: [] + summary: Computes slope of a line drawn through the two points. + description: Computes slope of a line drawn through the two points. + examples: [] + - name: SPLIT_PART + category_id: string_functions + category_label: String Functions + signature: + display: SPLIT_PART(string text, delimiter text, n integer) + args: + - name: string text + optional: false + type: any + - name: delimiter text + optional: false + type: any + - name: n integer + optional: false + type: any + tags: [] + aliases: [] + summary: Splits string at occurrences of delimiter and returns the n'th field + description: Splits string at occurrences of delimiter and returns the n'th field + (counting from one), or when n is negative, returns the |n|'th-from-last field. + examples: [] + - name: STARTS_WITH + category_id: string_functions + category_label: String Functions + signature: + display: STARTS_WITH(string text, prefix text) + args: + - name: string text + optional: false + type: any + - name: prefix text + optional: false + type: any + tags: [] + aliases: [] + summary: Returns true if string starts with prefix. + description: Returns true if string starts with prefix. + examples: [] + - name: STATEMENT_TIMESTAMP + category_id: date_time_functions + category_label: Date/Time Functions + signature: + display: STATEMENT_TIMESTAMP + args: [] + tags: [] + aliases: [] + summary: Current date and time (start of current statement); see Section 9.9.5 + description: Current date and time (start of current statement); see Section 9.9.5 + examples: [] + - name: STRING_TO_ARRAY + category_id: string_functions + category_label: String Functions + signature: + display: STRING_TO_ARRAY(string text, delimiter text [, null_string text ]) + args: + - name: string text + optional: false + type: any + - name: delimiter text [ + optional: false + type: any + - name: null_string text ] + optional: false + type: any + tags: [] + aliases: [] + summary: Splits the string at occurrences of delimiter and forms the resulting + description: Splits the string at occurrences of delimiter and forms the resulting + fields into a text array. If delimiter is NULL, each character in the string + will become a separate element in the array. If delimiter is an empty string, + then the string is treated as a single field. If null_string is supplied and + is not NULL, fields matching that string are replaced by NULL. See also array_to_string. + examples: [] + - name: STRING_TO_TABLE + category_id: string_functions + category_label: String Functions + signature: + display: STRING_TO_TABLE(string text, delimiter text [, null_string text ]) + args: + - name: string text + optional: false + type: any + - name: delimiter text [ + optional: false + type: any + - name: null_string text ] + optional: false + type: any + tags: [] + aliases: [] + summary: Splits the string at occurrences of delimiter and returns the resulting + description: Splits the string at occurrences of delimiter and returns the resulting + fields as a set of text rows. If delimiter is NULL, each character in the string + will become a separate row of the result. If delimiter is an empty string, then + the string is treated as a single field. If null_string is supplied and is not + NULL, fields matching that string are replaced by NULL. + examples: [] + - name: STRIP + category_id: text_search_functions + category_label: Text Search Functions + signature: + display: STRIP(tsvector) + args: + - name: tsvector + optional: false + type: any + tags: [] + aliases: [] + summary: Removes positions and weights from the tsvector. + description: Removes positions and weights from the tsvector. + examples: [] + - name: STRPOS + category_id: string_functions + category_label: String Functions + signature: + display: STRPOS(string text, substring text) + args: + - name: string text + optional: false + type: any + - name: substring text + optional: false + type: any + tags: [] + aliases: [] + summary: Returns first starting index of the specified substring within string, + or + description: Returns first starting index of the specified substring within string, + or zero if it's not present. (Same as position(substring in string), but note + the reversed argument order.) + examples: [] + - name: SUBSTR1 + category_id: string_functions + category_label: String Functions + signature: + display: SUBSTR1(string text, start integer [, count integer ]) + args: + - name: string text + optional: false + type: any + - name: start integer [ + optional: false + type: any + - name: count integer ] + optional: false + type: any + tags: [] + aliases: [] + summary: Extracts the substring of string starting at the start'th character, + and + description: Extracts the substring of string starting at the start'th character, + and extending for count characters if that is specified. (Same as substring(string + from start for count).) + examples: [] + - name: SUBSTR2 + category_id: binary_string_functions + category_label: Binary String Functions + signature: + display: SUBSTR2(bytes bytea, start integer [, count integer ]) + args: + - name: bytes bytea + optional: false + type: any + - name: start integer [ + optional: false + type: any + - name: count integer ] + optional: false + type: any + tags: [] + aliases: [] + summary: Extracts the substring of bytes starting at the start'th byte, and + description: Extracts the substring of bytes starting at the start'th byte, and + extending for count bytes if that is specified. (Same as substring(bytes from + start for count).) + examples: [] + - name: SUBSTRING1 + category_id: string_functions + category_label: String Functions + signature: + display: SUBSTRING1(string text [ FROM start integer ] [ FOR count integer ]) + args: + - name: string text [ FROM start integer ] [ FOR count integer ] + optional: false + type: any + tags: [] + aliases: [] + summary: Extracts the substring of string starting at the start'th character if + that + description: Extracts the substring of string starting at the start'th character + if that is specified, and stopping after count characters if that is specified. + Provide at least one of start and count. + examples: [] + - name: SUBSTRING2 + category_id: binary_string_functions + category_label: Binary String Functions + signature: + display: SUBSTRING2(bytes bytea [ FROM start integer ] [ FOR count integer ]) + args: + - name: bytes bytea [ FROM start integer ] [ FOR count integer ] + optional: false + type: any + tags: [] + aliases: [] + summary: Extracts the substring of bytes starting at the start'th byte if that + is + description: Extracts the substring of bytes starting at the start'th byte if + that is specified, and stopping after count bytes if that is specified. Provide + at least one of start and count. + examples: [] + - name: SUBSTRING3 + category_id: bit_string_functions + category_label: Bit String Functions + signature: + display: SUBSTRING3(bits bit [ FROM start integer ] [ FOR count integer ]) + args: + - name: bits bit [ FROM start integer ] [ FOR count integer ] + optional: false + type: any + tags: [] + aliases: [] + summary: Extracts the substring of bits starting at the start'th bit if that is + description: Extracts the substring of bits starting at the start'th bit if that + is specified, and stopping after count bits if that is specified. Provide at + least one of start and count. + examples: [] + - name: SUPPRESS_REDUNDANT_UPDATES_TRIGGER + category_id: trigger_functions + category_label: Trigger Functions + signature: + display: SUPPRESS_REDUNDANT_UPDATES_TRIGGER + args: [] + tags: [] + aliases: [] + summary: Suppresses do-nothing update operations. + description: Suppresses do-nothing update operations. See below for details. + examples: [] + - name: TAN + category_id: numeric_math_functions + category_label: Numeric/Math Functions + signature: + display: TAN(double precision) + args: + - name: double precision + optional: false + type: any + tags: [] + aliases: [] + summary: Tangent, argument in radians + description: Tangent, argument in radians + examples: [] + - name: TAND + category_id: numeric_math_functions + category_label: Numeric/Math Functions + signature: + display: TAND(double precision) + args: + - name: double precision + optional: false + type: any + tags: [] + aliases: [] + summary: Tangent, argument in degrees + description: Tangent, argument in degrees + examples: [] + - name: TANH + category_id: numeric_math_functions + category_label: Numeric/Math Functions + signature: + display: TANH(double precision) + args: + - name: double precision + optional: false + type: any + tags: [] + aliases: [] + summary: Hyperbolic tangent + description: Hyperbolic tangent + examples: [] + - name: TEXT + category_id: network_address_functions + category_label: Network Address Functions + signature: + display: TEXT(inet) + args: + - name: inet + optional: false + type: any + tags: [] + aliases: [] + summary: Returns the unabbreviated IP address and netmask length as text. + description: Returns the unabbreviated IP address and netmask length as text. + (This has the same result as an explicit cast to text.) + examples: [] + - name: TIMEOFDAY + category_id: date_time_functions + category_label: Date/Time Functions + signature: + display: TIMEOFDAY + args: [] + tags: [] + aliases: [] + summary: Current date and time (like clock_timestamp, but as a text string); see + description: Current date and time (like clock_timestamp, but as a text string); + see Section 9.9.5 + examples: [] + - name: TO_JSONB + category_id: json_functions + category_label: JSON Functions + signature: + display: TO_JSONB(anyelement) + args: + - name: anyelement + optional: false + type: any + tags: [] + aliases: [] + summary: Converts any SQL value to json or jsonb. + description: Converts any SQL value to json or jsonb. Arrays and composites are + converted recursively to arrays and objects (multidimensional arrays become + arrays of arrays in JSON). Otherwise, if there is a cast from the SQL data type + to json, the cast function will be used to perform the conversion;[a] otherwise, + a scalar JSON value is produced. For any scalar other than a number, a Boolean, + or a null value, the text representation will be used, with escaping as necessary + to make it a valid JSON string value. + examples: [] + - name: TO_REGCLASS + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: TO_REGCLASS(text) + args: + - name: text + optional: false + type: any + tags: [] + aliases: [] + summary: Translates a textual relation name to its OID. + description: Translates a textual relation name to its OID. A similar result is + obtained by casting the string to type regclass (see Section 8.19); however, + this function will return NULL rather than throwing an error if the name is + not found. + examples: [] + - name: TO_REGCOLLATION + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: TO_REGCOLLATION(text) + args: + - name: text + optional: false + type: any + tags: [] + aliases: [] + summary: Translates a textual collation name to its OID. + description: Translates a textual collation name to its OID. A similar result + is obtained by casting the string to type regcollation (see Section 8.19); however, + this function will return NULL rather than throwing an error if the name is + not found. + examples: [] + - name: TO_REGNAMESPACE + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: TO_REGNAMESPACE(text) + args: + - name: text + optional: false + type: any + tags: [] + aliases: [] + summary: Translates a textual schema name to its OID. + description: Translates a textual schema name to its OID. A similar result is + obtained by casting the string to type regnamespace (see Section 8.19); however, + this function will return NULL rather than throwing an error if the name is + not found. + examples: [] + - name: TO_REGOPER + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: TO_REGOPER(text) + args: + - name: text + optional: false + type: any + tags: [] + aliases: [] + summary: Translates a textual operator name to its OID. + description: Translates a textual operator name to its OID. A similar result is + obtained by casting the string to type regoper (see Section 8.19); however, + this function will return NULL rather than throwing an error if the name is + not found or is ambiguous. + examples: [] + - name: TO_REGOPERATOR + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: TO_REGOPERATOR(text) + args: + - name: text + optional: false + type: any + tags: [] + aliases: [] + summary: Translates a textual operator name (with parameter types) to its OID. + description: Translates a textual operator name (with parameter types) to its + OID. A similar result is obtained by casting the string to type regoperator + (see Section 8.19); however, this function will return NULL rather than throwing + an error if the name is not found. + examples: [] + - name: TO_REGPROC + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: TO_REGPROC(text) + args: + - name: text + optional: false + type: any + tags: [] + aliases: [] + summary: Translates a textual function or procedure name to its OID. + description: Translates a textual function or procedure name to its OID. A similar + result is obtained by casting the string to type regproc (see Section 8.19); + however, this function will return NULL rather than throwing an error if the + name is not found or is ambiguous. + examples: [] + - name: TO_REGPROCEDURE + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: TO_REGPROCEDURE(text) + args: + - name: text + optional: false + type: any + tags: [] + aliases: [] + summary: Translates a textual function or procedure name (with argument types) + to + description: Translates a textual function or procedure name (with argument types) + to its OID. A similar result is obtained by casting the string to type regprocedure + (see Section 8.19); however, this function will return NULL rather than throwing + an error if the name is not found. + examples: [] + - name: TO_REGROLE + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: TO_REGROLE(text) + args: + - name: text + optional: false + type: any + tags: [] + aliases: [] + summary: Translates a textual role name to its OID. + description: Translates a textual role name to its OID. A similar result is obtained + by casting the string to type regrole (see Section 8.19); however, this function + will return NULL rather than throwing an error if the name is not found. + examples: [] + - name: TO_REGTYPE + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: TO_REGTYPE(text) + args: + - name: text + optional: false + type: any + tags: [] + aliases: [] + summary: Parses a string of text, extracts a potential type name from it, and + description: Parses a string of text, extracts a potential type name from it, + and translates that name into a type OID. A syntax error in the string will + result in an error; but if the string is a syntactically valid type name that + happens not to be found in the catalogs, the result is NULL. A similar result + is obtained by casting the string to type regtype (see Section 8.19), except + that that will throw error for name not found. + examples: [] + - name: TO_REGTYPEMOD + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: TO_REGTYPEMOD(text) + args: + - name: text + optional: false + type: any + tags: [] + aliases: [] + summary: Parses a string of text, extracts a potential type name from it, and + description: Parses a string of text, extracts a potential type name from it, + and translates its type modifier, if any. A syntax error in the string will + result in an error; but if the string is a syntactically valid type name that + happens not to be found in the catalogs, the result is NULL. The result is -1 + if no type modifier is present. + examples: [] + - name: TO_TIMESTAMP + category_id: date_time_functions + category_label: Date/Time Functions + signature: + display: TO_TIMESTAMP(double precision) + args: + - name: double precision + optional: false + type: any + tags: [] + aliases: [] + summary: Convert Unix epoch (seconds since 1970-01-01 00:00:00+00) to timestamp + with + description: Convert Unix epoch (seconds since 1970-01-01 00:00:00+00) to timestamp + with time zone + examples: [] + - name: TO_TSQUERY + category_id: text_search_functions + category_label: Text Search Functions + signature: + display: TO_TSQUERY([ config regconfig, ] query text) + args: + - name: '[ config regconfig' + optional: false + type: any + - name: '] query text' + optional: false + type: any + tags: [] + aliases: [] + summary: Converts text to a tsquery, normalizing words according to the specified + or + description: Converts text to a tsquery, normalizing words according to the specified + or default configuration. The words must be combined by valid tsquery operators. + examples: [] + - name: TO_TSVECTOR + category_id: text_search_functions + category_label: Text Search Functions + signature: + display: TO_TSVECTOR([ config regconfig, ] document text) + args: + - name: '[ config regconfig' + optional: false + type: any + - name: '] document text' + optional: false + type: any + tags: [] + aliases: [] + summary: Converts text to a tsvector, normalizing words according to the specified + description: Converts text to a tsvector, normalizing words according to the specified + or default configuration. Position information is included in the result. + examples: [] + - name: TRANSACTION_TIMESTAMP + category_id: date_time_functions + category_label: Date/Time Functions + signature: + display: TRANSACTION_TIMESTAMP + args: [] + tags: [] + aliases: [] + summary: Current date and time (start of current transaction); see Section 9.9.5 + description: Current date and time (start of current transaction); see Section + 9.9.5 + examples: [] + - name: TRANSLATE + category_id: string_functions + category_label: String Functions + signature: + display: TRANSLATE(string text, from text, to text) + args: + - name: string text + optional: false + type: any + - name: from text + optional: false + type: any + - name: to text + optional: false + type: any + tags: [] + aliases: [] + summary: Replaces each character in string that matches a character in the from + set + description: Replaces each character in string that matches a character in the + from set with the corresponding character in the to set. If from is longer than + to, occurrences of the extra characters in from are deleted. + examples: [] + - name: TRIM1 + category_id: string_functions + category_label: String Functions + signature: + display: TRIM1([ LEADING | TRAILING | BOTH ] [ characters text ] FROM string + text) + args: + - name: '[ LEADING | TRAILING | BOTH ] [ characters text ] FROM string text' + optional: false + type: any + tags: [] + aliases: [] + summary: Removes the longest string containing only characters in characters (a + description: Removes the longest string containing only characters in characters + (a space by default) from the start, end, or both ends (BOTH is the default) + of string. + examples: [] + - name: TRIM2 + category_id: binary_string_functions + category_label: Binary String Functions + signature: + display: TRIM2([ LEADING | TRAILING | BOTH ] bytesremoved bytea FROM bytes bytea) + args: + - name: '[ LEADING | TRAILING | BOTH ] bytesremoved bytea FROM bytes bytea' + optional: false + type: any + tags: [] + aliases: [] + summary: Removes the longest string containing only bytes appearing in bytesremoved + description: Removes the longest string containing only bytes appearing in bytesremoved + from the start, end, or both ends (BOTH is the default) of bytes. + examples: [] + - name: TRIM_ARRAY + category_id: array_functions + category_label: Array Functions + signature: + display: TRIM_ARRAY(array anyarray, n integer) + args: + - name: array anyarray + optional: false + type: any + - name: n integer + optional: false + type: any + tags: [] + aliases: [] + summary: Trims an array by removing the last n elements. + description: Trims an array by removing the last n elements. If the array is multidimensional, + only the first dimension is trimmed. + examples: [] + - name: TRIM_SCALE + category_id: numeric_math_functions + category_label: Numeric/Math Functions + signature: + display: TRIM_SCALE(numeric) + args: + - name: numeric + optional: false + type: any + tags: [] + aliases: [] + summary: Reduces the value's scale (number of fractional decimal digits) by removing + description: Reduces the value's scale (number of fractional decimal digits) by + removing trailing zeroes + examples: [] + - name: TRUNC + category_id: network_address_functions + category_label: Network Address Functions + signature: + display: TRUNC(macaddr) + args: + - name: macaddr + optional: false + type: any + tags: [] + aliases: [] + summary: Sets the last 3 bytes of the address to zero. + description: Sets the last 3 bytes of the address to zero. The remaining prefix + can be associated with a particular manufacturer (using data not included in + PostgreSQL). + examples: [] + - name: TSQUERY_PHRASE + category_id: text_search_functions + category_label: Text Search Functions + signature: + display: TSQUERY_PHRASE(query1 tsquery, query2 tsquery) + args: + - name: query1 tsquery + optional: false + type: any + - name: query2 tsquery + optional: false + type: any + tags: [] + aliases: [] + summary: Constructs a phrase query that searches for matches of query1 and query2 + at + description: Constructs a phrase query that searches for matches of query1 and + query2 at successive lexemes (same as <-> operator). + examples: [] + - name: TSVECTOR_TO_ARRAY + category_id: text_search_functions + category_label: Text Search Functions + signature: + display: TSVECTOR_TO_ARRAY(tsvector) + args: + - name: tsvector + optional: false + type: any + tags: [] + aliases: [] + summary: Converts a tsvector to an array of lexemes. + description: Converts a tsvector to an array of lexemes. + examples: [] + - name: TSVECTOR_UPDATE_TRIGGER + category_id: trigger_functions + category_label: Trigger Functions + signature: + display: TSVECTOR_UPDATE_TRIGGER + args: [] + tags: [] + aliases: [] + summary: Automatically updates a tsvector column from associated plain-text document + description: Automatically updates a tsvector column from associated plain-text + document column(s). The text search configuration to use is specified by name + as a trigger argument. See Section 12.4.3 for details. + examples: [] + - name: TSVECTOR_UPDATE_TRIGGER_COLUMN + category_id: trigger_functions + category_label: Trigger Functions + signature: + display: TSVECTOR_UPDATE_TRIGGER_COLUMN + args: [] + tags: [] + aliases: [] + summary: Automatically updates a tsvector column from associated plain-text document + description: Automatically updates a tsvector column from associated plain-text + document column(s). The text search configuration to use is taken from a regconfig + column of the table. See Section 12.4.3 for details. + examples: [] + - name: TS_DEBUG + category_id: text_search_functions + category_label: Text Search Functions + signature: + display: TS_DEBUG([ config regconfig, ] document text) + args: + - name: '[ config regconfig' + optional: false + type: any + - name: '] document text' + optional: false + type: any + tags: [] + aliases: [] + summary: Extracts and normalizes tokens from the document according to the specified + description: Extracts and normalizes tokens from the document according to the + specified or default text search configuration, and returns information about + how each token was processed. See Section 12.8.1 for details. + examples: [] + - name: TS_DELETE + category_id: text_search_functions + category_label: Text Search Functions + signature: + display: TS_DELETE(vector tsvector, lexeme text) + args: + - name: vector tsvector + optional: false + type: any + - name: lexeme text + optional: false + type: any + tags: [] + aliases: [] + summary: Removes any occurrence of the given lexeme from the vector. + description: Removes any occurrence of the given lexeme from the vector. The lexeme + string is treated as a lexeme as-is, without further processing. + examples: [] + - name: TS_FILTER + category_id: text_search_functions + category_label: Text Search Functions + signature: + display: TS_FILTER(vector tsvector, weights "char"[]) + args: + - name: vector tsvector + optional: false + type: any + - name: weights "char"[] + optional: false + type: any + tags: [] + aliases: [] + summary: Selects only elements with the given weights from the vector. + description: Selects only elements with the given weights from the vector. + examples: [] + - name: TS_HEADLINE + category_id: text_search_functions + category_label: Text Search Functions + signature: + display: TS_HEADLINE([ config regconfig, ] document text, query tsquery [, options + text ]) + args: + - name: '[ config regconfig' + optional: false + type: any + - name: '] document text' + optional: false + type: any + - name: query tsquery [ + optional: false + type: any + - name: options text ] + optional: false + type: any + tags: [] + aliases: [] + summary: Displays, in an abbreviated form, the match(es) for the query in the + description: Displays, in an abbreviated form, the match(es) for the query in + the document, which must be raw text not a tsvector. Words in the document are + normalized according to the specified or default configuration before matching + to the query. Use of this function is discussed in Section 12.3.4, which also + describes the available options. + examples: [] + - name: TS_LEXIZE + category_id: text_search_functions + category_label: Text Search Functions + signature: + display: TS_LEXIZE(dict regdictionary, token text) + args: + - name: dict regdictionary + optional: false + type: any + - name: token text + optional: false + type: any + tags: [] + aliases: [] + summary: Returns an array of replacement lexemes if the input token is known to + the + description: Returns an array of replacement lexemes if the input token is known + to the dictionary, or an empty array if the token is known to the dictionary + but it is a stop word, or NULL if it is not a known word. See Section 12.8.3 + for details. + examples: [] + - name: TS_PARSE + category_id: text_search_functions + category_label: Text Search Functions + signature: + display: TS_PARSE(parser_name text, document text) + args: + - name: parser_name text + optional: false + type: any + - name: document text + optional: false + type: any + tags: [] + aliases: [] + summary: Extracts tokens from the document using the named parser. + description: Extracts tokens from the document using the named parser. See Section + 12.8.2 for details. + examples: [] + - name: TS_RANK + category_id: text_search_functions + category_label: Text Search Functions + signature: + display: TS_RANK([ weights real[], ] vector tsvector, query tsquery [, normalization + integer ]) + args: + - name: '[ weights real[]' + optional: false + type: any + - name: '] vector tsvector' + optional: false + type: any + - name: query tsquery [ + optional: false + type: any + - name: normalization integer ] + optional: false + type: any + tags: [] + aliases: [] + summary: Computes a score showing how well the vector matches the query. + description: Computes a score showing how well the vector matches the query. See + Section 12.3.3 for details. + examples: [] + - name: TS_RANK_CD + category_id: text_search_functions + category_label: Text Search Functions + signature: + display: TS_RANK_CD([ weights real[], ] vector tsvector, query tsquery [, normalization + integer ]) + args: + - name: '[ weights real[]' + optional: false + type: any + - name: '] vector tsvector' + optional: false + type: any + - name: query tsquery [ + optional: false + type: any + - name: normalization integer ] + optional: false + type: any + tags: [] + aliases: [] + summary: Computes a score showing how well the vector matches the query, using + a + description: Computes a score showing how well the vector matches the query, using + a cover density algorithm. See Section 12.3.3 for details. + examples: [] + - name: TS_REWRITE + category_id: text_search_functions + category_label: Text Search Functions + signature: + display: TS_REWRITE(query tsquery, target tsquery, substitute tsquery) + args: + - name: query tsquery + optional: false + type: any + - name: target tsquery + optional: false + type: any + - name: substitute tsquery + optional: false + type: any + tags: [] + aliases: [] + summary: Replaces occurrences of target with substitute within the query. + description: Replaces occurrences of target with substitute within the query. + See Section 12.4.2.1 for details. + examples: [] + - name: TS_STAT + category_id: text_search_functions + category_label: Text Search Functions + signature: + display: TS_STAT(sqlquery text [, weights text ]) + args: + - name: sqlquery text [ + optional: false + type: any + - name: weights text ] + optional: false + type: any + tags: [] + aliases: [] + summary: Executes the sqlquery, which must return a single tsvector column, and + description: Executes the sqlquery, which must return a single tsvector column, + and returns statistics about each distinct lexeme contained in the data. See + Section 12.4.4 for details. + examples: [] + - name: TS_TOKEN_TYPE + category_id: text_search_functions + category_label: Text Search Functions + signature: + display: TS_TOKEN_TYPE(parser_name text) + args: + - name: parser_name text + optional: false + type: any + tags: [] + aliases: [] + summary: Returns a table that describes each type of token the named parser can + description: Returns a table that describes each type of token the named parser + can recognize. See Section 12.8.2 for details. + examples: [] + - name: TXID_CURRENT + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: TXID_CURRENT + args: [] + tags: [] + aliases: [] + summary: See pg_current_xact_id(). + description: See pg_current_xact_id(). + examples: [] + - name: TXID_CURRENT_IF_ASSIGNED + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: TXID_CURRENT_IF_ASSIGNED + args: [] + tags: [] + aliases: [] + summary: See pg_current_xact_id_if_assigned(). + description: See pg_current_xact_id_if_assigned(). + examples: [] + - name: TXID_CURRENT_SNAPSHOT + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: TXID_CURRENT_SNAPSHOT + args: [] + tags: [] + aliases: [] + summary: See pg_current_snapshot(). + description: See pg_current_snapshot(). + examples: [] + - name: TXID_SNAPSHOT_XIP + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: TXID_SNAPSHOT_XIP(txid_snapshot) + args: + - name: txid_snapshot + optional: false + type: any + tags: [] + aliases: [] + summary: See pg_snapshot_xip(). + description: See pg_snapshot_xip(). + examples: [] + - name: TXID_SNAPSHOT_XMAX + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: TXID_SNAPSHOT_XMAX(txid_snapshot) + args: + - name: txid_snapshot + optional: false + type: any + tags: [] + aliases: [] + summary: See pg_snapshot_xmax(). + description: See pg_snapshot_xmax(). + examples: [] + - name: TXID_SNAPSHOT_XMIN + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: TXID_SNAPSHOT_XMIN(txid_snapshot) + args: + - name: txid_snapshot + optional: false + type: any + tags: [] + aliases: [] + summary: See pg_snapshot_xmin(). + description: See pg_snapshot_xmin(). + examples: [] + - name: TXID_STATUS + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: TXID_STATUS(bigint) + args: + - name: bigint + optional: false + type: any + tags: [] + aliases: [] + summary: See pg_xact_status(). + description: See pg_xact_status(). + examples: [] + - name: TXID_VISIBLE_IN_SNAPSHOT + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: TXID_VISIBLE_IN_SNAPSHOT(bigint, txid_snapshot) + args: + - name: bigint + optional: false + type: any + - name: txid_snapshot + optional: false + type: any + tags: [] + aliases: [] + summary: See pg_visible_in_snapshot(). + description: See pg_visible_in_snapshot(). + examples: [] + - name: UNICODE_ASSIGNED + category_id: string_functions + category_label: String Functions + signature: + display: UNICODE_ASSIGNED(text) + args: + - name: text + optional: false + type: any + tags: [] + aliases: [] + summary: Returns true if all characters in the string are assigned Unicode + description: Returns true if all characters in the string are assigned Unicode + codepoints; false otherwise. This function can only be used when the server + encoding is UTF8. + examples: [] + - name: UNICODE_VERSION + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: UNICODE_VERSION + args: [] + tags: [] + aliases: [] + summary: Returns a string representing the version of Unicode used by PostgreSQL. + description: Returns a string representing the version of Unicode used by PostgreSQL. + examples: [] + - name: UNISTR + category_id: string_functions + category_label: String Functions + signature: + display: UNISTR(text) + args: + - name: text + optional: false + type: any + tags: [] + aliases: [] + summary: Evaluate escaped Unicode characters in the argument. + description: Evaluate escaped Unicode characters in the argument. Unicode characters + can be specified as \XXXX (4 hexadecimal digits), \+XXXXXX (6 hexadecimal digits), + \uXXXX (4 hexadecimal digits), or \UXXXXXXXX (8 hexadecimal digits). To specify + a backslash, write two backslashes. All other characters are taken literally. + examples: [] + - name: UNNEST1 + category_id: text_search_functions + category_label: Text Search Functions + signature: + display: UNNEST1(tsvector) + args: + - name: tsvector + optional: false + type: any + tags: [] + aliases: [] + summary: Expands a tsvector into a set of rows, one per lexeme. + description: Expands a tsvector into a set of rows, one per lexeme. + examples: [] + - name: UNNEST2 + category_id: array_functions + category_label: Array Functions + signature: + display: UNNEST2(anyarray) + args: + - name: anyarray + optional: false + type: any + tags: [] + aliases: [] + summary: Expands an array into a set of rows. + description: Expands an array into a set of rows. The array's elements are read + out in storage order. + examples: [] + - name: UNNEST3 + category_id: range_functions + category_label: Range Functions + signature: + display: UNNEST3(anymultirange) + args: + - name: anymultirange + optional: false + type: any + tags: [] + aliases: [] + summary: Expands a multirange into a set of ranges in ascending order. + description: Expands a multirange into a set of ranges in ascending order. + examples: [] + - name: UPPER1 + category_id: string_functions + category_label: String Functions + signature: + display: UPPER1(text) + args: + - name: text + optional: false + type: any + tags: [] + aliases: [] + summary: Converts the string to all upper case, according to the rules of the + description: Converts the string to all upper case, according to the rules of + the database's locale. + examples: [] + - name: UPPER2 + category_id: range_functions + category_label: Range Functions + signature: + display: UPPER2(anyrange) + args: + - name: anyrange + optional: false + type: any + tags: [] + aliases: [] + summary: Extracts the upper bound of the range (NULL if the range is empty or + has no + description: Extracts the upper bound of the range (NULL if the range is empty + or has no upper bound). + examples: [] + - name: UPPER3 + category_id: range_functions + category_label: Range Functions + signature: + display: UPPER3(anymultirange) + args: + - name: anymultirange + optional: false + type: any + tags: [] + aliases: [] + summary: Extracts the upper bound of the multirange (NULL if the multirange is + empty + description: Extracts the upper bound of the multirange (NULL if the multirange + is empty or has no upper bound). + examples: [] + - name: UPPER_INC1 + category_id: range_functions + category_label: Range Functions + signature: + display: UPPER_INC1(anyrange) + args: + - name: anyrange + optional: false + type: any + tags: [] + aliases: [] + summary: Is the range's upper bound inclusive? + description: Is the range's upper bound inclusive? + examples: [] + - name: UPPER_INC2 + category_id: range_functions + category_label: Range Functions + signature: + display: UPPER_INC2(anymultirange) + args: + - name: anymultirange + optional: false + type: any + tags: [] + aliases: [] + summary: Is the multirange's upper bound inclusive? + description: Is the multirange's upper bound inclusive? + examples: [] + - name: UPPER_INF1 + category_id: range_functions + category_label: Range Functions + signature: + display: UPPER_INF1(anyrange) + args: + - name: anyrange + optional: false + type: any + tags: [] + aliases: [] + summary: Does the range have no upper bound? + description: Does the range have no upper bound? (An upper bound of Infinity returns + false.) + examples: [] + - name: UPPER_INF2 + category_id: range_functions + category_label: Range Functions + signature: + display: UPPER_INF2(anymultirange) + args: + - name: anymultirange + optional: false + type: any + tags: [] + aliases: [] + summary: Does the multirange have no upper bound? + description: Does the multirange have no upper bound? (An upper bound of Infinity + returns false.) + examples: [] + - name: VARIANCE + category_id: aggregate_functions + category_label: Aggregate Functions + signature: + display: VARIANCE(numeric_type) + args: + - name: numeric_type + optional: false + type: any + tags: [] + aliases: [] + summary: This is a historical alias for var_samp. + description: This is a historical alias for var_samp. + examples: [] + - name: VERSION + category_id: session_information_functions + category_label: Session Information Functions + signature: + display: VERSION + args: [] + tags: [] + aliases: [] + summary: Returns a string describing the PostgreSQL server's version. + description: Returns a string describing the PostgreSQL server's version. You + can also get this information from server_version, or for a machine-readable + version use server_version_num. Software developers should use server_version_num + (available since 8.2) or PQserverVersion instead of parsing the text version. + examples: [] + - name: WEBSEARCH_TO_TSQUERY + category_id: text_search_functions + category_label: Text Search Functions + signature: + display: WEBSEARCH_TO_TSQUERY([ config regconfig, ] query text) + args: + - name: '[ config regconfig' + optional: false + type: any + - name: '] query text' + optional: false + type: any + tags: [] + aliases: [] + summary: Converts text to a tsquery, normalizing words according to the specified + or + description: Converts text to a tsquery, normalizing words according to the specified + or default configuration. Quoted word sequences are converted to phrase tests. + The word "or" is understood as producing an OR operator, and a dash produces + a NOT operator; other punctuation is ignored. This approximates the behavior + of some common web search tools. + examples: [] + - name: WIDTH + category_id: geometric_functions + category_label: Geometric Functions + signature: + display: WIDTH(box) + args: + - name: box + optional: false + type: any + tags: [] + aliases: [] + summary: Computes horizontal size of box. + description: Computes horizontal size of box. + examples: [] + - name: XMLAGG + category_id: aggregate_functions + category_label: Aggregate Functions + signature: + display: XMLAGG(xml ORDER BY input_sort_columns) + args: + - name: xml ORDER BY input_sort_columns + optional: false + type: any + tags: [] + aliases: [] + summary: Concatenates the non-null XML input values (see Section 9.15.1.8). + description: Concatenates the non-null XML input values (see Section 9.15.1.8). + examples: [] +versions: + '15': {} + '16': {} + '17': {} + '18': {} diff --git a/structures/engines/specification.yaml b/structures/engines/specification.yaml new file mode 100644 index 0000000..74bcb4d --- /dev/null +++ b/structures/engines/specification.yaml @@ -0,0 +1,70 @@ +schema_version: 1 +scope: global +documentation: + purpose: Shared SQL vocabulary applied before engine-specific specifications. + fields: + - schema_version: Specification schema version. + - scope: Must be global for this file. + - common.keywords: Common SQL keywords used across engines. + - common.functions: Optional common functions used across engines. + notes: + - Values are merged with engine specification common and version deltas. + - Keywords and function names are matched case-insensitively. +example: + schema_version: 1 + scope: global + common: + keywords: + - SELECT + - FROM + functions: + - COALESCE +common: + keywords: + - ALTER + - AND + - AS + - ASC + - BETWEEN + - CREATE + - CROSS JOIN + - DELETE + - DESC + - DESCRIBE + - DROP + - EXISTS + - EXPLAIN + - 'FALSE' + - FROM + - FULL JOIN + - GROUP BY + - HAVING + - IN + - INNER JOIN + - INSERT + - IS NOT NULL + - IS NULL + - JOIN + - LEFT JOIN + - LIKE + - LIMIT + - MERGE + - NOT + - 'NULL' + - NULLS FIRST + - NULLS LAST + - OFFSET + - 'ON' + - OR + - ORDER BY + - REPLACE + - RIGHT JOIN + - SELECT + - SHOW + - 'TRUE' + - TRUNCATE + - UPDATE + - USING + - WHERE + - WITH + functions: [] diff --git a/structures/engines/sqlite/context.py b/structures/engines/sqlite/context.py index 8ba2cb3..23b1ebc 100755 --- a/structures/engines/sqlite/context.py +++ b/structures/engines/sqlite/context.py @@ -10,14 +10,39 @@ from structures.connection import Connection from structures.engines.context import QUERY_LOGS, AbstractContext -from structures.engines.database import SQLDatabase, SQLTable, SQLColumn, SQLIndex, SQLForeignKey, SQLTrigger +from structures.engines.database import ( + SQLDatabase, + SQLTable, + SQLColumn, + SQLIndex, + SQLForeignKey, + SQLTrigger, +) from structures.engines.datatype import SQLDataType from structures.engines.indextype import SQLIndexType -from structures.engines.sqlite.database import SQLiteTable, SQLiteColumn, SQLiteIndex, SQLiteForeignKey, SQLiteRecord, SQLiteView, SQLiteTrigger, SQLiteDatabase, SQLiteCheck +from structures.engines.sqlite.database import ( + SQLiteTable, + SQLiteColumn, + SQLiteIndex, + SQLiteForeignKey, + SQLiteRecord, + SQLiteView, + SQLiteTrigger, + SQLiteDatabase, + SQLiteCheck, +) from structures.engines.sqlite.datatype import SQLiteDataType from structures.engines.sqlite.indextype import SQLiteIndexType -from structures.engines.sqlite import COLLATIONS, MAP_COLUMN_FIELDS, COLUMNS_PATTERN, COLUMN_ATTRIBUTES_PATTERN, TABLE_CONSTRAINTS_PATTERN, ENGINE_KEYWORDS, ENGINE_FUNCTIONS +from structures.engines.sqlite import ( + COLLATIONS, + MAP_COLUMN_FIELDS, + COLUMNS_PATTERN, + COLUMN_ATTRIBUTES_PATTERN, + TABLE_CONSTRAINTS_PATTERN, + ENGINE_KEYWORDS, + ENGINE_FUNCTIONS, +) class SQLiteContext(AbstractContext): @@ -31,7 +56,8 @@ class SQLiteContext(AbstractContext): DATATYPE = SQLiteDataType() INDEXTYPE = SQLiteIndexType() - IDENTIFIER_QUOTE = '"' + IDENTIFIER_QUOTE_CHAR = '"' + DEFAULT_STATEMENT_SEPARATOR = ";" _map_sqlite_master = defaultdict(lambda: defaultdict(dict)) @@ -40,8 +66,24 @@ def __init__(self, connection: Connection): self.filename = connection.configuration.filename - def _on_connect(self, *args, **kwargs): - super()._on_connect(*args, **kwargs) + def after_connect(self, *args, **kwargs): + super().after_connect(*args, **kwargs) + + server_version = self.get_server_version() + spec_keywords, spec_functions = self.get_engine_vocabulary( + "sqlite", server_version + ) + self.KEYWORDS = tuple( + dict.fromkeys( + spec_keywords + tuple(value.upper() for value in ENGINE_KEYWORDS) + ) + ) + self.FUNCTIONS = tuple( + dict.fromkeys( + spec_functions + tuple(value.upper() for value in ENGINE_FUNCTIONS) + ) + ) + self.execute("PRAGMA database_list;") self.execute("PRAGMA foreign_keys = ON;") # self.execute("PRAGMA case_sensitive_like = ON") @@ -64,7 +106,10 @@ def connect(self, **connect_kwargs) -> None: else: self._connection.row_factory = sqlite3.Row self._cursor = self._connection.cursor() - self._on_connect() + self.after_connect() + + def set_database(self, database: SQLDatabase) -> None: + pass def get_server_version(self) -> str: self.execute("SELECT sqlite_version()") @@ -77,19 +122,25 @@ def get_server_uptime(self) -> Optional[int]: def get_databases(self) -> list[SQLDatabase]: self.execute("SELECT * from sqlite_master ORDER BY name") for i, result in enumerate(self.fetchall()): - self._map_sqlite_master[result['tbl_name']][result['type']][result['name']] = result['sql'] + self._map_sqlite_master[result["tbl_name"]][result["type"]][ + result["name"] + ] = result["sql"] - self.execute("SELECT page_count * page_size as total_bytes FROM pragma_page_count(), pragma_page_size();") + self.execute( + "SELECT page_count * page_size as total_bytes FROM pragma_page_count(), pragma_page_size();" + ) - return [SQLiteDatabase( - id=0, - name='main', - context=self, - total_bytes=float(self.fetchone()['total_bytes']), - get_tables_handler=self.get_tables, - get_views_handler=self.get_views, - get_triggers_handler=self.get_triggers, - )] + return [ + SQLiteDatabase( + id=0, + name="main", + context=self, + total_bytes=float(self.fetchone()["total_bytes"]), + get_tables_handler=self.get_tables, + get_views_handler=self.get_views, + get_triggers_handler=self.get_triggers, + ) + ] def get_tables(self, database: SQLDatabase) -> list[SQLTable]: QUERY_LOGS.append(f"/* get_tables for database={database.name} */") @@ -98,11 +149,15 @@ def get_tables(self, database: SQLDatabase) -> list[SQLTable]: self._map_sqlite_master.clear() self.execute("SELECT * from sqlite_master ORDER BY name") for result in self.fetchall(): - self._map_sqlite_master[result['tbl_name']][result['type']][result['name']] = result['sql'] + self._map_sqlite_master[result["tbl_name"]][result["type"]][ + result["name"] + ] = result["sql"] has_sqlite_sequence = False - self.execute(""" SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'sqlite_sequence'; """) + self.execute( + """ SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'sqlite_sequence'; """ + ) if self.fetchone(): has_sqlite_sequence = True @@ -117,7 +172,7 @@ def get_tables(self, database: SQLDatabase) -> list[SQLTable]: selects.append("0 AS autoincrement_value") self.execute(f""" - SELECT {', '.join(selects)} + SELECT {", ".join(selects)} FROM sqlite_master as sM JOIN dbstat As dbS ON dbS.name = sM.name {f"LEFT JOIN sqlite_sequence as sS ON sS.name = sM.tbl_name" if has_sqlite_sequence else ""} @@ -128,15 +183,15 @@ def get_tables(self, database: SQLDatabase) -> list[SQLTable]: results = [] for i, row in enumerate(self.fetchall()): - if row['type'] == 'table': + if row["type"] == "table": results.append( SQLiteTable( id=i, - name=row['tbl_name'], + name=row["tbl_name"], database=database, - engine='default', + engine="default", auto_increment=int(row["autoincrement_value"]), - total_bytes=row['total_bytes'], + total_bytes=row["total_bytes"], total_rows=row["total_rows"], collation_name="BINARY", get_columns_handler=self.get_columns, @@ -158,18 +213,32 @@ def get_columns(self, table: SQLiteTable) -> list[SQLColumn]: self._map_sqlite_master.clear() self.execute("SELECT * from sqlite_master ORDER BY name") for result in self.fetchall(): - self._map_sqlite_master[result['tbl_name']][result['type']][result['name']] = result['sql'] - - if not (table_match := re.search(r"""CREATE\s+TABLE\s+(?:[`'"]?\w+[`'"]?\s+)?\((?P.*)\)""", self._map_sqlite_master[table.name]["table"][table.name], re.IGNORECASE | re.DOTALL)): + self._map_sqlite_master[result["tbl_name"]][result["type"]][ + result["name"] + ] = result["sql"] + + if not ( + table_match := re.search( + r"""CREATE\s+TABLE\s+(?:[`'"]?\w+[`'"]?\s+)?\((?P.*)\)""", + self._map_sqlite_master[table.name]["table"][table.name], + re.IGNORECASE | re.DOTALL, + ) + ): return results table_group_dict = table_match.groupdict() - columns = re.sub(r'\s*--\s*.*', '', table_group_dict['columns']) + columns = re.sub(r"\s*--\s*.*", "", table_group_dict["columns"]) - columns_matches = re.findall(r'([^,(]+(?:\([^)]*\)[^,(]*)*)(?:\s*,\s*|$)', columns, re.DOTALL) + columns_matches = re.findall( + r"([^,(]+(?:\([^)]*\)[^,(]*)*)(?:\s*,\s*|$)", columns, re.DOTALL + ) - columns = [re.sub(r'\s+', ' ', match).strip().rstrip(',') for match in columns_matches if match.strip()] + columns = [ + re.sub(r"\s+", " ", match).strip().rstrip(",") + for match in columns_matches + if match.strip() + ] for i, column in enumerate(columns): is_special_syntax = False @@ -180,8 +249,12 @@ def get_columns(self, table: SQLiteTable) -> list[SQLColumn]: break for prefix in ["CONSTRAINT", "CHECK"]: - if re.match(f"^{re.escape(prefix)}", column[:len(prefix)], re.IGNORECASE): - self._map_sqlite_master[table.name]["constraints"].setdefault(prefix, []).append(column) + if re.match( + f"^{re.escape(prefix)}", column[: len(prefix)], re.IGNORECASE + ): + self._map_sqlite_master[table.name]["constraints"].setdefault( + prefix, [] + ).append(column) is_special_syntax = True break @@ -194,14 +267,16 @@ def get_columns(self, table: SQLiteTable) -> list[SQLColumn]: column_dict = columns_match.groupdict() attr_dict = {} - attributes_str = column_dict.pop('attributes').strip() + attributes_str = column_dict.pop("attributes").strip() for pattern in COLUMN_ATTRIBUTES_PATTERN: if not attributes_str: break if m := pattern.search(attributes_str): - attributes_str = attributes_str.replace(m.group(0), '', 1).strip() - attr_dict.update({k: v for k, v in m.groupdict().items() if v is not None}) + attributes_str = attributes_str.replace(m.group(0), "", 1).strip() + attr_dict.update( + {k: v for k, v in m.groupdict().items() if v is not None} + ) column_dict.update(attr_dict) @@ -209,18 +284,19 @@ def get_columns(self, table: SQLiteTable) -> list[SQLColumn]: SQLiteColumn( id=i, name=column_dict["name"], - datatype=SQLiteDataType.get_by_name(column_dict['datatype']), - is_nullable=column_dict.get('is_nullable', "NULL") == "NULL", + datatype=SQLiteDataType.get_by_name(column_dict["datatype"]), + is_nullable=column_dict.get("is_nullable", "NULL") == "NULL", table=table, - server_default=column_dict.get('default'), - is_auto_increment=column_dict.get("is_auto_increment") == "AUTOINCREMENT", - length=column_dict['length'], - numeric_precision=column_dict['precision'], - numeric_scale=column_dict['scale'], - virtuality=column_dict.get('virtuality'), - expression=column_dict.get('expression'), - collation_name=column_dict.get('collate'), - check=column_dict.get('check'), + server_default=column_dict.get("default"), + is_auto_increment=column_dict.get("is_auto_increment") + == "AUTOINCREMENT", + length=column_dict["length"], + numeric_precision=column_dict["precision"], + numeric_scale=column_dict["scale"], + virtuality=column_dict.get("virtuality"), + expression=column_dict.get("expression"), + collation_name=column_dict.get("collate"), + check=column_dict.get("check"), ) ) @@ -231,20 +307,25 @@ def get_checks(self, table: SQLiteTable) -> list[SQLiteCheck]: if table is None or table.is_new: return results - for check_type, constraints in self._map_sqlite_master[table.name]["constraints"].items(): - + for check_type, constraints in self._map_sqlite_master[table.name][ + "constraints" + ].items(): for i, constraint in enumerate(constraints): if not TABLE_CONSTRAINTS_PATTERN.get(check_type): continue - if constraint_column := re.search(TABLE_CONSTRAINTS_PATTERN[check_type].pattern, constraint, re.IGNORECASE | re.DOTALL): + if constraint_column := re.search( + TABLE_CONSTRAINTS_PATTERN[check_type].pattern, + constraint, + re.IGNORECASE | re.DOTALL, + ): constraint_column_dict = constraint_column.groupdict() results.append( SQLiteCheck( id=i, name=constraint_column_dict.get("constraint_name"), table=table, - expression=constraint_column_dict.get("check") + expression=constraint_column_dict.get("check"), ) ) @@ -261,48 +342,58 @@ def get_indexes(self, table: SQLiteTable) -> list[SQLIndex]: self._map_sqlite_master.clear() self.execute("SELECT * from sqlite_master ORDER BY name") for result in self.fetchall(): - self._map_sqlite_master[result['tbl_name']][result['type']][result['name']] = result['sql'] + self._map_sqlite_master[result["tbl_name"]][result["type"]][ + result["name"] + ] = result["sql"] results = [] - self.execute(f"SELECT * FROM pragma_table_info('{table.name}') WHERE pk != 0 ORDER BY pk;") + self.execute( + f"SELECT * FROM pragma_table_info('{table.name}') WHERE pk != 0 ORDER BY pk;" + ) if (pk_index := self.fetchall()) and len(pk_index): results.append( SQLiteIndex( id=0, name="PRIMARY KEY", type=SQLiteIndexType.PRIMARY, - columns=[col['name'] for col in pk_index], + columns=[col["name"] for col in pk_index], table=table, ) ) - self.execute(f"SELECT * FROM pragma_index_list('{table.name}') WHERE `origin` != 'pk' order by seq desc;") + self.execute( + f"SELECT * FROM pragma_index_list('{table.name}') WHERE `origin` != 'pk' order by seq desc;" + ) for idx in [dict(row) for row in self.cursor.fetchall()]: - id = int(idx['seq']) + 1 - name = idx['name'] - is_unique = bool(idx.get('unique', False)) - is_partial = bool(idx.get('partial', False)) + id = int(idx["seq"]) + 1 + name = idx["name"] + is_unique = bool(idx.get("unique", False)) + is_partial = bool(idx.get("partial", False)) self.execute(f"SELECT * FROM pragma_index_info('{name}');") pragma_index_info = self.fetchone() - is_expression = True if pragma_index_info['cid'] == -2 else False + is_expression = True if pragma_index_info["cid"] == -2 else False columns = [] condition = "" if name.startswith("sqlite_"): - columns = [pragma_index_info['name']] + columns = [pragma_index_info["name"]] else: sql = self._map_sqlite_master[table.name]["index"][name] - if search := re.search(r'CREATE\s+(?:UNIQUE\s+)?INDEX\s+\w+\s+ON\s+\w+\s*\((?P(?:[^()]+|\([^()]*\))+)\)(?:\s+WHERE\s+(?P.+))?', sql, re.IGNORECASE | re.DOTALL): + if search := re.search( + r"CREATE\s+(?:UNIQUE\s+)?INDEX\s+\w+\s+ON\s+\w+\s*\((?P(?:[^()]+|\([^()]*\))+)\)(?:\s+WHERE\s+(?P.+))?", + sql, + re.IGNORECASE | re.DOTALL, + ): groups = search.groupdict() - columns = groups['columns'].strip().split(',') - condition = groups.get('conditions', []) + columns = groups["columns"].strip().split(",") + condition = groups.get("conditions", []) # Determine index type index_type = SQLiteIndexType.INDEX @@ -337,16 +428,18 @@ def get_foreign_keys(self, table: SQLiteTable) -> list[SQLForeignKey]: QUERY_LOGS.append(f"/* get_foreign_keys for table={table.name} */") - self.execute(f"SELECT" - f" `id`, `table`, GROUP_CONCAT(`from`) as `from`, GROUP_CONCAT(`to`) as `to`, `on_update`, `on_delete`" - f" FROM pragma_foreign_key_list('{table.name}') GROUP BY id;") + self.execute( + f"SELECT" + f" `id`, `table`, GROUP_CONCAT(`from`) as `from`, GROUP_CONCAT(`to`) as `to`, `on_update`, `on_delete`" + f" FROM pragma_foreign_key_list('{table.name}') GROUP BY id;" + ) foreign_keys = [] for fk in [dict(row) for row in self.fetchall()]: - id = fk['id'] - columns = fk['from'].split(",") - reference_columns = fk['to'].split(",") - name = f"""fk_{table.name}_{'_'.join(columns)}-{fk['table']}_{'_'.join(reference_columns)}_{id}""" + id = fk["id"] + columns = fk["from"].split(",") + reference_columns = fk["to"].split(",") + name = f"""fk_{table.name}_{"_".join(columns)}-{fk["table"]}_{"_".join(reference_columns)}_{id}""" foreign_keys.append( SQLiteForeignKey( @@ -354,50 +447,71 @@ def get_foreign_keys(self, table: SQLiteTable) -> list[SQLForeignKey]: name=name, table=table, columns=columns, - reference_table=fk['table'], + reference_table=fk["table"], reference_columns=reference_columns, - on_update=fk.get('on_update', ''), - on_delete=fk.get('on_delete', ''), + on_update=fk.get("on_update", ""), + on_delete=fk.get("on_delete", ""), ) ) return foreign_keys - def get_records(self, table: SQLiteTable, /, *, filters: Optional[str] = None, limit: int = 1000, offset: int = 0, orders: Optional[str] = None) -> list[SQLiteRecord]: + def get_records( + self, + table: SQLiteTable, + /, + *, + filters: Optional[str] = None, + limit: int = 1000, + offset: int = 0, + orders: Optional[str] = None, + ) -> list[SQLiteRecord]: results = [] - for i, record in enumerate(super().get_records(table, filters=filters, limit=limit, offset=offset, orders=orders)): - results.append( - SQLiteRecord(id=i, table=table, values=dict(record)) + for i, record in enumerate( + super().get_records( + table, filters=filters, limit=limit, offset=offset, orders=orders ) + ): + results.append(SQLiteRecord(id=i, table=table, values=dict(record))) return results def get_views(self, database: SQLDatabase): results: list[SQLiteView] = [] - self.execute("SELECT * FROM sqlite_master WHERE type='view' AND name NOT LIKE 'sqlite_%' ORDER BY name") + self.execute( + "SELECT * FROM sqlite_master WHERE type='view' AND name NOT LIKE 'sqlite_%' ORDER BY name" + ) for i, result in enumerate(self.fetchall()): - results.append(SQLiteView( - id=i, - name=result['name'], - database=database, - sql=result['sql'] - )) + results.append( + SQLiteView( + id=i, + name=result["name"], + database=database, + statement=result["sql"], + ) + ) return results def get_triggers(self, database: SQLDatabase) -> list[SQLiteTrigger]: results: list[SQLiteTrigger] = [] - self.execute("SELECT * FROM sqlite_master WHERE type='trigger' AND name NOT LIKE 'sqlite_%' ORDER BY name") + self.execute( + "SELECT * FROM sqlite_master WHERE type='trigger' AND name NOT LIKE 'sqlite_%' ORDER BY name" + ) for i, result in enumerate(self.fetchall()): - results.append(SQLiteTrigger( - id=i, - name=result['name'], - database=database, - sql=result['sql'] - )) + results.append( + SQLiteTrigger( + id=i, + name=result["name"], + database=database, + statement=result["sql"], + ) + ) return results - def build_empty_table(self, database: SQLDatabase, /, name: Optional[str] = None, **default_values) -> SQLiteTable: + def build_empty_table( + self, database: SQLDatabase, /, name: Optional[str] = None, **default_values + ) -> SQLiteTable: id = SQLiteContext.get_temporary_id(database.tables) if name is None: @@ -407,28 +521,39 @@ def build_empty_table(self, database: SQLDatabase, /, name: Optional[str] = None id=id, name=name, database=database, - engine='sqlite', + engine="sqlite", get_indexes_handler=self.get_indexes, get_columns_handler=self.get_columns, get_foreign_keys_handler=self.get_foreign_keys, get_records_handler=self.get_records, ) - def build_empty_column(self, table: SQLiteTable, datatype: SQLDataType, /, name: Optional[str] = None, **default_values) -> SQLiteColumn: + def build_empty_column( + self, + table: SQLiteTable, + datatype: SQLDataType, + /, + name: Optional[str] = None, + **default_values, + ) -> SQLiteColumn: id = SQLiteContext.get_temporary_id(table.columns) if name is None: name = _(f"Column{str(id * -1):03}") return SQLiteColumn( - id=id, - name=name, - table=table, - datatype=datatype, - **default_values + id=id, name=name, table=table, datatype=datatype, **default_values ) - def build_empty_index(self, table: SQLiteTable, indextype: SQLIndexType, columns: list[str], /, name: Optional[str] = None, **default_values) -> SQLiteIndex: + def build_empty_index( + self, + table: SQLiteTable, + indextype: SQLIndexType, + columns: list[str], + /, + name: Optional[str] = None, + **default_values, + ) -> SQLiteIndex: id = SQLiteContext.get_temporary_id(table.indexes) if name is None: @@ -442,7 +567,35 @@ def build_empty_index(self, table: SQLiteTable, indextype: SQLIndexType, columns table=table, ) - def build_empty_foreign_key(self, table: SQLiteTable, columns: list[str], /, name: Optional[str] = None, **default_values) -> SQLiteForeignKey: + def build_empty_check( + self, + table: SQLiteTable, + /, + name: Optional[str] = None, + expression: Optional[str] = None, + **default_values, + ) -> SQLiteCheck: + from structures.engines.sqlite.database import SQLiteCheck + + id = SQLiteContext.get_temporary_id(table.checks) + + if name is None: + name = f"check_{abs(id)}" + + return SQLiteCheck( + id=id, name=name, table=table, expression=expression or "", **default_values + ) + + def build_empty_foreign_key( + self, + table: SQLiteTable, + columns: list[str], + reference_table: str, + reference_columns: list[str], + /, + name: Optional[str] = None, + **default_values, + ) -> SQLiteForeignKey: id = SQLiteContext.get_temporary_id(table.foreign_keys) if name is None: @@ -456,17 +609,19 @@ def build_empty_foreign_key(self, table: SQLiteTable, columns: list[str], /, nam reference_table="", reference_columns=[], on_update="", - on_delete="" + on_delete="", ) - def build_empty_record(self, table: SQLiteTable, /, *, values: dict[str, Any]) -> SQLiteRecord: + def build_empty_record( + self, table: SQLiteTable, /, *, values: dict[str, Any] + ) -> SQLiteRecord: return SQLiteRecord( - id=SQLiteContext.get_temporary_id(table.records), - table=table, - values=values + id=SQLiteContext.get_temporary_id(table.records), table=table, values=values ) - def build_empty_view(self, database: SQLDatabase, /, name: Optional[str] = None, **default_values) -> SQLiteView: + def build_empty_view( + self, database: SQLDatabase, /, name: Optional[str] = None, **default_values + ) -> SQLiteView: id = SQLiteContext.get_temporary_id(database.views) if name is None: @@ -476,18 +631,30 @@ def build_empty_view(self, database: SQLDatabase, /, name: Optional[str] = None, id=id, name=name, database=database, - sql=default_values.get("sql", ""), + statement=default_values.get("statement", ""), ) - def build_empty_trigger(self, database: SQLDatabase, /, name: Optional[str] = None, **default_values) -> SQLiteTrigger: + def build_empty_function( + self, database: SQLDatabase, /, name: Optional[str] = None, **default_values + ): + raise NotImplementedError("SQLite does not support stored functions") + + def build_empty_procedure( + self, database: SQLDatabase, /, name: Optional[str] = None, **default_values + ): + raise NotImplementedError("SQLite does not support stored procedures") + + def build_empty_trigger( + self, database: SQLDatabase, /, name: Optional[str] = None, **default_values + ) -> SQLiteTrigger: id = SQLiteContext.get_temporary_id(database.triggers) if name is None: - name = _(f"Trigger{str(id * -1):03}") + name = f"trigger_{id}" return SQLiteTrigger( id=id, name=name, database=database, - sql=default_values.get("sql", ""), + statement=default_values.get("statement", ""), ) diff --git a/structures/engines/sqlite/database.py b/structures/engines/sqlite/database.py index 5fa0a9d..f31cb0e 100644 --- a/structures/engines/sqlite/database.py +++ b/structures/engines/sqlite/database.py @@ -1,3 +1,4 @@ +import re import dataclasses from typing import Self, Optional, Dict @@ -20,14 +21,14 @@ class SQLiteDatabase(SQLDatabase): @dataclasses.dataclass(eq=False) class SQLiteTable(SQLTable): def set_auto_increment(self, auto_increment): - sql = f"UPDATE sqlite_sequence SET seq = {auto_increment} WHERE name = '{self.name}';" - self.database.context.execute(sql) + statement = f"UPDATE sqlite_sequence SET seq = {auto_increment} WHERE name = '{self.name}';" + self.database.context.execute(statement) return True def rename(self, table: Self, new_name: str) -> bool: - sql = f"ALTER TABLE `{table.name}` RENAME TO `{new_name}`;" - self.database.context.execute(sql) + statement = f"ALTER TABLE `{table.name}` RENAME TO `{new_name}`;" + self.database.context.execute(statement) return True @@ -43,6 +44,11 @@ def truncate(self): return True + # @property + # def fully_qualified_name(self): + # # SQLite doesn't need database prefix for table references + # return self.quoted_name + def raw_create(self) -> str: # PeterSQL schema emission policy (SQLite): # @@ -269,22 +275,32 @@ def drop(self) -> bool: @dataclasses.dataclass(eq=False) class SQLiteCheck(SQLCheck): - # name: Optional[str] = None - pass + def create(self) -> bool: + # SQLite doesn't support ADD CONSTRAINT for CHECK after table creation + # CHECK constraints must be defined inline during CREATE TABLE + raise NotImplementedError("SQLite does not support adding CHECK constraints after table creation") + + def drop(self) -> bool: + # SQLite doesn't support DROP CONSTRAINT + raise NotImplementedError("SQLite does not support dropping CHECK constraints") + + def alter(self) -> bool: + # SQLite doesn't support ALTER CONSTRAINT + raise NotImplementedError("SQLite does not support altering CHECK constraints") @dataclasses.dataclass(eq=False) class SQLiteColumn(SQLColumn): def add(self) -> bool: - sql = f"ALTER TABLE {self.table.sql_safe_name} ADD COLUMN {str(SQLiteColumnBuilder(self, exclude=['primary_key', 'auto_increment']))}" + statement = f"ALTER TABLE {self.table.fully_qualified_name} ADD COLUMN {str(SQLiteColumnBuilder(self, exclude=['primary_key', 'auto_increment']))}" - return self.table.database.context.execute(sql) + return self.table.database.context.execute(statement) def rename(self, new_name: str) -> bool: - return self.table.database.context.execute(f"ALTER TABLE {self.table.sql_safe_name} RENAME COLUMN {self.sql_safe_name} TO `{new_name}`") + return self.table.database.context.execute(f"ALTER TABLE {self.table.fully_qualified_name} RENAME COLUMN {self.quoted_name} TO `{new_name}`") def modify(self): - sql_safe_new_name = self.table.database.context.build_sql_safe_name(f"_{self.table.name}_{self.generate_uuid()}") + sql_safe_new_name = self.table.database.context.quote_identifier(f"_{self.table.name}_{self.generate_uuid()}") for i, c in enumerate(self.table.columns): if c.name == self.name: @@ -296,16 +312,20 @@ def modify(self): self.table.create() - transaction.execute(f"INSERT INTO {self.table.sql_safe_name} SELECT * FROM {sql_safe_new_name};") + transaction.execute(f"INSERT INTO {self.table.fully_qualified_name} SELECT * FROM {sql_safe_new_name};") transaction.execute(f"DROP TABLE {sql_safe_new_name};") def drop(self, table: SQLTable, column: SQLColumn) -> bool: - return self.table.database.context.execute(f"ALTER TABLE {table.sql_safe_name} DROP COLUMN {self.sql_safe_name}") + return self.table.database.context.execute(f"ALTER TABLE {table.fully_qualified_name} DROP COLUMN {self.quoted_name}") @dataclasses.dataclass(eq=False) class SQLiteIndex(SQLIndex): + @property + def fully_qualified_name(self): + return self.table.database.context.qualify(self.table.database.name, self.name) + def create(self) -> bool: if self.type == SQLiteIndexType.PRIMARY: return False # PRIMARY is handled in table creation @@ -315,14 +335,14 @@ def create(self) -> bool: unique_index = "UNIQUE INDEX" if self.type == SQLiteIndexType.UNIQUE else "INDEX" - build_sql_safe_name = self.table.database.context.build_sql_safe_name + quote_identifier = self.table.database.context.quote_identifier - columns_clause = ", ".join([build_sql_safe_name(column) for column in self.columns]) + columns_clause = ", ".join([quote_identifier(column) for column in self.columns]) where_clause = f" WHERE {self.condition}" if self.condition else "" statement = ( - f"CREATE {unique_index} IF NOT EXISTS {self.sql_safe_name} " - f"ON {self.table.sql_safe_name}({columns_clause}){where_clause} " + f"CREATE {unique_index} IF NOT EXISTS {self.fully_qualified_name} " + f"ON {self.table.name}({columns_clause}){where_clause} " ) return self.table.database.context.execute(statement) @@ -334,8 +354,10 @@ def drop(self) -> bool: if self.type == SQLiteIndexType.UNIQUE and self.name.startswith("sqlite_autoindex_"): return False # sqlite_ UNIQUE is handled in table creation + + print(f"DROP INDEX IF EXISTS {self.fully_qualified_name}") return self.table.database.context.execute( - f"DROP INDEX IF EXISTS {self.sql_safe_name}" + f"DROP INDEX IF EXISTS {self.fully_qualified_name}" ) def modify(self, new_index: Self): @@ -441,23 +463,29 @@ def delete(self) -> bool: class SQLiteView(SQLView): + def __init__(self, /, id: int, name: str, database: SQLDatabase, statement: str): + match = re.search(r'CREATE\s+VIEW\s+.*?\s+AS\s+(.*)', statement, re.IGNORECASE | re.DOTALL) + if match: + statement = match.group(1).strip() + + super().__init__(id=id, name=name, database=database, statement=statement) + def create(self) -> bool: - return self.database.context.execute(f"CREATE VIEW IF NOT EXISTS {self.name} AS {self.sql}") + return self.database.context.execute(f"CREATE VIEW IF NOT EXISTS {self.fully_qualified_name} AS {self.statement}") def drop(self) -> bool: - return self.database.context.execute(f"DROP VIEW IF EXISTS {self.name}") + return self.database.context.execute(f"DROP VIEW IF EXISTS {self.fully_qualified_name}") def alter(self) -> bool: - self.drop() - return self.create() + return self.database.context.execute(f"CREATE VIEW IF NOT EXISTS {self.fully_qualified_name} AS {self.statement}") class SQLiteTrigger(SQLTrigger): def create(self) -> bool: - return self.database.context.execute(f"CREATE TRIGGER IF NOT EXISTS {self.name} {self.sql}") + return self.database.context.execute(f"CREATE TRIGGER IF NOT EXISTS {self.fully_qualified_name} {self.statement}") def drop(self) -> bool: - return self.database.context.execute(f"DROP TRIGGER IF EXISTS {self.name}") + return self.database.context.execute(f"DROP TRIGGER IF EXISTS {self.fully_qualified_name}") def alter(self) -> bool: self.drop() diff --git a/structures/engines/sqlite/specification.yaml b/structures/engines/sqlite/specification.yaml new file mode 100644 index 0000000..70d062f --- /dev/null +++ b/structures/engines/sqlite/specification.yaml @@ -0,0 +1,1570 @@ +schema_version: 1 +engine: sqlite +documentation: + purpose: SQLite vocabulary and version deltas. + fields: + - schema_version: Specification schema version. + - engine: Engine name. + - common.keywords: Engine common keywords. + - common.functions: Engine common function specs. + - versions..keywords_remove: Keywords to remove for that major version. + - versions..functions_remove: Function names to remove for that major version. + notes: + - Runtime selection uses exact major match, else highest configured major <= server major. +example: + schema_version: 1 + engine: sqlite + common: + keywords: + - ROWID + functions: + - name: ABS + summary: Absolute value. + versions: + '3': + keywords_remove: + - LEGACY_KEYWORD +common: + keywords: [] + functions: + - name: ABS + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: ABS(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: The abs(X) function returns the absolute value of the numeric argument + X. + description: The abs(X) function returns the absolute value of the numeric argument + X. Abs(X) returns NULL if X is NULL. Abs(X) returns 0.0 if X is a string or + blob that cannot be converted to a numeric value. If X is the integer -9223372036854775808 + then abs(X) throws an integer overflow error since there is no equivalent positive + 64-bit two complement value. + examples: [] + - name: AVG + category_id: aggregate_functions + category_label: Aggregate Functions + signature: + display: AVG(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: The avg() function returns the average value of all non-NULL X within + a + description: The avg() function returns the average value of all non-NULL X within + a group. String and BLOB values that do not look like numbers are interpreted + as 0. The result of avg() is always a floating point value as long as at there + is at least one non-NULL input even if all inputs are integers. The result of + avg() is NULL if and only if there are no non-NULL inputs. + examples: [] + - name: CHANGES + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: CHANGES + args: [] + tags: [] + aliases: [] + summary: The changes() function returns the number of database rows that were + description: The changes() function returns the number of database rows that were + changed or inserted or deleted by the most recently completed INSERT, DELETE, + or UPDATE statement, exclusive of statements in lower-level triggers. The changes() + SQL function is a wrapper around the sqlite3_changes() C/C++ function and hence + follows the same rules for counting changes. + examples: [] + - name: CHAR + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: CHAR(X1,X2,...,XN) + args: + - name: X1 + optional: false + type: any + - name: X2 + optional: false + type: any + - name: '...' + optional: false + type: any + - name: XN + optional: false + type: any + tags: [] + aliases: [] + summary: The char(X1,X2,...,XN) function returns a string composed of characters + description: The char(X1,X2,...,XN) function returns a string composed of characters + having the unicode code point values of integers X1 through XN, respectively. + examples: [] + - name: COALESCE + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: COALESCE(X,Y,...) + args: + - name: X + optional: false + type: any + - name: Y + optional: false + type: any + - name: '...' + optional: false + type: any + tags: [] + aliases: [] + summary: The coalesce() function returns a copy of its first non-NULL argument, + or + description: The coalesce() function returns a copy of its first non-NULL argument, + or NULL if all arguments are NULL. Coalesce() must have at least 2 arguments. + examples: [] + - name: COUNT1 + category_id: aggregate_functions + category_label: Aggregate Functions + signature: + display: COUNT1(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: The count(X) function returns a count of the number of times that X is + not + description: The count(X) function returns a count of the number of times that + X is not NULL in a group. The count(*) function (with no arguments) returns + the total number of rows in the group. + examples: [] + - name: COUNT2 + category_id: aggregate_functions + category_label: Aggregate Functions + signature: + display: COUNT2(*) + args: + - name: '*' + optional: false + type: any + tags: [] + aliases: [] + summary: The count(X) function returns a count of the number of times that X is + not + description: The count(X) function returns a count of the number of times that + X is not NULL in a group. The count(*) function (with no arguments) returns + the total number of rows in the group. + examples: [] + - name: CUME_DIST + category_id: window_functions + category_label: Window Functions + signature: + display: CUME_DIST + args: [] + tags: [] + aliases: [] + summary: The cumulative distribution. + description: The cumulative distribution. Calculated as row-number/partition-rows, + where row-number is the value returned by row_number() for the last peer in + the group and partition-rows the number of rows in the partition. + examples: [] + - name: DATE + category_id: date_and_time_functions + category_label: Date And Time Functions + signature: + display: DATE(time-value, modifier, modifier, ...) + args: + - name: time-value + optional: false + type: any + - name: modifier + optional: false + type: any + - name: modifier + optional: false + type: any + - name: '...' + optional: false + type: any + tags: [] + aliases: [] + summary: All five date and time functions take a time value as an argument. + description: All five date and time functions take a time value as an argument. + The time value is followed by zero or more modifiers. The strftime() function + also takes a format string as its first argument. + examples: [] + - name: DATETIME + category_id: date_and_time_functions + category_label: Date And Time Functions + signature: + display: DATETIME(time-value, modifier, modifier, ...) + args: + - name: time-value + optional: false + type: any + - name: modifier + optional: false + type: any + - name: modifier + optional: false + type: any + - name: '...' + optional: false + type: any + tags: [] + aliases: [] + summary: All five date and time functions take a time value as an argument. + description: All five date and time functions take a time value as an argument. + The time value is followed by zero or more modifiers. The strftime() function + also takes a format string as its first argument. + examples: [] + - name: DENSE_RANK + category_id: window_functions + category_label: Window Functions + signature: + display: DENSE_RANK + args: [] + tags: [] + aliases: [] + summary: The number of the current row's peer group within its partition - the + rank + description: The number of the current row's peer group within its partition - + the rank of the current row without gaps. Partitions are numbered starting from + 1 in the order defined by the ORDER BY clause in the window definition. If there + is no ORDER BY clause, then all rows are considered peers and this function + always returns 1. + examples: [] + - name: FIRST_VALUE + category_id: window_functions + category_label: Window Functions + signature: + display: FIRST_VALUE(expr) + args: + - name: expr + optional: false + type: any + tags: [] + aliases: [] + summary: This built-in window function calculates the window frame for each row + in + description: This built-in window function calculates the window frame for each + row in the same way as an aggregate window function. It returns the value of + expr evaluated against the first row in the window frame for each row. + examples: [] + - name: GLOB + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: GLOB(X,Y) + args: + - name: X + optional: false + type: any + - name: Y + optional: false + type: any + tags: [] + aliases: [] + summary: The glob(X,Y) function is equivalent to the expression "Y GLOB X". + description: 'The glob(X,Y) function is equivalent to the expression "Y GLOB X". + Note that the X and Y arguments are reversed in the glob() function relative + to the infix GLOB operator. Y is the string and X is the pattern. So, for example, + the following expressions are equivalent: name GLOB ''*helium*'' glob(''*helium*'',name) + If the sqlite3_create_function() interface is used to override the glob(X,Y) + function with an alternative implementation then the GLOB operator will invoke + the alternative implementation.' + examples: [] + - name: GROUP_CONCAT1 + category_id: aggregate_functions + category_label: Aggregate Functions + signature: + display: GROUP_CONCAT1(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: The group_concat() function returns a string which is the concatenation + of + description: The group_concat() function returns a string which is the concatenation + of all non-NULL values of X. If parameter Y is present then it is used as the + separator between instances of X. A comma (",") is used as the separator if + Y is omitted. The order of the concatenated elements is arbitrary. + examples: [] + - name: GROUP_CONCAT2 + category_id: aggregate_functions + category_label: Aggregate Functions + signature: + display: GROUP_CONCAT2(X,Y) + args: + - name: X + optional: false + type: any + - name: Y + optional: false + type: any + tags: [] + aliases: [] + summary: The group_concat() function returns a string which is the concatenation + of + description: The group_concat() function returns a string which is the concatenation + of all non-NULL values of X. If parameter Y is present then it is used as the + separator between instances of X. A comma (",") is used as the separator if + Y is omitted. The order of the concatenated elements is arbitrary. + examples: [] + - name: HEX + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: HEX(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: The hex() function interprets its argument as a BLOB and returns a string + description: The hex() function interprets its argument as a BLOB and returns + a string which is the upper-case hexadecimal rendering of the content of that + blob. If the argument X in "hex(X)" is an integer or floating point number, + then "interprets its argument as a BLOB" means that the binary number is first + converted into a UTF8 text representation, then that text is interpreted as + a BLOB. Hence, "hex(12345678)" renders as "3132333435363738" not the binary + representation of the integer value "0000000000BC614E". + examples: [] + - name: IFNULL + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: IFNULL(X,Y) + args: + - name: X + optional: false + type: any + - name: Y + optional: false + type: any + tags: [] + aliases: [] + summary: The ifnull() function returns a copy of its first non-NULL argument, + or + description: The ifnull() function returns a copy of its first non-NULL argument, + or NULL if both arguments are NULL. Ifnull() must have exactly 2 arguments. + The ifnull() function is equivalent to coalesce() with two arguments. + examples: [] + - name: IIF + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: IIF(X,Y,Z) + args: + - name: X + optional: false + type: any + - name: Y + optional: false + type: any + - name: Z + optional: false + type: any + tags: [] + aliases: [] + summary: The iif(X,Y,Z) function returns the value Y if X is true, and Z otherwise. + description: The iif(X,Y,Z) function returns the value Y if X is true, and Z otherwise. + The iif(X,Y,Z) function is logically equivalent to and generates the same bytecode + as the CASE expression "CASE WHEN X THEN Y ELSE Z END". + examples: [] + - name: INSTR + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: INSTR(X,Y) + args: + - name: X + optional: false + type: any + - name: Y + optional: false + type: any + tags: [] + aliases: [] + summary: The instr(X,Y) function finds the first occurrence of string Y within + description: The instr(X,Y) function finds the first occurrence of string Y within + string X and returns the number of prior characters plus 1, or 0 if Y is nowhere + found within X. Or, if X and Y are both BLOBs, then instr(X,Y) returns one more + than the number bytes prior to the first occurrence of Y, or 0 if Y does not + occur anywhere within X. If both arguments X and Y to instr(X,Y) are non-NULL + and are not BLOBs then both are interpreted as strings. If either X or Y are + NULL in instr(X,Y) then the result is NULL. + examples: [] + - name: JULIANDAY + category_id: date_and_time_functions + category_label: Date And Time Functions + signature: + display: JULIANDAY(time-value, modifier, modifier, ...) + args: + - name: time-value + optional: false + type: any + - name: modifier + optional: false + type: any + - name: modifier + optional: false + type: any + - name: '...' + optional: false + type: any + tags: [] + aliases: [] + summary: All five date and time functions take a time value as an argument. + description: All five date and time functions take a time value as an argument. + The time value is followed by zero or more modifiers. The strftime() function + also takes a format string as its first argument. + examples: [] + - name: LAG + category_id: window_functions + category_label: Window Functions + signature: + display: LAG(expr) + args: + - name: expr + optional: false + type: any + tags: [] + aliases: [] + summary: The first form of the lag() function returns the result of evaluating + description: "The first form of the lag() function returns the result of evaluating\n\ + expression expr against the previous row in the partition. Or, if there is\n\ + no previous row (because the current row is the first), NULL. \n If the\noffset\ + \ argument is provided, then it must be a non-negative integer. In\nthis case\ + \ the value returned is the result of evaluating expr against the\nrow offset\ + \ rows before the current row within the partition. If offset is\n0, then expr\ + \ is evaluated against the current row. If there is no row\noffset rows before\ + \ the current row, NULL is returned. \n If default is also\nprovided, then it\ + \ is returned instead of NULL if the row identified by\noffset does not exist." + examples: [] + - name: LAST_INSERT_ROWID + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: LAST_INSERT_ROWID + args: [] + tags: [] + aliases: [] + summary: The last_insert_rowid() function returns the ROWID of the last row insert + description: The last_insert_rowid() function returns the ROWID of the last row + insert from the database connection which invoked the function. The last_insert_rowid() + SQL function is a wrapper around the sqlite3_last_insert_rowid() C/C++ interface + function. + examples: [] + - name: LAST_VALUE + category_id: window_functions + category_label: Window Functions + signature: + display: LAST_VALUE(expr) + args: + - name: expr + optional: false + type: any + tags: [] + aliases: [] + summary: This built-in window function calculates the window frame for each row + in + description: This built-in window function calculates the window frame for each + row in the same way as an aggregate window function. It returns the value of + expr evaluated against the last row in the window frame for each row. + examples: [] + - name: LEAD + category_id: window_functions + category_label: Window Functions + signature: + display: LEAD(expr) + args: + - name: expr + optional: false + type: any + tags: [] + aliases: [] + summary: The first form of the lead() function returns the result of evaluating + description: "The first form of the lead() function returns the result of evaluating\n\ + expression expr against the next row in the partition. Or, if there is no\n\ + next row (because the current row is the last), NULL. \n If the offset\nargument\ + \ is provided, then it must be a non-negative integer. In this case\nthe value\ + \ returned is the result of evaluating expr against the row offset\nrows after\ + \ the current row within the partition. If offset is 0, then expr\nis evaluated\ + \ against the current row. If there is no row offset rows after\nthe current\ + \ row, NULL is returned. \n If default is also provided, then it\nis returned\ + \ instead of NULL if the row identified by offset does not exist." + examples: [] + - name: LENGTH + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: LENGTH(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: For a string value X, the length(X) function returns the number of + description: For a string value X, the length(X) function returns the number of + characters (not bytes) in X prior to the first NUL character. Since SQLite strings + do not normally contain NUL characters, the length(X) function will usually + return the total number of characters in the string X. For a blob value X, length(X) + returns the number of bytes in the blob. If X is NULL then length(X) is NULL. + If X is numeric then length(X) returns the length of a string representation + of X. + examples: [] + - name: LIKE1 + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: LIKE1(X,Y) + args: + - name: X + optional: false + type: any + - name: Y + optional: false + type: any + tags: [] + aliases: [] + summary: The like() function is used to implement the "Y LIKE X [ESCAPE Z]" + description: 'The like() function is used to implement the "Y LIKE X [ESCAPE + Z]" expression. If the optional ESCAPE clause is present, then the like() + function is invoked with three arguments. Otherwise, it is invoked with two + arguments only. Note that the X and Y parameters are reversed in the like() + function relative to the infix LIKE operator. X is the pattern and Y is the + string to match against that pattern. Hence, the following expressions are equivalent: + name LIKE ''%neon%'' like(''%neon%'',name) The sqlite3_create_function() interface + can be used to override the like() function and thereby change the operation + of the LIKE operator. When overriding the like() function, it may be important + to override both the two and three argument versions of the like() function. + Otherwise, different code may be called to implement the LIKE operator depending + on whether or not an ESCAPE clause was specified.' + examples: [] + - name: LIKE2 + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: LIKE2(X,Y,Z) + args: + - name: X + optional: false + type: any + - name: Y + optional: false + type: any + - name: Z + optional: false + type: any + tags: [] + aliases: [] + summary: The like() function is used to implement the "Y LIKE X [ESCAPE Z]" + description: 'The like() function is used to implement the "Y LIKE X [ESCAPE + Z]" expression. If the optional ESCAPE clause is present, then the like() + function is invoked with three arguments. Otherwise, it is invoked with two + arguments only. Note that the X and Y parameters are reversed in the like() + function relative to the infix LIKE operator. X is the pattern and Y is the + string to match against that pattern. Hence, the following expressions are equivalent: + name LIKE ''%neon%'' like(''%neon%'',name) The sqlite3_create_function() interface + can be used to override the like() function and thereby change the operation + of the LIKE operator. When overriding the like() function, it may be important + to override both the two and three argument versions of the like() function. + Otherwise, different code may be called to implement the LIKE operator depending + on whether or not an ESCAPE clause was specified.' + examples: [] + - name: LIKELIHOOD + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: LIKELIHOOD(X,Y) + args: + - name: X + optional: false + type: any + - name: Y + optional: false + type: any + tags: [] + aliases: [] + summary: The likelihood(X,Y) function returns argument X unchanged. + description: The likelihood(X,Y) function returns argument X unchanged. The value + Y in likelihood(X,Y) must be a floating point constant between 0.0 and 1.0, + inclusive. The likelihood(X) function is a no-op that the code generator optimizes + away so that it consumes no CPU cycles during run-time (that is, during calls + to sqlite3_step()). The purpose of the likelihood(X,Y) function is to provide + a hint to the query planner that the argument X is a boolean that is true with + a probability of approximately Y. The unlikely(X) function is short-hand for + likelihood(X,0.0625). The likely(X) function is short-hand for likelihood(X,0.9375). + examples: [] + - name: LIKELY + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: LIKELY(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: The likely(X) function returns the argument X unchanged. + description: 'The likely(X) function returns the argument X unchanged. The likely(X) + function is a no-op that the code generator optimizes away so that it consumes + no CPU cycles at run-time (that is, during calls to sqlite3_step()). The purpose + of the likely(X) function is to provide a hint to the query planner that the + argument X is a boolean value that is usually true. The likely(X) function is + equivalent to likelihood(X,0.9375). See also: unlikely(X).' + examples: [] + - name: LOAD_EXTENSION1 + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: LOAD_EXTENSION1(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: The load_extension(X,Y) function loads SQLite extensions out of the shared + description: The load_extension(X,Y) function loads SQLite extensions out of the + shared library file named X using the entry point Y. The result of load_extension() + is always a NULL. If Y is omitted then the default entry point name is used. + The load_extension() function raises an exception if the extension fails to + load or initialize correctly. The load_extension() function will fail if the + extension attempts to modify or delete an SQL function or collating sequence. + The extension can add new functions or collating sequences, but cannot modify + or delete existing functions or collating sequences because those functions + and/or collating sequences might be used elsewhere in the currently running + SQL statement. To load an extension that changes or deletes functions or collating + sequences, use the sqlite3_load_extension() C-language API. For security reasons, + extension loaded is turned off by default and must be enabled by a prior call + to sqlite3_enable_load_extension(). + examples: [] + - name: LOAD_EXTENSION2 + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: LOAD_EXTENSION2(X,Y) + args: + - name: X + optional: false + type: any + - name: Y + optional: false + type: any + tags: [] + aliases: [] + summary: The load_extension(X,Y) function loads SQLite extensions out of the shared + description: The load_extension(X,Y) function loads SQLite extensions out of the + shared library file named X using the entry point Y. The result of load_extension() + is always a NULL. If Y is omitted then the default entry point name is used. + The load_extension() function raises an exception if the extension fails to + load or initialize correctly. The load_extension() function will fail if the + extension attempts to modify or delete an SQL function or collating sequence. + The extension can add new functions or collating sequences, but cannot modify + or delete existing functions or collating sequences because those functions + and/or collating sequences might be used elsewhere in the currently running + SQL statement. To load an extension that changes or deletes functions or collating + sequences, use the sqlite3_load_extension() C-language API. For security reasons, + extension loaded is turned off by default and must be enabled by a prior call + to sqlite3_enable_load_extension(). + examples: [] + - name: LOWER + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: LOWER(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: The lower(X) function returns a copy of string X with all ASCII characters + description: The lower(X) function returns a copy of string X with all ASCII characters + converted to lower case. The default built-in lower() function works for ASCII + characters only. To do case conversions on non-ASCII characters, load the ICU + extension. + examples: [] + - name: LTRIM1 + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: LTRIM1(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: The ltrim(X,Y) function returns a string formed by removing any and all + description: The ltrim(X,Y) function returns a string formed by removing any and + all characters that appear in Y from the left side of X. If the Y argument is + omitted, ltrim(X) removes spaces from the left side of X. + examples: [] + - name: LTRIM2 + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: LTRIM2(X,Y) + args: + - name: X + optional: false + type: any + - name: Y + optional: false + type: any + tags: [] + aliases: [] + summary: The ltrim(X,Y) function returns a string formed by removing any and all + description: The ltrim(X,Y) function returns a string formed by removing any and + all characters that appear in Y from the left side of X. If the Y argument is + omitted, ltrim(X) removes spaces from the left side of X. + examples: [] + - name: MAX1 + category_id: aggregate_functions + category_label: Aggregate Functions + signature: + display: MAX1(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: The max() aggregate function returns the maximum value of all values + in the + description: The max() aggregate function returns the maximum value of all values + in the group. The maximum value is the value that would be returned last in + an ORDER BY on the same column. Aggregate max() returns NULL if and only if + there are no non-NULL values in the group. + examples: [] + - name: MAX2 + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: MAX2(X,Y,...) + args: + - name: X + optional: false + type: any + - name: Y + optional: false + type: any + - name: '...' + optional: false + type: any + tags: [] + aliases: [] + summary: The multi-argument max() function returns the argument with the maximum + description: The multi-argument max() function returns the argument with the maximum + value, or return NULL if any argument is NULL. The multi-argument max() function + searches its arguments from left to right for an argument that defines a collating + function and uses that collating function for all string comparisons. If none + of the arguments to max() define a collating function, then the BINARY collating + function is used. Note that max() is a simple function when it has 2 or more + arguments but operates as an aggregate function if given only a single argument. + examples: [] + - name: MIN1 + category_id: aggregate_functions + category_label: Aggregate Functions + signature: + display: MIN1(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: The min() aggregate function returns the minimum non-NULL value of all + description: The min() aggregate function returns the minimum non-NULL value of + all values in the group. The minimum value is the first non-NULL value that + would appear in an ORDER BY of the column. Aggregate min() returns NULL if and + only if there are no non-NULL values in the group. + examples: [] + - name: MIN2 + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: MIN2(X,Y,...) + args: + - name: X + optional: false + type: any + - name: Y + optional: false + type: any + - name: '...' + optional: false + type: any + tags: [] + aliases: [] + summary: The multi-argument min() function returns the argument with the minimum + description: The multi-argument min() function returns the argument with the minimum + value. The multi-argument min() function searches its arguments from left to + right for an argument that defines a collating function and uses that collating + function for all string comparisons. If none of the arguments to min() define + a collating function, then the BINARY collating function is used. Note that + min() is a simple function when it has 2 or more arguments but operates as an + aggregate function if given only a single argument. + examples: [] + - name: NTH_VALUE + category_id: window_functions + category_label: Window Functions + signature: + display: NTH_VALUE(expr, N) + args: + - name: expr + optional: false + type: any + - name: N + optional: false + type: any + tags: [] + aliases: [] + summary: This built-in window function calculates the window frame for each row + in + description: This built-in window function calculates the window frame for each + row in the same way as an aggregate window function. It returns the value of + expr evaluated against the row N of the window frame. Rows are numbered within + the window frame starting from 1 in the order defined by the ORDER BY clause + if one is present, or in arbitrary order otherwise. If there is no Nth row in + the partition, then NULL is returned. + examples: [] + - name: NTILE + category_id: window_functions + category_label: Window Functions + signature: + display: NTILE(N) + args: + - name: N + optional: false + type: any + tags: [] + aliases: [] + summary: Argument N is handled as an integer. + description: Argument N is handled as an integer. This function divides the partition + into N groups as evenly as possible and assigns an integer between 1 and N to + each group, in the order defined by the ORDER BY clause, or in arbitrary order + otherwise. If necessary, larger groups occur first. This function returns the + integer value assigned to the group that the current row is a part of. + examples: [] + - name: NULLIF + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: NULLIF(X,Y) + args: + - name: X + optional: false + type: any + - name: Y + optional: false + type: any + tags: [] + aliases: [] + summary: The nullif(X,Y) function returns its first argument if the arguments + are + description: The nullif(X,Y) function returns its first argument if the arguments + are different and NULL if the arguments are the same. The nullif(X,Y) function + searches its arguments from left to right for an argument that defines a collating + function and uses that collating function for all string comparisons. If neither + argument to nullif() defines a collating function then the BINARY is used. + examples: [] + - name: PERCENT_RANK + category_id: window_functions + category_label: Window Functions + signature: + display: PERCENT_RANK + args: [] + tags: [] + aliases: [] + summary: Despite the name, this function always returns a value between 0.0 and + 1.0 + description: Despite the name, this function always returns a value between 0.0 + and 1.0 equal to (rank - 1)/(partition-rows - 1), where rank is the value returned + by built-in window function rank() and partition-rows is the total number of + rows in the partition. If the partition contains only one row, this function + returns 0.0. + examples: [] + - name: PRINTF + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: PRINTF(FORMAT,...) + args: + - name: FORMAT + optional: false + type: any + - name: '...' + optional: false + type: any + tags: [] + aliases: [] + summary: The printf(FORMAT,...) SQL function works like the sqlite3_mprintf() + description: The printf(FORMAT,...) SQL function works like the sqlite3_mprintf() + C-language function and the printf() function from the standard C library. The + first argument is a format string that specifies how to construct the output + string using values taken from subsequent arguments. If the FORMAT argument + is missing or NULL then the result is NULL. The %n format is silently ignored + and does not consume an argument. The %p format is an alias for %X. The %z format + is interchangeable with %s. If there are too few arguments in the argument list, + missing arguments are assumed to have a NULL value, which is translated into + 0 or 0.0 for numeric formats or an empty string for %s. See the built-in printf() + documentation for additional information. + examples: [] + - name: QUOTE + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: QUOTE(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: The quote(X) function returns the text of an SQL literal which is the + value + description: The quote(X) function returns the text of an SQL literal which is + the value of its argument suitable for inclusion into an SQL statement. Strings + are surrounded by single-quotes with escapes on interior quotes as needed. BLOBs + are encoded as hexadecimal literals. Strings with embedded NUL characters cannot + be represented as string literals in SQL and hence the returned string literal + is truncated prior to the first NUL. + examples: [] + - name: RANDOM + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: RANDOM + args: [] + tags: [] + aliases: [] + summary: The random() function returns a pseudo-random integer between + description: The random() function returns a pseudo-random integer between -9223372036854775808 + and +9223372036854775807. + examples: [] + - name: RANDOMBLOB + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: RANDOMBLOB(N) + args: + - name: N + optional: false + type: any + tags: [] + aliases: [] + summary: The randomblob(N) function return an N-byte blob containing pseudo-random + description: 'The randomblob(N) function return an N-byte blob containing pseudo-random + bytes. If N is less than 1 then a 1-byte random blob is returned. Hint: applications + can generate globally unique identifiers using this function together with hex() + and/or lower() like this: hex(randomblob(16)) lower(hex(randomblob(16)))' + examples: [] + - name: RANK + category_id: window_functions + category_label: Window Functions + signature: + display: RANK + args: [] + tags: [] + aliases: [] + summary: The row_number() of the first peer in each group - the rank of the current + description: The row_number() of the first peer in each group - the rank of the + current row with gaps. If there is no ORDER BY clause, then all rows are considered + peers and this function always returns 1. + examples: [] + - name: REPLACE + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: REPLACE(X,Y,Z) + args: + - name: X + optional: false + type: any + - name: Y + optional: false + type: any + - name: Z + optional: false + type: any + tags: [] + aliases: [] + summary: The replace(X,Y,Z) function returns a string formed by substituting string + description: The replace(X,Y,Z) function returns a string formed by substituting + string Z for every occurrence of string Y in string X. The BINARY collating + sequence is used for comparisons. If Y is an empty string then return X unchanged. + If Z is not initially a string, it is cast to a UTF-8 string prior to processing. + examples: [] + - name: ROUND1 + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: ROUND1(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: The round(X,Y) function returns a floating-point value X rounded to Y + description: The round(X,Y) function returns a floating-point value X rounded + to Y digits to the right of the decimal point. If the Y argument is omitted, + it is assumed to be 0. + examples: [] + - name: ROUND2 + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: ROUND2(X,Y) + args: + - name: X + optional: false + type: any + - name: Y + optional: false + type: any + tags: [] + aliases: [] + summary: The round(X,Y) function returns a floating-point value X rounded to Y + description: The round(X,Y) function returns a floating-point value X rounded + to Y digits to the right of the decimal point. If the Y argument is omitted, + it is assumed to be 0. + examples: [] + - name: ROW_NUMBER + category_id: window_functions + category_label: Window Functions + signature: + display: ROW_NUMBER + args: [] + tags: [] + aliases: [] + summary: The number of the row within the current partition. + description: The number of the row within the current partition. Rows are numbered + starting from 1 in the order defined by the ORDER BY clause in the window definition, + or in arbitrary order otherwise. + examples: [] + - name: RTRIM1 + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: RTRIM1(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: The rtrim(X,Y) function returns a string formed by removing any and all + description: The rtrim(X,Y) function returns a string formed by removing any and + all characters that appear in Y from the right side of X. If the Y argument + is omitted, rtrim(X) removes spaces from the right side of X. + examples: [] + - name: RTRIM2 + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: RTRIM2(X,Y) + args: + - name: X + optional: false + type: any + - name: Y + optional: false + type: any + tags: [] + aliases: [] + summary: The rtrim(X,Y) function returns a string formed by removing any and all + description: The rtrim(X,Y) function returns a string formed by removing any and + all characters that appear in Y from the right side of X. If the Y argument + is omitted, rtrim(X) removes spaces from the right side of X. + examples: [] + - name: SIGN + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: SIGN(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: The sign(X) function returns -1, 0, or +1 if the argument X is a numeric + description: The sign(X) function returns -1, 0, or +1 if the argument X is a + numeric value that is negative, zero, or positive, respectively. If the argument + to sign(X) is NULL or is a string or blob that cannot be losslessly converted + into a number, then sign(X) returns NULL. + examples: [] + - name: SOUNDEX + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: SOUNDEX(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: The soundex(X) function returns a string that is the soundex encoding + of + description: The soundex(X) function returns a string that is the soundex encoding + of the string X. The string "?000" is returned if the argument is NULL or contains + no ASCII alphabetic characters. This function is omitted from SQLite by default. + It is only available if the SQLITE_SOUNDEX compile-time option is used when + SQLite is built. + examples: [] + - name: SQLITE_COMPILEOPTION_GET + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: SQLITE_COMPILEOPTION_GET(N) + args: + - name: N + optional: false + type: any + tags: [] + aliases: [] + summary: The sqlite_compileoption_get() SQL function is a wrapper around the + description: The sqlite_compileoption_get() SQL function is a wrapper around the + sqlite3_compileoption_get() C/C++ function. This routine returns the N-th compile-time + option used to build SQLite or NULL if N is out of range. See also the compile_options + pragma. + examples: [] + - name: SQLITE_COMPILEOPTION_USED + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: SQLITE_COMPILEOPTION_USED(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: The sqlite_compileoption_used() SQL function is a wrapper around the + description: The sqlite_compileoption_used() SQL function is a wrapper around + the sqlite3_compileoption_used() C/C++ function. When the argument X to sqlite_compileoption_used(X) + is a string which is the name of a compile-time option, this routine returns + true (1) or false (0) depending on whether or not that option was used during + the build. + examples: [] + - name: SQLITE_OFFSET + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: SQLITE_OFFSET(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: The sqlite_offset(X) function returns the byte offset in the database + file + description: The sqlite_offset(X) function returns the byte offset in the database + file for the beginning of the record from which value would be read. If X is + not a column in an ordinary table, then sqlite_offset(X) returns NULL. The value + returned by sqlite_offset(X) might reference either the original table or an + index, depending on the query. If the value X would normally be extracted from + an index, the sqlite_offset(X) returns the offset to the corresponding index + record. If the value X would be extracted from the original table, then sqlite_offset(X) + returns the offset to the table record. The sqlite_offset(X) SQL function is + only available if SQLite is built using the -DSQLITE_ENABLE_OFFSET_SQL_FUNC + compile-time option. + examples: [] + - name: SQLITE_SOURCE_ID + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: SQLITE_SOURCE_ID + args: [] + tags: [] + aliases: [] + summary: The sqlite_source_id() function returns a string that identifies the + description: The sqlite_source_id() function returns a string that identifies + the specific version of the source code that was used to build the SQLite library. + The string returned by sqlite_source_id() is the date and time that the source + code was checked in followed by the SHA3-256 hash for that check-in. This function + is an SQL wrapper around the sqlite3_sourceid() C interface. + examples: [] + - name: SQLITE_VERSION + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: SQLITE_VERSION + args: [] + tags: [] + aliases: [] + summary: The sqlite_version() function returns the version string for the SQLite + description: The sqlite_version() function returns the version string for the + SQLite library that is running. This function is an SQL wrapper around the sqlite3_libversion() + C-interface. + examples: [] + - name: STRFTIME + category_id: date_and_time_functions + category_label: Date And Time Functions + signature: + display: STRFTIME(format, time-value, modifier, modifier, ...) + args: + - name: format + optional: false + type: any + - name: time-value + optional: false + type: any + - name: modifier + optional: false + type: any + - name: modifier + optional: false + type: any + - name: '...' + optional: false + type: any + tags: [] + aliases: [] + summary: All five date and time functions take a time value as an argument. + description: All five date and time functions take a time value as an argument. + The time value is followed by zero or more modifiers. The strftime() function + also takes a format string as its first argument. + examples: [] + - name: SUBSTR1 + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: SUBSTR1(X,Y,Z) + args: + - name: X + optional: false + type: any + - name: Y + optional: false + type: any + - name: Z + optional: false + type: any + tags: [] + aliases: [] + summary: The substr(X,Y,Z) function returns a substring of input string X that + description: The substr(X,Y,Z) function returns a substring of input string X + that begins with the Y-th character and which is Z characters long. If Z is + omitted then substr(X,Y) returns all characters through the end of the string + X beginning with the Y-th. The left-most character of X is number 1. If Y is + negative then the first character of the substring is found by counting from + the right rather than the left. If Z is negative then the abs(Z) characters + preceding the Y-th character are returned. If X is a string then characters + indices refer to actual UTF-8 characters. If X is a BLOB then the indices refer + to bytes. "substring()" is an alias for "substr()" beginning with SQLite version + 3.34. + examples: [] + - name: SUBSTR2 + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: SUBSTR2(X,Y) + args: + - name: X + optional: false + type: any + - name: Y + optional: false + type: any + tags: [] + aliases: [] + summary: The substr(X,Y,Z) function returns a substring of input string X that + description: The substr(X,Y,Z) function returns a substring of input string X + that begins with the Y-th character and which is Z characters long. If Z is + omitted then substr(X,Y) returns all characters through the end of the string + X beginning with the Y-th. The left-most character of X is number 1. If Y is + negative then the first character of the substring is found by counting from + the right rather than the left. If Z is negative then the abs(Z) characters + preceding the Y-th character are returned. If X is a string then characters + indices refer to actual UTF-8 characters. If X is a BLOB then the indices refer + to bytes. "substring()" is an alias for "substr()" beginning with SQLite version + 3.34. + examples: [] + - name: SUBSTRING1 + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: SUBSTRING1(X,Y,Z) + args: + - name: X + optional: false + type: any + - name: Y + optional: false + type: any + - name: Z + optional: false + type: any + tags: [] + aliases: [] + summary: The substr(X,Y,Z) function returns a substring of input string X that + description: The substr(X,Y,Z) function returns a substring of input string X + that begins with the Y-th character and which is Z characters long. If Z is + omitted then substr(X,Y) returns all characters through the end of the string + X beginning with the Y-th. The left-most character of X is number 1. If Y is + negative then the first character of the substring is found by counting from + the right rather than the left. If Z is negative then the abs(Z) characters + preceding the Y-th character are returned. If X is a string then characters + indices refer to actual UTF-8 characters. If X is a BLOB then the indices refer + to bytes. "substring()" is an alias for "substr()" beginning with SQLite version + 3.34. + examples: [] + - name: SUBSTRING2 + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: SUBSTRING2(X,Y) + args: + - name: X + optional: false + type: any + - name: Y + optional: false + type: any + tags: [] + aliases: [] + summary: The substr(X,Y,Z) function returns a substring of input string X that + description: The substr(X,Y,Z) function returns a substring of input string X + that begins with the Y-th character and which is Z characters long. If Z is + omitted then substr(X,Y) returns all characters through the end of the string + X beginning with the Y-th. The left-most character of X is number 1. If Y is + negative then the first character of the substring is found by counting from + the right rather than the left. If Z is negative then the abs(Z) characters + preceding the Y-th character are returned. If X is a string then characters + indices refer to actual UTF-8 characters. If X is a BLOB then the indices refer + to bytes. "substring()" is an alias for "substr()" beginning with SQLite version + 3.34. + examples: [] + - name: SUM + category_id: aggregate_functions + category_label: Aggregate Functions + signature: + display: SUM(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: The sum() and total() aggregate functions return sum of all non-NULL + values + description: The sum() and total() aggregate functions return sum of all non-NULL + values in the group. If there are no non-NULL input rows then sum() returns + NULL but total() returns 0.0. NULL is not normally a helpful result for the + sum of no rows but the SQL standard requires it and most other SQL database + engines implement sum() that way so SQLite does it in the same way in order + to be compatible. The non-standard total() function is provided as a convenient + way to work around this design problem in the SQL language. The result of total() + is always a floating point value. The result of sum() is an integer value if + all non-NULL inputs are integers. If any input to sum() is neither an integer + or a NULL then sum() returns a floating point value which might be an approximation + to the true sum. Sum() will throw an "integer overflow" exception if all inputs + are integers or NULL and an integer overflow occurs at any point during the + computation. Total() never throws an integer overflow. + examples: [] + - name: TIME + category_id: date_and_time_functions + category_label: Date And Time Functions + signature: + display: TIME(time-value, modifier, modifier, ...) + args: + - name: time-value + optional: false + type: any + - name: modifier + optional: false + type: any + - name: modifier + optional: false + type: any + - name: '...' + optional: false + type: any + tags: [] + aliases: [] + summary: All five date and time functions take a time value as an argument. + description: All five date and time functions take a time value as an argument. + The time value is followed by zero or more modifiers. The strftime() function + also takes a format string as its first argument. + examples: [] + - name: TOTAL + category_id: aggregate_functions + category_label: Aggregate Functions + signature: + display: TOTAL(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: The sum() and total() aggregate functions return sum of all non-NULL + values + description: The sum() and total() aggregate functions return sum of all non-NULL + values in the group. If there are no non-NULL input rows then sum() returns + NULL but total() returns 0.0. NULL is not normally a helpful result for the + sum of no rows but the SQL standard requires it and most other SQL database + engines implement sum() that way so SQLite does it in the same way in order + to be compatible. The non-standard total() function is provided as a convenient + way to work around this design problem in the SQL language. The result of total() + is always a floating point value. The result of sum() is an integer value if + all non-NULL inputs are integers. If any input to sum() is neither an integer + or a NULL then sum() returns a floating point value which might be an approximation + to the true sum. Sum() will throw an "integer overflow" exception if all inputs + are integers or NULL and an integer overflow occurs at any point during the + computation. Total() never throws an integer overflow. + examples: [] + - name: TOTAL_CHANGES + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: TOTAL_CHANGES + args: [] + tags: [] + aliases: [] + summary: The total_changes() function returns the number of row changes caused + by + description: The total_changes() function returns the number of row changes caused + by INSERT, UPDATE or DELETE statements since the current database connection + was opened. This function is a wrapper around the sqlite3_total_changes() C/C++ + interface. + examples: [] + - name: TRIM1 + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: TRIM1(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: The trim(X,Y) function returns a string formed by removing any and all + description: The trim(X,Y) function returns a string formed by removing any and + all characters that appear in Y from both ends of X. If the Y argument is omitted, + trim(X) removes spaces from both ends of X. + examples: [] + - name: TRIM2 + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: TRIM2(X,Y) + args: + - name: X + optional: false + type: any + - name: Y + optional: false + type: any + tags: [] + aliases: [] + summary: The trim(X,Y) function returns a string formed by removing any and all + description: The trim(X,Y) function returns a string formed by removing any and + all characters that appear in Y from both ends of X. If the Y argument is omitted, + trim(X) removes spaces from both ends of X. + examples: [] + - name: TYPEOF + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: TYPEOF(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: The typeof(X) function returns a string that indicates the datatype of + the + description: 'The typeof(X) function returns a string that indicates the datatype + of the expression X: "null", "integer", "real", "text", or "blob".' + examples: [] + - name: UNICODE + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: UNICODE(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: The unicode(X) function returns the numeric unicode code point + description: The unicode(X) function returns the numeric unicode code point corresponding + to the first character of the string X. If the argument to unicode(X) is not + a string then the result is undefined. + examples: [] + - name: UNLIKELY + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: UNLIKELY(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: The unlikely(X) function returns the argument X unchanged. + description: The unlikely(X) function returns the argument X unchanged. The unlikely(X) + function is a no-op that the code generator optimizes away so that it consumes + no CPU cycles at run-time (that is, during calls to sqlite3_step()). The purpose + of the unlikely(X) function is to provide a hint to the query planner that the + argument X is a boolean value that is usually not true. The unlikely(X) function + is equivalent to likelihood(X, 0.0625). + examples: [] + - name: UPPER + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: UPPER(X) + args: + - name: X + optional: false + type: any + tags: [] + aliases: [] + summary: The upper(X) function returns a copy of input string X in which all + description: The upper(X) function returns a copy of input string X in which all + lower-case ASCII characters are converted to their upper-case equivalent. + examples: [] + - name: ZEROBLOB + category_id: scalar_sql_functions + category_label: Scalar SQL Functions + signature: + display: ZEROBLOB(N) + args: + - name: N + optional: false + type: any + tags: [] + aliases: [] + summary: The zeroblob(N) function returns a BLOB consisting of N bytes of 0x00. + description: The zeroblob(N) function returns a BLOB consisting of N bytes of + 0x00. SQLite manages these zeroblobs very efficiently. Zeroblobs can be used + to reserve space for a BLOB that is later written using incremental BLOB I/O. + This SQL function is implemented using the sqlite3_result_zeroblob() routine + from the C/C++ interface. + examples: [] +versions: + '3': {} diff --git a/structures/ssh_tunnel.py b/structures/ssh_tunnel.py index ff07940..24b960b 100644 --- a/structures/ssh_tunnel.py +++ b/structures/ssh_tunnel.py @@ -8,26 +8,33 @@ from typing import Optional -import gettext as _ +from gettext import gettext as _ from helpers.exceptions import SSHTunnelError from helpers.logger import logger class SSHTunnel: - def __init__(self, ssh_hostname: str, ssh_port: int = 22, /, - ssh_username: Optional[str] = None, ssh_password: Optional[str] = None, - remote_port: int = 3306, local_bind_address: tuple[str, int] = ('localhost', 0), - ssh_executable: str = 'ssh', - identity_file: Optional[str] = None, - extra_args: Optional[list[str]] = None): - + def __init__( + self, + ssh_hostname: str, + ssh_port: int = 22, + /, + ssh_username: Optional[str] = None, + ssh_password: Optional[str] = None, + remote_host: str = "127.0.0.1", + remote_port: int = 3306, + local_bind_address: tuple[str, int] = ("localhost", 0), + ssh_executable: str = "ssh", + identity_file: Optional[str] = None, + extra_args: Optional[list[str]] = None, + ): self.ssh_hostname = ssh_hostname self.ssh_port = ssh_port self.ssh_username = ssh_username self.ssh_password = ssh_password - # self.remote_host, self.remote_port = remote_bind_address + self.remote_host = remote_host self.remote_port = remote_port self.local_address, self.local_port = local_bind_address @@ -44,10 +51,23 @@ def __exit__(self, exc_type, exc_val, exc_tb): self.stop() def start(self, timeout: float = 5.0): + if self.is_running(): + logger.debug("SSH tunnel already running on local port %s", self.local_port) + return + if self.local_port == 0: self.local_port = self._find_free_port(self.local_address) - self._check_ssh_available() + self._check_ssh_available(self.ssh_executable) + logger.debug( + "Preparing SSH tunnel: ssh_host=%s ssh_port=%s local=%s:%s remote=%s:%s", + self.ssh_hostname, + self.ssh_port, + self.local_address, + self.local_port, + self.remote_host, + self.remote_port, + ) destination = [] if self.ssh_username: @@ -57,18 +77,25 @@ def start(self, timeout: float = 5.0): cmd = [ self.ssh_executable, "-N", - "-o", "ExitOnForwardFailure=yes", - "-o", "ServerAliveInterval=30", - "-o", "ServerAliveCountMax=3", - "-L", f"{self.local_address}:{self.local_port}:127.0.0.1:{self.remote_port}", - "-p", str(self.ssh_port), + "-o", + "ExitOnForwardFailure=yes", + "-o", + "ServerAliveInterval=10", + "-o", + "ServerAliveCountMax=6", + "-o", + "TCPKeepAlive=yes", + "-L", + f"{self.local_address}:{self.local_port}:{self.remote_host}:{self.remote_port}", + "-p", + str(self.ssh_port), ] if self.identity_file: cmd += ["-i", self.identity_file] cmd += self.extra_args - cmd.append(''.join(destination)) + cmd.append("".join(destination)) base_cmd = cmd @@ -80,17 +107,39 @@ def start(self, timeout: float = 5.0): self._process = subprocess.Popen( cmd, - stdout=subprocess.DEVNULL, - stderr=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, text=True, start_new_session=True, ) - self._wait_until_ready(timeout) + try: + self._wait_until_ready(timeout) + except Exception as ex: + output = "" + if self._process and self._process.stdout is not None: + try: + output = self._process.stdout.read() or "" + except Exception: + output = "" + + self.stop() + logger.debug("SSH tunnel startup failed output: %s", output.strip()) + raise SSHTunnelError(f"SSH tunnel failed to start: {ex}") from ex + + logger.debug( + "SSH tunnel ready: pid=%s local=%s:%s remote=%s:%s", + self._process.pid if self._process else None, + self.local_address, + self.local_port, + self.remote_host, + self.remote_port, + ) atexit.register(self.stop) def stop(self): + logger.info("Stopping SSH tunnel...") if not self._process: return @@ -112,8 +161,8 @@ def is_running(self) -> bool: # ---------- internals ---------- @staticmethod - def _check_ssh_available(): - if not shutil.which("ssh"): + def _check_ssh_available(executable: str): + if not shutil.which(executable): raise SSHTunnelError(_("OpenSSH client not found.")) def _find_free_port(self, host: str) -> int: @@ -124,8 +173,6 @@ def _find_free_port(self, host: str) -> int: def _wait_until_ready(self, timeout: float): deadline = time.time() + timeout while time.time() <= deadline: - logger.debug(f"Checking port {self.local_address}:{self.local_port} is ready...") - # Check if port is open with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: sock.settimeout(0.5) diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..2560cfb --- /dev/null +++ b/tests/README.md @@ -0,0 +1,363 @@ +# PeterSQL Test Suite + +Comprehensive integration tests across all supported database engines. + +## 🧪 Test Coverage Matrix + +| Feature | Operation | MariaDB | MySQL | SQLite | PostgreSQL | +|---------|-----------|---------|-------|--------|------------| +| **Table** | Create | ✅ | ✅ | ✅ | ✅ | +| | Drop | ✅ | ✅ | ✅ | ✅ | +| | Truncate | ✅ | ✅ | ✅ | ✅ | +| | Rename | ✅ | ✅ | ✅ | ✅ | +| | Alter (engine/collation) | ❓ | ❓ | ❌ | ❌ | +| **Record** | Insert (Create) | ✅ | ✅ | ✅ | ✅ | +| | Select (Read) | ✅ | ✅ | ✅ | ✅ | +| | Update | ✅ | ✅ | ✅ | ✅ | +| | Delete | ✅ | ✅ | ✅ | ✅ | +| **Column** | Add (Create) | ✅ | ✅ | ✅ | ✅ | +| | Modify/Alter | ✅ | ✅ | ⏭️ | ✅ | +| | Drop | ✅ | ✅ | ⏭️ | ✅ | +| **Index** | Create | ✅ | ✅ | ✅ | ✅ | +| | Drop | ✅ | ✅ | ✅ | ✅ | +| | Modify (drop+create) | ✅ | ✅ | ✅ | ✅ | +| **ForeignKey** | Create | ✅ | ✅ | ⏭️ | ✅ | +| | Drop | ✅ | ✅ | ⏭️ | ✅ | +| | Modify (drop+create) | ✅ | ✅ | ⏭️ | ✅ | +| **Check** | Create | ✅ | ✅ | ⏭️ | ✅ | +| | Drop | ✅ | ✅ | ⏭️ | ✅ | +| | Alter (drop+create) | ✅ | ✅ | ⏭️ | ✅ | +| | Load from table | ✅ | ✅ | ✅ | ✅ | +| **Trigger** | Create | ✅ | ✅ | ✅ | ✅ | +| | Drop | ✅ | ✅ | ✅ | ✅ | +| | Modify (drop+create) | ✅ | ✅ | ✅ | ✅ | +| **View** | Create/Save | ✅ | ✅ | ✅ | ✅ | +| | Alter | ✅ | ✅ | ✅ | ✅ | +| | Drop | ✅ | ✅ | ✅ | ✅ | +| | Get Definers | ✅ | ✅ | ❌ | ❌ | +| **Function** | Create | ✅ | ✅ | ❌ | ✅ | +| | Drop | ✅ | ✅ | ❌ | ✅ | +| | Alter | ✅ | ✅ | ❌ | ✅ | +| **Procedure** | Create | ⚠️ | ⚠️ | ❌ | ✅ | +| | Drop | ⚠️ | ⚠️ | ❌ | ✅ | +| | Alter | ⚠️ | ⚠️ | ❌ | ✅ | +| **Event** | Create | ⚠️ | ⚠️ | ❌ | ❌ | +| | Drop | ⚠️ | ⚠️ | ❌ | ❌ | +| | Alter | ⚠️ | ⚠️ | ❌ | ❌ | +| **Infrastructure** | SSH Tunnel | ✅ | ✅ | ❌ | ❌ | + +**Legend:** +- ✅ **Tested and passing** - Operation is fully tested +- ❓ **Not tested yet** - Operation exists in API but lacks tests +- ⏭️ **Skipped** - Tests exist but skipped (engine bugs/limitations) +- ❌ **Not applicable** - Feature doesn't exist for this engine + +## 📊 Test Statistics + +- **Total tests:** 182 integration tests collected (266 with all engines) +- **Passing:** 182 tests (+57 PostgreSQL) ✅ **100% PASS RATE** +- **Skipped:** 0 tests ✅ **ALL TESTS ENABLED** + - SQLite: 6 tests (column/check modify/drop - incompatible API) + - MariaDB 5.5: 1 test (CHECK constraints not supported) +- **Versions tested:** + - MariaDB: `latest`, `11.8`, `10.11`, `5.5` (4 versions) + - MySQL: `latest`, `8.0` (2 versions) + - SQLite: in-memory + - PostgreSQL: `latest`, `16`, `15` (3 versions) + +## 🏗️ Test Architecture + +Tests follow a **granular base class architecture** for maximum reusability and zero code duplication. + +### Directory Structure + +``` +tests/ +├── engines/ +│ ├── base_table_tests.py # Reusable table tests +│ ├── base_record_tests.py # Reusable record tests +│ ├── base_column_tests.py # Reusable column tests +│ ├── base_index_tests.py # Reusable index tests +│ ├── base_foreignkey_tests.py # Reusable foreign key tests +│ ├── base_trigger_tests.py # Reusable trigger tests +│ ├── base_view_tests.py # Reusable view tests +│ ├── base_ssh_tests.py # Reusable SSH tunnel tests +│ └── {engine}/ +│ ├── conftest.py # Engine-specific fixtures +│ ├── test_integration_suite.py # All integration tests +│ └── test_ssh_tunnel.py # SSH tests (if supported) +``` + +### Design Principles + +1. **Base Classes** - Each database object has a dedicated base test class +2. **Engine Inheritance** - Engine tests inherit from base classes +3. **Fixture Injection** - Engine-specific behavior via pytest fixtures +4. **Zero Duplication** - Test logic written once, runs on all engines +5. **Granular Skipping** - Skip specific tests per engine when needed + +### Example: Column Tests + +**Base class** (`base_column_tests.py`): +```python +class BaseColumnTests: + def test_column_add(self, session, database, create_users_table, datatype_class): + # Generic test logic + + @pytest.mark.skip_sqlite + def test_column_modify(self, session, database, create_users_table, datatype_class): + # Test that SQLite skips due to API incompatibility +``` + +**Engine implementation** (`mariadb/test_integration_suite.py`): +```python +@pytest.mark.integration +class TestMariaDBColumn(BaseColumnTests): + pass # Inherits all tests, uses MariaDB fixtures +``` + +## 🚀 Running Tests + +### Run all integration tests +```bash +uv run pytest tests/engines/ -m integration +``` + +### Run specific engine +```bash +uv run pytest tests/engines/mariadb/ -m integration +``` + +### Run specific test class +```bash +uv run pytest tests/engines/mariadb/test_integration_suite.py::TestMariaDBColumn -m integration +``` + +### Run with coverage +```bash +uv run pytest tests/engines/ -m integration --cov=structures --cov-report=html +``` + +## 🐛 Known Issues + +### PostgreSQL Tests Skipped +All PostgreSQL integration tests are currently skipped due to builder bugs: +- `raw_create()` includes indexes in column list (SQL syntax error) +- Index creation uses incorrect template +- Needs separate fix before enabling tests + +### SQLite Column Operations +SQLite has incompatible API for column modify/drop: +- `modify()` - Returns `None`, uses drop+recreate pattern +- `drop()` - Requires `(table, column)` parameters instead of `()` +- Tests marked with `@pytest.mark.skip_sqlite` + +## 📝 Adding New Tests + +### 1. Create base test class +```python +# tests/engines/base_feature_tests.py +class BaseFeatureTests: + def test_feature_operation(self, session, database, ...): + # Generic test logic +``` + +### 2. Update engine test suites +```python +# tests/engines/{engine}/test_integration_suite.py +from tests.engines.base_feature_tests import BaseFeatureTests + +@pytest.mark.integration +class Test{Engine}Feature(BaseFeatureTests): + pass +``` + +### 3. Add engine-specific fixtures if needed +```python +# tests/engines/{engine}/conftest.py +@pytest.fixture +def feature_specific_fixture(): + return EngineSpecificValue +``` + +## 🎯 Coverage Goals + +### Completed ✅ +- **Record CRUD** - Full coverage (Create, Read, Update, Delete) +- **View CRUD** - Full coverage (Create, Alter, Drop) +- **Column CRUD** - Create, Modify, Drop (MariaDB/MySQL) +- **Index CRUD** - Create, Drop, Modify (drop+create pattern) +- **Foreign Key CRUD** - Create, Drop, Modify (drop+create pattern, MariaDB/MySQL) +- **Check CRUD** - Create, Drop, Alter (drop+create pattern, MariaDB/MySQL) +- **Trigger CRUD** - Create, Drop, Modify (drop+create pattern) +- **Table CRUD** - Create, Drop, Truncate, Rename + +### In Progress 🚧 +- **Table Alter** - Change engine, modify collation (MariaDB/MySQL specific) + +### Planned 📋 +- **Stored Procedures** - Create, Drop, Execute +- **Functions** - Create, Drop, Execute (PostgreSQL/MySQL 8+) +- **Sequences** - Create, Drop, Alter (PostgreSQL) +- **Schemas** - Create, Drop (PostgreSQL) + +## 🔍 Abstract Methods Implementation Matrix + +This matrix shows which engines correctly implement the required abstract methods from base classes. + +### Required Abstract Methods (Must Implement) + +| Class | Method | MariaDB | MySQL | SQLite | PostgreSQL | +|-------|--------|---------|-------|--------|------------| +| **Table** | rename | ✅ | ✅ | ✅ | ✅ | +| | create | ✅ | ✅ | ✅ | ✅ | +| | alter | ✅ | ✅ | ✅ | ✅ | +| | drop | ✅ | ✅ | ✅ | ✅ | +| | truncate | ✅ | ✅ | ✅ | ✅ | +| | raw_create | ✅ | ✅ | ✅ | ✅ | +| **Record** | insert | ✅ | ✅ | ✅ | ✅ | +| | update | ✅ | ✅ | ✅ | ✅ | +| | delete | ✅ | ✅ | ✅ | ✅ | +| **View** | create | ✅ | ✅ | ✅ | ✅ | +| | drop | ✅ | ✅ | ✅ | ✅ | +| | alter | ✅ | ✅ | ✅ | ✅ | +| **Trigger** | create | ✅ | ✅ | ✅ | ✅ | +| | drop | ✅ | ✅ | ✅ | ✅ | +| | alter | ✅ | ✅ | ✅ | ✅ | +| **Function** | create | ✅ | ✅ | ⚠️ | ⚠️ | +| | drop | ✅ | ✅ | ⚠️ | ⚠️ | +| | alter | ✅ | ✅ | ⚠️ | ⚠️ | +| **Procedure** | create | ⚠️ | ⚠️ | ⚠️ | ⚠️ | +| | drop | ⚠️ | ⚠️ | ⚠️ | ⚠️ | +| | alter | ⚠️ | ⚠️ | ⚠️ | ⚠️ | + +### Common Methods (Optional but Standard) + +| Class | Method | MariaDB | MySQL | SQLite | PostgreSQL | +|-------|--------|---------|-------|--------|------------| +| **Column** | add | ✅ | ✅ | ✅ | ✅ | +| | drop | ✅ | ✅ | ✅ | ✅ | +| | rename | ✅ | ✅ | ✅ | ✅ | +| | modify | ✅ | ✅ | ⚠️ | ✅ | +| **Index** | create | ✅ | ✅ | ✅ | ✅ | +| | drop | ✅ | ✅ | ✅ | ✅ | +| | modify | ✅ | ✅ | ✅ | ✅ | +| **ForeignKey** | create | ✅ | ✅ | ⚠️ | ✅ | +| | drop | ✅ | ✅ | ⚠️ | ✅ | +| | modify | ✅ | ✅ | ⚠️ | ✅ | +| **Check** | create | ✅ | ✅ | ✅ | ✅ | +| | drop | ✅ | ✅ | ✅ | ✅ | +| | alter | ✅ | ✅ | ✅ | ✅ | + +**Legend:** +- ✅ **Implemented** - Method is correctly implemented +- ⚠️ **Missing/Not Applicable** - Class or method not implemented for this engine +- ❌ **Error** - Implementation exists but has bugs + +**Key Findings:** +- ✅ **PostgreSQL 100% COMPLETE** - All 63 tests passing (was 0/63), full CRUD for all objects including Functions and Procedures +- ✅ **Code style refactoring** - All `sql` variable names changed to `statement` across all engines +- ✅ **PostgreSQL View implemented** - Uses `public` schema, proper `fully_qualified_name` override +- ✅ **PostgreSQL Trigger implemented** - Uses regex to extract table name from CREATE TRIGGER statement for proper DROP +- ✅ **PostgreSQL Column.modify implemented** - Uses separate ALTER COLUMN statements for type, nullable, default changes +- ✅ **PostgreSQL Column added** - Missing `PostgreSQLColumn` class was the main cause of skipped tests +- ✅ **PostgreSQL Check CRUD working** - All 9 Check tests now pass (was 0/9 before) +- ✅ **PostgreSQL Table CRUD working** - All 9 Table tests now pass (create, drop, truncate, rename) +- ✅ **PostgreSQL Record CRUD working** - All 9 Record tests now pass (insert, select, update, delete) +- ✅ **PostgreSQL primary key detection fixed** - Uses `pg_index` instead of hardcoded 'PRIMARY' constraint name +- ✅ **PostgreSQL Record uses DataTypeFormat** - Consistent with other engines, values formatted via `column.datatype.format()` +- ✅ **Schema vs Database.name** - PostgreSQL correctly uses `schema` attribute with fallback to `database.name` +- ✅ **Quoting refactored** - All engines now use `quote_identifier()` and `qualify()` instead of manual quoting +- ✅ **`fully_qualified_name` property** - Centralized qualified name generation, PostgreSQL overrides for schema support +- ✅ **Check constraints CRUD** - Full create/drop/alter support (MariaDB/MySQL/PostgreSQL/SQLite) +- ✅ **Functions** - MariaDB/MySQL/PostgreSQL implemented + - MariaDB: ✅ `MariaDBFunction` with create/drop/alter + - MySQL: ✅ `MySQLFunction` with create/drop/alter + - PostgreSQL: ✅ **IMPLEMENTED** `PostgreSQLFunction` with create/drop/alter (plpgsql, volatility support) + - SQLite: ❌ N/A (SQLite doesn't support stored functions) +- ⚠️ **Procedures** - PostgreSQL implemented, MariaDB/MySQL pending + - PostgreSQL: ✅ **IMPLEMENTED** `PostgreSQLProcedure` with create/drop/alter (plpgsql support) + - MariaDB/MySQL: ⚠️ NOT IMPLEMENTED (native support exists, classes missing) + - SQLite: ❌ N/A (SQLite doesn't support stored procedures) +- ⚠️ **Events** - NOT IMPLEMENTED on any engine + - Abstract `SQLEvent` class exists but no engine implementation + - MariaDB/MySQL support events natively but classes missing + - PostgreSQL/SQLite: ❌ N/A (don't support scheduled events) +- ⚠️ **SQLite Check/ForeignKey** - Inline-only (no separate create/drop after table creation) +- ⚠️ **SQLite Column.modify** - Not supported (SQLite doesn't support ALTER COLUMN) +- ⚠️ **MariaDB 5.5** - CHECK constraints not supported (too old, released 2009) + +## 📈 Test Quality Metrics + +- **Bug Detection** - Tests have found multiple API inconsistencies +- **Regression Prevention** - Ensures changes don't break existing functionality +- **Documentation** - Tests serve as executable API documentation +- **Cross-Engine Validation** - Ensures consistent behavior across databases +- **API Compliance** - Abstract methods matrix verifies all engines implement required methods + +## 🚧 Missing Implementations + +### **PostgreSQL** +- ✅ **DONE** `PostgreSQLFunction` - Implemented with create/drop/alter, plpgsql support +- ✅ **DONE** `PostgreSQLProcedure` - Implemented with create/drop/alter, plpgsql support + +### **MariaDB/MySQL** +- ⚠️ `MariaDBProcedure` / `MySQLProcedure` - Both support procedures, classes need implementation +- ⚠️ `MariaDBEvent` / `MySQLEvent` - Both support events, classes need implementation + +### **All Engines** +- ⚠️ No test coverage for Function/Procedure/Event (base test classes don't exist) +- ⚠️ Abstract `SQLProcedure` and `SQLEvent` exist but unused + +## 🎯 Implementation Priorities + +### **Current Status:** + +| Object | MariaDB | MySQL | PostgreSQL | SQLite | Priority | +|---------|---------|-------|------------|--------|----------| +| **Function** | ✅ Implemented | ✅ Implemented | ✅ **IMPLEMENTED** | ❌ N/A | ✅ **DONE** | +| **Procedure** | ⚠️ Missing | ⚠️ Missing | ✅ **IMPLEMENTED** | ❌ N/A | 🟡 Medium | +| **Event** | ⚠️ Missing | ⚠️ Missing | ❌ N/A | ❌ N/A | 🟢 Low | + +### **✅ COMPLETED: PostgreSQLFunction** + +**Implementation complete:** +1. ✅ **PostgreSQL Function** - Implemented with create/drop/alter, plpgsql support, volatility +2. ✅ **Test coverage** - 3 tests passing across postgres:latest, 16, 15 +3. ✅ **Schema handling** - Uses public schema via fully_qualified_name override +4. ✅ **All engines** - build_empty_function added to all contexts + +### **✅ COMPLETED: PostgreSQLProcedure** + +**Implementation complete:** +1. ✅ **PostgreSQL Procedure** - Implemented with create/drop/alter, plpgsql support +2. ✅ **Test coverage** - 3 tests passing across postgres:latest, 16, 15 +3. ✅ **Schema handling** - Uses public schema via fully_qualified_name override +4. ✅ **All engines** - build_empty_procedure added to all contexts + +### **🟡 NEXT Priority: MariaDB/MySQL Procedure** + +**Why this is next:** +1. **Complete engine parity** - Align MariaDB/MySQL with PostgreSQL +2. **Native support** - Both engines support procedures natively +3. **Existing pattern** - Follow PostgreSQL implementation + +### **📋 Recommended Implementation Order:** + +**Phase 1: PostgreSQLFunction** ✅ **DONE** +- ✅ Implemented `PostgreSQLFunction` with create/drop/alter methods +- ✅ Added test coverage (3 tests passing) +- ✅ PostgreSQL now has full Function support + +**Phase 2: PostgreSQLProcedure** ✅ **DONE** +- ✅ Implemented `PostgreSQLProcedure` with create/drop/alter methods +- ✅ Added test coverage (3 tests passing) +- ✅ PostgreSQL now has full Procedure support + +**Phase 3: MariaDB/MySQL Procedure** (MEDIUM priority) +- Complete `MariaDBProcedure` and `MySQLProcedure` +- Align all engines for procedure support + +**Phase 4: MariaDB/MySQL Event** (LOW priority) +- Implement `MariaDBEvent` and `MySQLEvent` +- Less commonly used, lower priority diff --git a/tests/autocomplete/README.md b/tests/autocomplete/README.md new file mode 100644 index 0000000..846956d --- /dev/null +++ b/tests/autocomplete/README.md @@ -0,0 +1,200 @@ +# Autocomplete Testing + +This directory contains golden test cases for SQL autocomplete functionality. + +## Design Principles + +### Minimum Noise Principle +**Suggest only what is truly useful.** + +The autocomplete system prioritizes relevance and context over completeness: +- Without prefix: Show the most useful suggestions for the current context +- With prefix: Filter all available options that match the prefix +- Contextual keywords (e.g., `FROM users`) appear before plain keywords (e.g., `FROM`) +- Plain keywords that guide workflow (e.g., `FROM`, `AS`) are shown even without context +- Avoid redundant suggestions that add noise without value + +**Example:** +```sql +SELECT users.id | +→ Suggestions: FROM users, AS, FROM + (Contextual keyword first, then plain keywords for flexibility) + +SELECT users.id F| +→ Suggestions: FROM users, FROM + (Both match prefix "F") +``` + +## Test Structure + +Tests are organized in JSON files under `cases/` directory. Each file contains test cases for a specific autocomplete scenario. + +## Suggestion Ordering Rules + +The autocomplete system follows strict ordering rules to ensure predictable and useful suggestions: + +### 1. Columns +**Ordered by schema definition** (as they appear in the table structure), NOT alphabetically. + +Example: For table `users(id, name, email, status, created_at)`, suggestions appear in that exact order. + +### 2. Tables +**Ordered alphabetically** by table name. + +Special cases: +- **CTEs (Common Table Expressions)**: Appear first +- **Referenced tables**: Tables used in CTEs or SELECT appear before unreferenced tables +- **Other tables**: Alphabetically sorted + +### 3. Functions and Keywords +**Ordered alphabetically** by name. + +### 4. Literals +Constants like `NULL`, `TRUE`, `FALSE` appear after columns but before functions. + +## Example + +For `SELECT * FROM users u JOIN orders o ON |`: + +``` +Suggestions order: +1. o.id, o.user_id, o.total, o.status, o.created_at (JOIN table 'orders' columns by schema) +2. u.id, u.name, u.email, u.status, u.created_at (FROM table 'users' columns by schema) +3. NULL, TRUE, FALSE (literals) +4. AVG, COALESCE, CONCAT, COUNT, ... (functions alphabetically) +``` + +Note: In JOIN ON context, JOIN tables appear before FROM tables. Columns within each table follow schema order. + +### JOIN ON Column Filtering Rule + +After an operator in JOIN ON clause, only columns from OTHER tables are suggested: + +- `FROM users u JOIN orders o ON u.id = |` → Suggest ONLY `o.*` columns (not `u.*`) +- `FROM users u JOIN orders o ON o.user_id = |` → Suggest ONLY `u.*` columns (not `o.*`) + +The query can be written both ways: +- `users.id = orders.user_id` ✓ +- `orders.user_id = users.id` ✓ + +The system detects which table is on the left of the operator and filters out ALL columns from that table. + +## Test Coverage Matrix + +Golden tests organized by SQL query writing flow (180 base tests, executed across 11 engine/version targets): + +- mysql: `8`, `9` +- mariadb: `5`, `10`, `11`, `12` +- postgresql: `15`, `16`, `17`, `18` +- sqlite: `3` + +### 1. Query Start & Basic Context +| Test Group | File | Total | ✅ | ❌ | ⚠️ | Description | +|------------|------|-------|---|---|---|-------------| +| EMPTY ![status](https://img.shields.io/badge/status-pass-brightgreen) | `cases/empty.json` | 1 | 1 | 0 | 0 | Entry-point suggestions when the editor is empty. | +| SINGLE ![status](https://img.shields.io/badge/status-pass-brightgreen) | `cases/single.json` | 6 | 6 | 0 | 0 | Single-token bootstrap and keyword completion before full parsing. | + +### 2. SELECT Clause +| Test Group | File | Total | ✅ | ❌ | ⚠️ | Description | +|------------|------|-------|---|---|---|-------------| +| SEL ![status](https://img.shields.io/badge/status-pass-brightgreen) | `cases/sel.json` | 4 | 4 | 0 | 0 | Baseline SELECT-list suggestions (functions/keywords) without table scope. | +| SELECT_PREFIX ![status](https://img.shields.io/badge/status-pass-brightgreen) | `cases/select_prefix.json` | 5 | 5 | 0 | 0 | Prefix filtering in SELECT with and without `current_table` influence. | +| SELECT_COLUMN_BEHAVIOR ![status](https://img.shields.io/badge/status-pass-brightgreen) | `cases/select_column_behavior.json` | 14 | 14 | 0 | 0 | Comma/whitespace transitions after columns and expression boundaries. | +| SELECT_SCOPED_CURRENT_TABLE ![status](https://img.shields.io/badge/status-pass-brightgreen) | `cases/select_scoped_current_table.json` | 3 | 3 | 0 | 0 | Scope-aware SELECT suggestions when FROM/JOIN tables are already known. | + +### 3. FROM Clause +| Test Group | File | Total | ✅ | ❌ | ⚠️ | Description | +|------------|------|-------|---|---|---|-------------| +| FROM ![status](https://img.shields.io/badge/status-pass-brightgreen) | `cases/from.json` | 11 | 11 | 0 | 0 | Table suggestions plus post-table clause transitions (including no-space continuations). | +| FROM_CLAUSE_PRIORITIZATION ![status](https://img.shields.io/badge/status-pass-brightgreen) | `cases/from_clause_prioritization.json` | 3 | 3 | 0 | 0 | Prioritization when SELECT already references qualified tables. | +| FROM_CLAUSE_CURRENT_TABLE ![status](https://img.shields.io/badge/status-pass-brightgreen) | `cases/from_clause_current_table.json` | 1 | 1 | 0 | 0 | FROM behavior when `current_table` is set and should affect choices. | + +### 4. JOIN Clause +| Test Group | File | Total | ✅ | ❌ | ⚠️ | Description | +|------------|------|-------|---|---|---|-------------| +| JOIN ![status](https://img.shields.io/badge/status-pass-brightgreen) | `cases/join.json` | 6 | 6 | 0 | 0 | JOIN table suggestions and join-keyword progression. | +| JOIN_ON ![status](https://img.shields.io/badge/status-pass-brightgreen) | `cases/join_on.json` | 8 | 8 | 0 | 0 | ON-clause suggestions with scope ordering and FK condition hints. | +| JOIN_AFTER_TABLE ![status](https://img.shields.io/badge/status-pass-brightgreen) | `cases/join_after_table.json` | 5 | 5 | 0 | 0 | Keyword transitions immediately after a JOIN target table, including aliased no-space prefixes. | +| JOIN_OPERATOR_LEFT_COLUMN_FILTER ![status](https://img.shields.io/badge/status-pass-brightgreen) | `cases/join_operator_left_column_filter.json` | 6 | 6 | 0 | 0 | Left-side table exclusion after JOIN operators (`=`, `<`, `>`, etc.). | + +### 5. WHERE Clause +| Test Group | File | Total | ✅ | ❌ | ⚠️ | Description | +|------------|------|-------|---|---|---|-------------| +| WHERE ![status](https://img.shields.io/badge/status-pass-brightgreen) | `cases/where.json` | 12 | 12 | 0 | 0 | WHERE context, operator, expression follow-up, and qualified-style propagation rules. | +| WHERE_SCOPED ![status](https://img.shields.io/badge/status-pass-brightgreen) | `cases/where_scoped.json` | 4 | 4 | 0 | 0 | WHERE suggestions constrained to active scope/aliases only. | + +### 6. GROUP BY Clause +| Test Group | File | Total | ✅ | ❌ | ⚠️ | Description | +|------------|------|-------|---|---|---|-------------| +| GROUP ![status](https://img.shields.io/badge/status-pass-brightgreen) | `cases/group.json` | 7 | 7 | 0 | 0 | GROUP BY column suggestions, duplicate filtering, and qualified-style propagation. | + +### 7. HAVING Clause +| Test Group | File | Total | ✅ | ❌ | ⚠️ | Description | +|------------|------|-------|---|---|---|-------------| +| HAVING ![status](https://img.shields.io/badge/status-pass-brightgreen) | `cases/having.json` | 6 | 6 | 0 | 0 | Aggregate-aware HAVING behavior, operators, and qualified-style propagation. | + +### 8. ORDER BY Clause +| Test Group | File | Total | ✅ | ❌ | ⚠️ | Description | +|------------|------|-------|---|---|---|-------------| +| ORDER ![status](https://img.shields.io/badge/status-pass-brightgreen) | `cases/order.json` | 8 | 8 | 0 | 0 | ORDER BY columns, sort-direction keyword flow, and qualified-style propagation. | + +### 9. LIMIT Clause +| Test Group | File | Total | ✅ | ❌ | ⚠️ | Description | +|------------|------|-------|---|---|---|-------------| +| LIMIT ![status](https://img.shields.io/badge/status-pass-brightgreen) | `cases/limit.json` | 3 | 3 | 0 | 0 | LIMIT/OFFSET context behavior and post-number suggestions. | + +### 10. Advanced Features +| Test Group | File | Total | ✅ | ❌ | ⚠️ | Description | +|------------|------|-------|---|---|---|-------------| +| DOT_COMPLETION ![status](https://img.shields.io/badge/status-pass-brightgreen) | `cases/dot_completion.json` | 7 | 7 | 0 | 0 | `table_or_alias.` completion, including prefix filtering after dot. | +| ALIAS ![status](https://img.shields.io/badge/status-pass-brightgreen) | `cases/alias.json` | 11 | 11 | 0 | 0 | Alias resolution and qualified suggestions in scoped expressions. | +| ALIAS_PREFIX_DISAMBIGUATION ![status](https://img.shields.io/badge/status-pass-brightgreen) | `cases/alias_prefix_disambiguation.json` | 7 | 7 | 0 | 0 | Exact alias-prefix disambiguation versus generic prefix matching. | +| PREFIX_EXPANSION ![status](https://img.shields.io/badge/status-pass-brightgreen) | `cases/prefix_expansion.json` | 6 | 6 | 0 | 0 | Prefix expansion behavior across columns, functions, and qualifiers. | +| WINDOW_FUNCTIONS_OVER ![status](https://img.shields.io/badge/status-pass-brightgreen) | `cases/window_functions_over.json` | 1 | 1 | 0 | 0 | OVER-clause bootstrap suggestions (`PARTITION BY`, `ORDER BY`). | +| CURSOR_IN_TOKEN ![status](https://img.shields.io/badge/status-pass-brightgreen) | `cases/cursor_in_token.json` | 1 | 1 | 0 | 0 | Correct prefix/context when cursor is inside an existing token. | + +### 11. Multi-Query & Special Cases +| Test Group | File | Total | ✅ | ❌ | ⚠️ | Description | +|------------|------|-------|---|---|---|-------------| +| DERIVED_TABLES_CTE ![status](https://img.shields.io/badge/status-pass-brightgreen) | `cases/derived_tables_cte.json` | 9 | 9 | 0 | 0 | Minimal CTE/derived-table scope extraction for FROM/JOIN/WHERE and dot completion. | +| MULTI_QUERY_SUPPORT ![status](https://img.shields.io/badge/status-pass-brightgreen) | `cases/multi_query_support.json` | 7 | 7 | 0 | 0 | Statement isolation in multi-query editors with correct active-scope selection. | +| MULTI_QUERY_EDGE_CASES ![status](https://img.shields.io/badge/status-pass-brightgreen) | `cases/mq.json` | 1 | 1 | 0 | 0 | Separator edge behavior in multi-query parsing. | +| OUT_OF_SCOPE_HINTS ![status](https://img.shields.io/badge/status-pass-brightgreen) | `cases/out_of_scope_hints.json` | 4 | 4 | 0 | 0 | Scope-first prefix behavior when out-of-scope names also share the prefix. | +| LEX ![status](https://img.shields.io/badge/status-pass-brightgreen) | `cases/lex.json` | 2 | 2 | 0 | 0 | Lexical resilience with quotes/comments around separators and dots. | +| ALX ![status](https://img.shields.io/badge/status-pass-brightgreen) | `cases/alx.json` | 5 | 5 | 0 | 0 | Advanced lexical interactions with alias parsing and token boundaries. | +| LARGE_SCHEMA_GUARDRAILS ![status](https://img.shields.io/badge/status-pass-brightgreen) | `cases/perf.json` | 2 | 2 | 0 | 0 | Large-schema guardrails for prefix filtering and noise control. | + +### Summary Statistics +- **Total Tests**: 1936 (176 base × 11 engine/version targets) +- **✅ Passing**: 1936 (176 base × 11 targets, 100%) +- **❌ Failing**: 0 (remaining tests, 0%) +- **⚠️ Expected Failures (xfail)**: 0 (0 base × 11 targets, 0%) +- **⚪ Not Implemented**: 0 (0%) + +### Legend +- ✅ **Pass**: Test passes successfully +- ❌ **Fail**: Test fails (needs fixing) +- ⚠️ **XFail**: Expected failure (feature not yet implemented) +- ⚪ **Skip**: Test skipped + +## Running Tests + +```bash +uv run pytest tests/autocomplete/test_golden_cases.py -v +``` + +Run specific test: +```bash +uv run pytest tests/autocomplete/test_golden_cases.py -k "test_name" +``` + +Run specific group: +```bash +uv run pytest tests/autocomplete/test_golden_cases.py -k "ON" +``` + +## Implementation Notes + +- `DERIVED_TABLES_CTE` is fully enabled in the current suite with lightweight virtual-table scope resolution. +- Current implementation intentionally targets common patterns (`WITH ... AS (SELECT col1, col2 ...)`, `FROM (SELECT col1, col2 ...) AS alias`) and avoids deep SQL normalization. +- If we later need nested or highly dynamic CTE projection inference, we can extend parsing incrementally without changing the test contract. diff --git a/tests/autocomplete/RULES.md b/tests/autocomplete/RULES.md new file mode 100644 index 0000000..229403e --- /dev/null +++ b/tests/autocomplete/RULES.md @@ -0,0 +1,2725 @@ +# SQL Autocomplete Rules + +This document defines the autocomplete behavior for each SQL context. + +--- + +## Glossary + +This section defines key terms used throughout the document to avoid ambiguity. + +### Statement vs Query vs Buffer + +- **Buffer**: The entire text content in the SQL editor, potentially containing multiple queries +- **Statement**: A single SQL query separated by the effective statement separator (e.g., `;` or `GO`) +- **Query**: Synonym for statement - a single executable SQL command +- **Current statement**: The statement where the cursor is currently positioned + +**Example:** +```sql +SELECT * FROM users; -- Statement 1 +SELECT * FROM orders; -- Statement 2 (current statement if cursor is here) +SELECT * FROM products; -- Statement 3 +``` + +### Scope and Tables + +- **Scope tables**: Tables/CTEs/derived tables that appear in the current statement's FROM/JOIN clauses + - Includes: physical tables, CTEs (Common Table Expressions), derived tables (subquery aliases) + - Priority order: derived tables > CTEs > physical tables (see **Tables in Scope Definition** section) +- **In scope**: A table is "in scope" if it appears in the FROM/JOIN of the current statement +- **Out of scope**: A table exists in the database but is not referenced in the current statement's FROM/JOIN +- **DB-wide tables**: All physical tables in the database, regardless of scope +- **CURRENT_TABLE**: The table currently selected in the table editor UI (optional, may be `None`) + +### Scope Classification + +The scope classification determines which columns are suggested in expression contexts: + +- **SCOPED**: Explicit scope exists via FROM/JOIN clauses in the current statement + - Example: `SELECT id, | FROM users` → scope = `users` table + - Example: `SELECT * FROM users u JOIN orders o ON u.id = o.user_id; SELECT u.id, |` → scope = `u`, `o` tables + - Behavior: Suggest only columns from scope tables (qualified if multiple tables, unqualified if single table) + +- **VIRTUAL_SCOPED**: Implicit scope inferred from context without explicit FROM/JOIN + - Via qualified columns: `SELECT users.id, |` → virtual scope = `users` (inferred from qualified column) + - Via CURRENT_TABLE: `SELECT id, |` with CURRENT_TABLE=users → virtual scope = `users` + - **CRITICAL:** VIRTUAL_SCOPED requires CURRENT_TABLE to be set in UI (or qualified column present) + - When VIRTUAL_SCOPED via CURRENT_TABLE (no FROM/JOIN), columns MUST be qualified (e.g., `users.id`, not `id`) + - Behavior: Suggest columns from the inferred table(s), but allow DB-wide suggestions with prefix + +- **NO_SCOPED**: No scope information available + - No FROM/JOIN in current statement + - No qualified columns to infer scope from + - No CURRENT_TABLE set + - Example: `SELECT id, |` with CURRENT_TABLE=null and no qualified columns + - Behavior: Suggest only functions (no columns without prefix) + +### Prefix and Token + +- **Token**: A valid SQL identifier matching `^[A-Za-z_][A-Za-z0-9_]*$` (or dialect-equivalent) + - Must start with letter or underscore (NOT digit) + - Can contain letters, digits, underscores after first character + - Dialect-aware: some dialects support `$`, `#`, or unicode in identifiers +- **Prefix**: The token immediately before the cursor, without containing `.` +- **Dot-completion**: When the token before cursor contains `.` (e.g., `users.` or `u.`) + +**Examples:** +- `SELECT u|` → prefix = `"u"`, triggers prefix matching +- `SELECT u.i|` → NOT a prefix (contains dot), triggers dot-completion on `u` +- `SELECT ui|` → prefix = `"ui"`, triggers prefix matching +- `SELECT 1|` → NOT a token (starts with digit), no prefix matching + +### Context Types + +- **Table-selection context**: Context where tables are suggested (FROM_CLAUSE, JOIN_CLAUSE) +- **Expression context**: Context where columns/functions are suggested (SELECT_LIST, WHERE, JOIN_ON, ORDER_BY, GROUP_BY, HAVING) +- **Scope-restricted expression context**: Expression context that MUST limit suggestions to scope tables only (WHERE, JOIN_ON, ORDER_BY, GROUP_BY, HAVING) + +### Qualification + +- **Qualified column**: Column name prefixed with table/alias (e.g., `users.id` or `u.id`) +- **Unqualified column**: Column name without prefix (e.g., `id`) +- **Alias-first qualification**: Use `alias.column` when alias exists, otherwise `table.column` + +--- + +## Important Note on Column Ordering in Examples + +**All column suggestions throughout this document preserve their table definition order (schema order / ordinal_position), NOT alphabetical order.** + +Examples may show columns in sequences that appear alphabetical for readability, but the implementation MUST return columns in their actual schema order. When in doubt, the rule is: **preserve schema order, NOT alphabetical order**. + +For clarity, examples use the following assumed schema order: +- `users`: id, name, email, password, is_enabled, created_at +- `orders`: id, user_id, total, created_at +- `products`: id, name, unit_price, stock + +--- + +## Context Detection + +The autocomplete system uses `sqlglot` to parse the SQL query and determine the current context. +Contexts are defined in the `SQLContext` enum. + +--- + +## Key Examples: Scope Restriction in Action + +These examples demonstrate the strict separation between table-selection and expression contexts. + +**Assume:** `CURRENT_TABLE = users` (set in table editor) + +### Example 1: SELECT with no FROM → CURRENT_TABLE + DB-wide allowed + +```sql +SELECT u| +``` + +**Context:** SELECT_LIST, no scope tables exist + +**Suggestions:** +- `users.id, users.name, users.email, ...` (CURRENT_TABLE columns first) +- `products.unit_price, ...` (DB-wide columns matching 'u') +- `UPPER, UUID, UNIX_TIMESTAMP` (functions) + +**Rationale:** No scope tables exist, so CURRENT_TABLE and DB-wide columns are allowed. + +--- + +### Example 2: FROM/JOIN suggests CURRENT_TABLE as table candidate + +```sql +SELECT * FROM orders JOIN | +``` + +**Context:** JOIN_CLAUSE (table-selection) + +**Suggestions:** +- `users` (CURRENT_TABLE as convenience shortcut) +- `products, customers, ...` (other physical tables) +- CTEs (if any) + +**Rationale:** JOIN_CLAUSE is table-selection. CURRENT_TABLE may be suggested even though scope tables (orders) already exist. This is how the user brings it into scope. + +--- + +### Example 3: WHERE/JOIN_ON shows only scoped columns (CURRENT_TABLE excluded unless in scope) + +```sql +-- Case A: CURRENT_TABLE not in scope +SELECT * FROM orders WHERE u| +``` + +**Context:** WHERE (expression context), scope = [orders] + +**Suggestions:** +- `orders.user_id` (scope table column matching 'u') +- `UPPER, UUID, UNIX_TIMESTAMP` (functions) + +**NOT suggested:** +- ❌ `users.*` (CURRENT_TABLE not in scope) +- ❌ `products.unit_price` (DB-wide column) + +**Rationale:** WHERE is an expression context with scope tables. CURRENT_TABLE is not in scope, so it MUST be ignored. DB-wide columns MUST NOT be suggested. + +```sql +-- Case B: CURRENT_TABLE in scope +SELECT * FROM users u JOIN orders o WHERE u| +``` + +**Context:** WHERE (expression context), scope = [users (alias u), orders (alias o)] + +**Suggestions:** +- `u.id, u.name, u.email, u.password, u.is_enabled, u.created_at` (CURRENT_TABLE in scope via alias 'u', schema order) +- `UPPER, UUID, UNIX_TIMESTAMP` (functions) + +**Rationale:** CURRENT_TABLE (users) is in scope via alias 'u', so its columns are included. + +--- + +## Precedence Chain + +The autocomplete resolution follows this strict precedence order: + +1. **Multi-Query Separation** + - If multiple queries in editor (separated by the effective statement separator), extract the current statement where the cursor is + - All subsequent rules apply only to current statement + +2. **Dot-Completion** (`table.` or `alias.`) + - If token immediately before cursor contains `.` → Dot-Completion mode + - Show columns of that table/alias (ignore broader context) + - Example: `WHERE u.i|` → show columns of `u` starting with `i` + +3. **Context Detection** (sqlglot/regex) + - Determine SQL context: SELECT_LIST, WHERE, JOIN ON, ORDER BY, etc. + - Use sqlglot parsing (primary) or regex fallback + +4. **Within Context: Prefix Rules** + - If prefix exists (token before cursor without `.`) → apply prefix matching + - Check for exact alias match first (Alias Prefix Disambiguation) + - Otherwise generic prefix matching (tables, columns, functions, keywords) + +**Example resolution:** +```sql +-- Multi-query: extract current statement +SELECT * FROM users; SELECT * FROM orders WHERE u| + +-- No dot → not Dot-Completion +-- Context: WHERE clause (scope-restricted expression context) +-- Scope: orders (from current statement's FROM clause) +-- Prefix: "u" +-- Check aliases: no alias "u" in this statement +-- Generic prefix (scope-restricted): show orders.user_id, UPPER, UUID, etc. +-- DB-wide columns excluded (scope restriction active) +``` + +**Example with dot:** +```sql +SELECT * FROM users u WHERE u.i| + +-- Dot detected → Dot-Completion (precedence 2) +-- Show columns of "u" starting with "i" +-- Context (WHERE) is ignored for this specific resolution +``` + +This precedence eliminates ambiguity: **Dot-Completion always wins**, then context, then prefix rules. + +--- + +## Universal Rules (Apply to All Contexts) + +### Prefix Definition + +**Prefix** = the identifier token immediately before the cursor, matching `^[A-Za-z_][A-Za-z0-9_]*$` (or equivalent for dialect). + +**Rules:** +- Must start with letter or underscore (NOT digit) +- Can contain letters, digits, underscores after first character +- Match is case-insensitive +- Output preserves original form (alias/table/column name) +- If token contains `.` → **not a prefix**: triggers Dot-Completion instead + +**Cursor Position Handling:** +- **Cursor at end of token** (e.g., `SELECT u|`): prefix = entire token before cursor +- **Cursor in middle of token** (e.g., `SEL|ECT`, `users.na|me`): prefix = partial token before cursor + - Text after cursor is ignored for matching + - Selecting a suggestion replaces the entire token (not just the part before cursor) + +**Examples:** +```sql +SELECT u| +→ Prefix: "u" (triggers prefix matching) + +SELECT u.i| +→ NOT a prefix (contains dot) +→ Triggers Dot-Completion on table/alias "u" + +SEL|ECT +→ Prefix: "SEL" (cursor in middle of token) +→ Suggestions: SELECT +→ Selection replaces entire token "SELECT" (removes "ECT") + +users.na|me +→ Dot-completion on "users" +→ Prefix: "na" (cursor in middle of token) +→ Suggestions: name +→ Selection replaces entire token "name" (removes "me") + +SELECT ui| +→ Prefix: "ui" (triggers prefix matching) +``` + +**This distinction is critical:** +- `u.i|` → Dot-Completion (show columns of table/alias `u` starting with `i`) +- `ui|` → Prefix matching (show items starting with `ui`) + +--- + +### Column Qualification (table.column vs alias.column) + +**Qualification rules:** + +1. **Single table in scope (no ambiguity):** + - **No prefix:** Use **unqualified** column names (e.g., `id`, `name`) + - **With prefix:** + - **Column-name match** (Generic Prefix Matching rule B): Use **unqualified** column names (e.g., `name`) + - **Table-name expansion** (Generic Prefix Matching rule A): Use **qualified** column names (e.g., `users.id`) + - **Order when BOTH match:** + 1. Column-name matches **unqualified** (e.g., `id`, `item_name`) + 2. Column-name matches **qualified** (e.g., `items.id`, `items.item_name`) + 3. Table-name expansion remaining columns **qualified** (e.g., `items.*` excluding already shown columns) + 4. Functions +2. **Multiple tables in scope:** Use qualified names with alias-first preference: + - If alias exists: `alias.column` (e.g., `u.id`) + - If no alias: `table.column` (e.g., `users.id`) + +3. **CRITICAL - Aliased tables:** When a table has an alias, the original table name CANNOT be used for qualification. SQL will reject `table.column` when an alias exists. + - **Correct:** `FROM users u WHERE u.id = 1` (use alias) + - **INCORRECT:** `FROM users u WHERE users.id = 1` (SQL error - table name not accessible) + - **Implication for autocomplete:** If prefix does not match the alias and does not match any column name, return empty suggestions. Do NOT suggest qualified columns with the original table name. + - **Example:** `FROM users u WHERE us|` → NO suggestions (prefix 'us' does not match alias 'u' or any column) + +4. **Consistency rule - Qualified context propagation:** If the query already uses at least one qualified column (e.g., `users.id` or `u.id`) in the SELECT list, column suggestions MUST stay qualified for consistency, even for single-table scopes. + - This is a style lock: once qualified style is used, autocomplete keeps qualified style. + - Applies to all column contexts: SELECT list (after comma), WHERE, JOIN ON, ORDER BY, GROUP BY, HAVING. + - For aliased tables, qualification MUST use alias only (never table name). + - For non-aliased tables, qualification uses `table.column`. + +**Rationale:** When only one table is in scope, qualification usually adds noise. However, table-name prefix expansion and explicit qualified usage both express a clear qualification intent. Once user intent is qualified style, maintaining it across contexts keeps SQL consistent and avoids invalid `table.column` usage when aliases are present. + +**Examples:** +```sql +-- Single table, no prefix: unqualified +SELECT * FROM users WHERE | +→ id, name, email, password, is_enabled, created_at + +-- Single table, prefix matches ONLY column name: unqualified +SELECT * FROM users WHERE n| +→ name (column-name match, unqualified) +→ (functions starting with 'n' if any) + +-- Single table, prefix matches ONLY table name: qualified (table-name expansion) +SELECT * FROM users WHERE u| +→ users.id, users.name, users.email, users.password, users.is_enabled, users.created_at (table-name expansion, qualified) +→ UPPER, UUID, UNIX_TIMESTAMP (functions) + +-- Single table, prefix matches BOTH column name AND table name: both shown +-- Example: table 'items' with columns: id, item_name, stock, price +SELECT * FROM items WHERE i| +→ id, item_name (column-name match, unqualified - FIRST) +→ items.id, items.item_name (column-name match, qualified - SECOND) +→ items.stock, items.price (table-name expansion remaining, qualified - THIRD) +→ IF, IFNULL (functions - FOURTH) + +-- Single table with alias, no qualified style yet: unqualified +SELECT * FROM users u WHERE | +→ id, name, email + +-- Multiple tables: qualified (alias-first) +SELECT * FROM users u JOIN orders o ON u.id = o.user_id WHERE | +→ u.id, u.name, u.email, o.user_id, o.total, o.created_at + +-- Consistency rule: qualified style propagates to all contexts +SELECT u.id FROM users u WHERE | +→ u.id, u.name, u.email, u.status, u.created_at + +SELECT u.id FROM users u ORDER BY | +→ u.id, u.name, u.email, ... + +SELECT u.id, COUNT(*) FROM users u GROUP BY | +→ u.id, u.name, u.email, ... +``` + +**Note:** This rule applies to all contexts: SELECT_LIST, WHERE, JOIN ON, ORDER BY, GROUP BY, HAVING. + +**Exception:** Dot-completion mode always returns **unqualified** column names (e.g., `id`, `name`), regardless of scope table count. See **Dot-Completion** section for details. + +--- + +### Comma and Whitespace Behavior + +**Universal rule for all contexts:** + +- **Comma is never suggested** as an autocomplete item +- If the user types `,` → they want another item → apply "next-item" rules for that context (e.g., after comma in SELECT list, show columns/functions) +- If the user types **whitespace** after a completed identifier/expression → treat it as "selection complete" → show only keywords valid in that position (clause keywords or context modifiers like `ASC`, `DESC`, `NULLS FIRST`, etc.) +- **Exception:** If whitespace follows an incomplete/invalid keyword (e.g., `SEL |` where `SEL` is not a recognized keyword) → show nothing (no suggestions) + +**Rationale:** Whitespace signals intentional pause/completion. Comma signals continuation. Incomplete keywords with whitespace are ambiguous and should not trigger suggestions. This distinction applies consistently across all SQL contexts. + +**Examples:** +```sql +SELECT id, | +→ Comma typed → next-item rules → show columns/functions + +SELECT id | +→ Whitespace typed → selection complete → show clause keywords (FROM, WHERE, AS, ...) + +ORDER BY created_at, | +→ Comma typed → next-item rules → show columns/functions + +ORDER BY created_at | +→ Whitespace typed → selection complete → show ASC, DESC, NULLS FIRST, NULLS LAST +``` + +**Special case - Qualified column in SELECT_LIST:** +```sql +SELECT users.id | +→ Whitespace after qualified column → suggest contextual keywords + plain keywords +→ Suggestions: FROM users, AS, FROM + +SELECT users.id F| +→ Whitespace after qualified column + prefix 'F' +→ Suggest: FROM {table} (contextual keyword) + FROM (plain keyword) +→ Suggestions: FROM users, FROM + +SELECT users.id W| +→ Whitespace after qualified column + prefix 'W' +→ WHERE is syntactically invalid (cannot follow SELECT item directly) +→ Suggestions: [] (empty - no valid suggestions) +``` + +**Terminology:** +- **Contextual keyword**: A keyword enriched with context-specific information (e.g., `FROM users` where `users` is inferred from the qualified column) +- **Plain keyword**: Generic keyword without context (e.g., `FROM`, `AS`) + +**Ordering:** Contextual keywords MUST appear before plain keywords. + +**Rationale:** When a qualified column is used (e.g., `users.id`), the table name is already known. Suggesting `FROM {table}` as a contextual keyword helps the user quickly add the FROM clause with the correct table. However, `FROM` plain keyword is also shown because the user might want to use a different table (e.g., `FROM orders AS users`). Contextual keywords are more specific and useful than plain keywords, so they appear first. With prefix, filter contextual keywords + plain keywords by prefix. Some keywords (e.g., WHERE) are syntactically invalid after SELECT item and should not be suggested. + +--- + +### Scope-Restricted Expression Contexts + +**Definition:** The following contexts are **scope-restricted expression contexts**: +- WHERE +- JOIN_ON +- ORDER_BY +- GROUP_BY +- HAVING + +**Scope restriction rules for these contexts:** +- Column suggestions MUST be limited to scope tables only +- Database-wide columns MUST NOT be suggested +- Table-name expansion MUST be limited to scope tables only +- Column-name matching MUST be limited to scope tables only +- `CURRENT_TABLE` columns MUST NOT be suggested unless `CURRENT_TABLE` is in scope + +**Operator context rule (WHERE, JOIN_ON):** +- When cursor is after a comparison operator (`=`, `!=`, `<>`, `<`, `>`, `<=`, `>=`, `LIKE`, `IN`, etc.) +- The column **immediately to the LEFT** of the current operator MUST NOT be suggested +- **Rationale:** Suggesting the same column on both sides (e.g., `WHERE users.id = users.id`) is redundant and not useful + +**Definition of "column immediately to the LEFT":** +- Parse the expression immediately to the left of the operator using AST +- If the expression root is (or contains as root) a **column reference** (qualified or unqualified): + - Exclude that column from suggestions +- If the expression root is a **function call, cast, or literal**: + - Do NOT exclude any columns (even if the function contains columns as arguments) + +**Examples:** +```sql +WHERE users.id = | +→ Exclude: users.id (direct column reference) + +WHERE (users.id) = | +→ Exclude: users.id (parentheses ignored, root = column reference) + +WHERE users.id::int = | +→ Exclude: users.id (cast ignored, root = column reference) + +WHERE COALESCE(users.id, 0) = | +→ Do NOT exclude users.id (root = function call, not column reference) + +WHERE users.id + 1 = | +→ Exclude: users.id (binary expression, root contains column reference) + +WHERE users.id = orders.user_id AND status = | +→ Exclude only: status (immediately left of current operator) +→ Do NOT exclude: users.id, orders.user_id (not immediately left) +``` + +**Rationale:** These clauses cannot legally reference tables not present in scope. + +--- + +### CURRENT_TABLE Scope Restriction + +**Definition:** `CURRENT_TABLE` = UI-selected table from table editor context (optional, may be `None`). + +**Scope tables** = tables/CTEs/derived tables that appear in the current statement's FROM/JOIN clauses: +- Physical tables in FROM/JOIN +- CTEs referenced in FROM/JOIN +- Derived tables (subquery aliases) in FROM/JOIN + +--- + +#### Table-Selection Contexts (FROM_CLAUSE, JOIN_CLAUSE) + +These contexts suggest **tables**, not columns. + +**Rules:** +- `CURRENT_TABLE` MAY be suggested as a table candidate +- Allowed even if scope tables already exist (this is how user brings it into scope) +- MUST NOT suggest `CURRENT_TABLE` if it is already present in the current statement +- **Statement definition:** The current query where the cursor is located, separated by the effective statement separator (not the entire buffer) +- Purpose: convenience shortcut for selecting the current table + +**Example:** +```sql +SELECT * FROM users; SELECT * FROM | +``` +In this case, `users` is NOT present in the current statement (second query), so it MAY be suggested if it is `CURRENT_TABLE`. + +--- + +#### Expression Contexts (JOIN_ON, WHERE, ORDER_BY, GROUP_BY, HAVING) + +These are **scope-restricted expression contexts** (see **Scope-Restricted Expression Contexts** section). + +These contexts suggest **columns** from scope tables only. + +--- + +#### SELECT_LIST Context (Special Case) + +**If statement has NO scope tables (no FROM/JOIN yet):** +- Without prefix: `CURRENT_TABLE` columns MUST be included first (if set) +- With prefix: `CURRENT_TABLE` columns MUST be included ONLY if they match the prefix via: + - Column-name match (e.g., `SELECT na|` → `name` from CURRENT_TABLE) + - Table-name expansion (e.g., `SELECT u|` and CURRENT_TABLE is `users` → suggest `users.*` columns) +- Database-wide columns MUST be included ONLY if prefix exists (guardrail: avoid noise when no prefix) +- Functions and keywords are included + +**If statement HAS scope tables (FROM/JOIN exists):** +- `CURRENT_TABLE` columns MUST be included ONLY if `CURRENT_TABLE` is in scope +- If `CURRENT_TABLE` is not in scope, it MUST be ignored +- Database-wide columns MUST NOT be suggested +- Scope table columns are included with alias-first qualification +- **Exception:** If prefix matches DB-wide tables but no scope tables/columns, suggest Out-of-Scope Table Hints (see dedicated section) + +--- + +### Dot-Completion (table.column or alias.column) + +**Trigger:** After `table.` or `alias.` in any SQL context + +**Show:** +- Columns of the specific table/alias (filtered by prefix if present) + +**Scope lookup:** +- Dot-completion works for physical tables, aliases, derived tables (subquery aliases), and CTEs +- The table/alias is resolved from the current statement's scope (FROM/JOIN clauses) +- For derived tables and CTEs, columns are shown if their column list is known/parseable +- If table/alias not found in scope, return empty suggestions (see Edge Case #8) + +**Output format:** Unqualified column names (e.g., `id`, `name`) NOT qualified (e.g., `u.id`, `u.name`). This is an exception to the alias-first rule used elsewhere. + +**Ordering:** Dot-completion bypasses global ordering rules and returns only the selected table's columns (table definition order). Columns preserve their ordinal position in the table schema. No functions, keywords, or other tables. + +**Filtering:** When a prefix is present after the dot (e.g., `users.i|`), filtering uses `startswith(prefix)` on column name (case-insensitive). NOT contains or fuzzy matching. + +**Examples:** +```sql +SELECT users.| +→ id, name, email, password, is_enabled, created_at (schema order, NOT alphabetical) +→ NOT users.id, users.name, ... + +SELECT users.i| +→ id, is_enabled (columns starting with 'i', in schema order) +→ NOT users.id + +WHERE u.| (where u is alias of users) +→ id, name, email, password, is_enabled, created_at (schema order) +→ NOT u.id, u.name, ... + +ON orders.| +→ id, user_id, total, created_at + +ORDER BY users.| +→ id, name, email, password, is_enabled, created_at (schema order) + +-- Dot-completion on CTE alias +WITH active_users AS (SELECT id, name FROM users WHERE status = 'active') +SELECT au.| FROM active_users au +→ id, name (CTE columns, schema order) + +-- Dot-completion on derived table alias +SELECT dt.| FROM (SELECT id, total FROM orders) AS dt +→ id, total (derived table columns, schema order) +``` + +**Note:** This rule takes precedence over context-specific rules when a dot is detected. + +--- + +## Autocomplete Rules by Context + +### 1. EMPTY (Empty editor) + +**Trigger:** Completely empty editor (no characters typed) + +**Show:** +- Primary keywords: `SELECT`, `INSERT`, `UPDATE`, `DELETE`, `CREATE`, `DROP`, `ALTER`, `TRUNCATE`, `SHOW`, `DESCRIBE`, `EXPLAIN`, `WITH`, `REPLACE`, `MERGE` + +**Examples:** +```sql +| → SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ... +``` + +--- + +### 2. SINGLE_TOKEN (Single token without spaces) + +**Trigger:** Single partial token, no spaces + +**Important:** Applies only if the entire current statement contains exactly one token (no whitespace). Whitespace includes newline. This avoids misinterpretation when splitting statements/lines. + +**Token definition:** A valid identifier matching the pattern `^[A-Za-z_][A-Za-z0-9_]*$` (or dialect-equivalent). This excludes symbols like `(`, `)`, `,`, `.`, etc. Examples: `SEL` ✅, `SEL(` ❌, `SELECT,` ❌. + +**Distinction from EMPTY:** +- EMPTY: No characters typed (cursor at position 0) +- SINGLE_TOKEN: At least one character typed, no whitespace + +**Note:** Token matching is dialect-aware; the pattern above is the default baseline. Some dialects may support `$`, `#`, or unicode characters in identifiers. + +**Show:** +- All keywords (filtered by prefix) + +**Examples:** +```sql +S| → SELECT, SHOW, SET (SINGLE_TOKEN - one character typed) +SEL| → SELECT (SINGLE_TOKEN) +| → SELECT, INSERT, UPDATE (EMPTY - no characters) +``` + +--- + +### 3. SELECT_LIST (Inside SELECT, before FROM) + +**Trigger:** After `SELECT` and before `FROM` + +**Important:** Column suggestions depend on whether FROM/JOIN are present in the query. + +#### 3a. Without prefix (after SELECT, no FROM/JOIN in query) + +**Show:** +- `CURRENT_TABLE` columns (if set) +- Functions +- Keywords (FROM, WHERE, etc.) - **only if SELECT list already has items** + - e.g., `SELECT id |` → show keywords (can continue query) + - e.g., `SELECT |` → no keywords (nothing to continue) + +**Rationale:** Keywords like FROM/WHERE make sense to continue the query, but only if there's already something to continue from. Empty SELECT has no context for keywords. + +**Examples:** +```sql +-- Assume CURRENT_TABLE = users +SELECT | +→ users.id, users.name, users.email, users.status, users.created_at +→ COUNT, SUM, AVG, MAX, MIN, UPPER, LOWER, ... + +-- SELECT with existing item + CURRENT_TABLE +SELECT id | +→ users.id, users.name, users.email, users.status, users.created_at +→ FROM, WHERE, LIMIT, ... + +-- No CURRENT_TABLE set (empty SELECT) +SELECT | +→ COUNT, SUM, AVG, MAX, MIN, UPPER, LOWER, ... +``` + +#### 3a-bis. Without prefix (after SELECT, with FROM/JOIN in query) + +**Show:** +- Columns in scope (unqualified if single table, qualified with alias-first if multiple tables) + - **Note:** With prefix, see **Generic Prefix Matching** section for table-name expansion rules +- All functions + +**Examples:** +```sql +SELECT * FROM users WHERE id = 1; SELECT | +→ id, name, email, password, is_enabled, created_at, ... (single table: unqualified) +→ COUNT, SUM, AVG, ... + +SELECT * FROM users u JOIN orders o ON u.id = o.user_id; SELECT | +→ u.id, u.name, o.user_id, o.total, ... (multiple tables: qualified) +→ COUNT, SUM, AVG, ... +``` + +#### 3b. With prefix + +**Show:** +- Columns matching the prefix (see **Generic Prefix Matching for Column Contexts** section) +- Functions matching the prefix +- **Note:** Keywords are NOT included (syntactically invalid in SELECT list, e.g., `SELECT SELECT` or `SELECT UPDATE`) + +**Matching priority:** +1. If prefix exactly equals an alias → Alias-exact-match mode (see **Alias Prefix Disambiguation** section) +2. Otherwise → Generic prefix matching (see **Generic Prefix Matching for Column Contexts** section) + +**CURRENT_TABLE handling:** + +- **When NO scope tables exist (no FROM/JOIN):** + - `CURRENT_TABLE` columns MUST be included first (if set) + - Database-wide table-name expansion and column-name matching are included + - Functions are included + +- **When NO scope tables AND NO prefix:** + - `CURRENT_TABLE` columns (if set) + - Functions + - Keywords - only if SELECT list already has items (e.g., `SELECT id |`) + - **Rationale:** Keywords only make sense when there's something to continue from + +- **When scope tables exist (FROM/JOIN present):** + - `CURRENT_TABLE` columns MUST be included ONLY if `CURRENT_TABLE` is in scope + - If `CURRENT_TABLE` is not in scope, it MUST be ignored + - Scope table columns are included with alias-first qualification + - **Out-of-Scope Table Hints:** If prefix matches DB-wide tables but no scope tables/columns, suggest table names as hints (see **Out-of-Scope Table Hints** section) + +**Examples:** + +**No FROM/JOIN (CURRENT_TABLE included):** +```sql +-- Assume CURRENT_TABLE = users +SELECT u| +→ users.id, users.name, users.email, ... (CURRENT_TABLE via table-name expansion) +→ user_sessions.* (other tables starting with 'u') +→ orders.user_id, products.unit_price (DB-wide columns starting with 'u') +→ Functions: UPPER, UUID, UNIX_TIMESTAMP +``` + +**FROM/JOIN exists, CURRENT_TABLE not in scope (CURRENT_TABLE ignored):** +```sql +-- Assume CURRENT_TABLE = users +SELECT u| FROM orders +→ orders.user_id (scope table column starting with 'u') +→ UPPER, UUID, UNIX_TIMESTAMP (functions) +→ users + Add via FROM/JOIN (Out-of-Scope Table Hint) +→ (CURRENT_TABLE ignored - not in scope) +→ (DB-wide columns excluded - scope restriction active) +``` + +**FROM/JOIN exists, CURRENT_TABLE in scope (CURRENT_TABLE included):** +```sql +-- Assume CURRENT_TABLE = users +SELECT u| FROM users u JOIN orders o +→ Alias-exact-match mode activated (u == alias 'u') +→ u.id, u.name, u.email (CURRENT_TABLE in scope via alias 'u') +→ UPPER, UUID, UNIX_TIMESTAMP (functions) +``` + +#### 3c. After comma (next column) + +**Trigger:** After comma in SELECT list + +**Show:** +- All columns (unqualified if single table, qualified with alias-first if multiple tables) (filtered by prefix if present) + - **Note:** With prefix, see **Generic Prefix Matching** section for table-name expansion rules +- All functions (filtered by prefix if present) + +**Special case - previous item is qualified:** +- If the select item immediately before the comma is a qualified column reference (`table.column` or `alias.column`), suggest columns from that same qualifier first. +- This applies even when there is no FROM/JOIN scope and no prefix. +- Do NOT include database-wide columns from other tables unless a prefix exists. + +**Rationale:** The user has already chosen a specific table/alias by qualifying the previous select item, so suggesting the remaining columns from the same qualifier is useful and avoids noisy DB-wide suggestions. + +**Examples:** +```sql +-- Single table, no prefix: unqualified +SELECT col1, | +→ id, name, email, ... (single table: unqualified) +→ COUNT, SUM, AVG, ... + +-- Single table, no FROM in current statement, no prefix: unqualified +SELECT * FROM users u WHERE id = 1; SELECT id, | +→ id, name, email, ... (single table: unqualified) +→ COUNT, SUM, AVG, ... + +-- Single table, prefix matches column name: unqualified +SELECT id, n| +→ name (column-name match, unqualified) + +-- Single table, prefix matches table name: qualified (table-name expansion) +SELECT * FROM users; SELECT id, u| +→ users.id, users.name, users.email, users.password, users.is_enabled, users.created_at (table-name expansion, qualified) +→ UPPER, UUID, UNIX_TIMESTAMP + +-- Multiple tables: qualified +SELECT * FROM users u JOIN orders o ON u.id = o.user_id; SELECT u.id, | +→ u.id, u.name, u.email, o.user_id, o.total, o.created_at (multiple tables: qualified) +→ COUNT, SUM, AVG, ... +``` + +#### 3d. After complete column (space after column) + +**Trigger:** After a complete column name (with or without table prefix) followed by space + +**Show:** +- Keywords ONLY: `FROM`, `WHERE`, `AS`, `LIMIT`, `ORDER BY`, `GROUP BY`, `HAVING` +- `AS` only if the current select item has no alias yet +- **IMPORTANT:** NO functions (COUNT, SUM, UPPER, etc.) - user has completed selection + +**Note:** If alias presence cannot be reliably detected in incomplete SQL, default to offering `AS` (non-breaking UX). + +**Rationale:** Whitespace after a complete column indicates "selection complete" - user wants to move to next clause, not continue with functions. + +**Examples:** +```sql +SELECT id | +→ FROM, WHERE, AS, LIMIT, ORDER BY, GROUP BY, HAVING +→ NOT: COUNT, SUM, UPPER, etc. + +SELECT id AS user_id | +→ FROM, WHERE, LIMIT, ORDER BY, GROUP BY, HAVING (AS excluded - alias already present) + +SELECT users.id | +→ FROM, WHERE, AS, LIMIT, ORDER BY, GROUP BY, HAVING +→ NOT: COUNT, SUM, UPPER, etc. + +SELECT table.column | +→ FROM, WHERE, AS, LIMIT, ORDER BY, GROUP BY, HAVING +→ NOT: functions +``` + +--- + +### 4. FROM_CLAUSE (After FROM) + +**Trigger:** After `FROM` and before `WHERE`/`JOIN` + +#### 4a. Without prefix + +**Show:** +- CTE names (if available from WITH clause) +- All physical tables +- `CURRENT_TABLE` (if set and not already present in current statement) + +**Prioritization/Filtering:** If SELECT list contains qualified columns (e.g., `SELECT users.id`), suggest ONLY those referenced tables in FROM suggestions. When multiple tables are referenced, follow their left-to-right appearance order in the SELECT list. + +**Examples:** +```sql +SELECT * FROM | +→ customers, orders, products, users (alphabetical - no prioritization) + +SELECT users.id FROM | +→ users (ONLY referenced table from qualified SELECT column) + +SELECT orders.total, users.name FROM | +→ orders, users (ONLY referenced tables, left-to-right from SELECT) + +WITH active_users AS (SELECT * FROM users WHERE status = 'active') +SELECT * FROM | +→ active_users, users, orders, products, ... +``` + +**Note:** Derived tables are not suggested as candidates to type in FROM/JOIN in v1 (they are inline subqueries, not selectable by name); but if present in the query, their alias contributes columns to scope. + +**CURRENT_TABLE handling:** + +`CURRENT_TABLE` may be suggested if: +- It is set +- It is not already present in the current statement + +**Table re-suggestion policy (self-join support):** + +Physical tables already present in FROM/JOIN: +- **WITHOUT alias**: MUST NOT be suggested again (e.g., `FROM products, |` → products excluded) +- **WITH alias**: MAY be suggested again for self-join (e.g., `FROM products p, |` → products allowed) + +**Rationale:** FROM_CLAUSE is a table-selection context (scope construction). When users explicitly type qualified columns in SELECT, suggesting only referenced tables reduces ambiguous choices (e.g., `product` vs `products`) and avoids accidental wrong-table selection. Tables without aliases cannot be re-used (SQL syntax error), but tables with aliases enable self-join patterns (e.g., `FROM users u1 JOIN users u2`). + +#### 4b. With prefix + +**Show:** +- CTE names starting with the prefix +- Physical tables starting with the prefix +- `CURRENT_TABLE` (if set, matches prefix, and not already present in current statement) + +**Prioritization/Filtering:** Same as 4a - if SELECT list contains qualified columns, filter to ONLY referenced tables, then apply prefix matching within that set. When multiple tables are referenced, follow their left-to-right appearance order in the SELECT list. + +**Examples:** +```sql +SELECT * FROM u| +→ users + +SELECT users.column FROM u| +→ users (ONLY referenced table matches prefix) + +SELECT products.price, users.name FROM u| +→ users (ONLY referenced table matching prefix) + +SELECT orders.total, users.name FROM o| +→ orders (ONLY referenced table matching prefix) + +WITH active_users AS (...) +SELECT * FROM a| +→ active_users +``` + +#### 4c. After comma (multiple tables) + +**Trigger:** After comma in FROM clause + +**Show:** +- CTE names (if available) +- All physical tables (filtered by prefix if present) + +**Examples:** +```sql +SELECT * FROM users, | +→ orders, products, customers, ... + +WITH active_users AS (...) +SELECT * FROM users, | +→ active_users, orders, products, ... + +SELECT * FROM users, o| +→ orders +``` + +#### 4d. After table name (space after table) + +**Trigger:** After a complete table name followed by space + +**Show:** +- Keywords: `JOIN`, `INNER JOIN`, `LEFT JOIN`, `RIGHT JOIN`, `CROSS JOIN`, `WHERE`, `GROUP BY`, `ORDER BY`, `LIMIT` +- `AS` (only if the table doesn't already have an alias) + +**Alias detection (best-effort):** +- SQL allows aliases with or without `AS` keyword (e.g., `FROM users u` or `FROM users AS u`) +- Autocomplete should treat both forms as "alias present" to avoid suggesting `AS` when alias already exists +- Detection: if token after table name is a valid identifier (not a keyword), treat it as an alias + +**Examples:** +```sql +SELECT * FROM users | +→ JOIN, INNER JOIN, LEFT JOIN, RIGHT JOIN, CROSS JOIN, AS, WHERE, GROUP BY, ORDER BY, LIMIT + (AS included because no alias defined yet) + +SELECT * FROM users AS u | +→ JOIN, INNER JOIN, LEFT JOIN, RIGHT JOIN, CROSS JOIN, WHERE, GROUP BY, ORDER BY, LIMIT + (AS excluded because alias 'u' already exists) + +SELECT * FROM users u | +→ JOIN, INNER JOIN, LEFT JOIN, RIGHT JOIN, CROSS JOIN, WHERE, GROUP BY, ORDER BY, LIMIT + (AS excluded because alias 'u' already exists, even without AS keyword) +``` + +#### 4e. After AS (alias definition) + +**Trigger:** After `AS` keyword in FROM clause + +**Show:** +- Nothing (empty list) + +**Rationale:** User is typing a custom alias name. No suggestions should interfere with free-form text input. + +**Examples:** +```sql +SELECT * FROM users AS | +→ (no suggestions - user types alias freely) + +SELECT * FROM users AS u| +→ (no suggestions - user is typing alias name) + +SELECT * FROM users AS u | +→ JOIN, INNER JOIN, LEFT JOIN, RIGHT JOIN, WHERE, GROUP BY, ORDER BY, LIMIT + (alias complete, suggest next clauses) +``` + +**Note:** Once the alias is complete (followed by space), normal clause keyword suggestions resume. + +--- + +### 5. JOIN_CLAUSE (After JOIN) + +**JOIN_CLAUSE is a table-selection context (like FROM).** + +It suggests tables, not columns. + +**Allowed suggestions:** +- CTE names +- Physical tables +- `CURRENT_TABLE` (as a convenience table candidate, if not already present in the statement) + +**Important:** JOIN_CLAUSE is part of scope construction. It may include `CURRENT_TABLE` even if other scope tables already exist. Column suggestion logic must NOT run in this context. + +**Trigger:** After `JOIN`, `INNER JOIN`, `LEFT JOIN`, `RIGHT JOIN`, etc. + +#### 5a. Without prefix + +**Show:** +- CTE names (if available from WITH clause) +- All physical tables +- `CURRENT_TABLE` (if set and not already present in current statement) + +**Examples:** +```sql +SELECT * FROM users JOIN | +→ orders, products, customers, ... + +WITH active_users AS (...) +SELECT * FROM users JOIN | +→ active_users, orders, products, ... +``` + +**Note:** Derived tables are not suggested as candidates to type in FROM/JOIN in v1 (they are inline subqueries, not selectable by name); but if present in the query, their alias contributes columns to scope. + +**CURRENT_TABLE handling:** + +`CURRENT_TABLE` may be suggested if: +- It is set +- It is not already present in the current statement + +**Table re-suggestion policy (self-join support):** + +Physical tables already present in FROM/JOIN: +- **WITHOUT alias**: MUST NOT be suggested again (e.g., `FROM orders JOIN |` → orders excluded) +- **WITH alias**: MAY be suggested again for self-join (e.g., `FROM orders o JOIN |` → orders allowed) + +**Rationale:** JOIN_CLAUSE is a table-selection context (scope extension). It follows the same rule as FROM_CLAUSE. Tables without aliases cannot be re-used (SQL syntax error), but tables with aliases enable self-join patterns (e.g., `FROM users u1 JOIN users u2`). + +#### 5b. With prefix + +**Show:** +- CTE names starting with the prefix +- Physical tables starting with the prefix +- `CURRENT_TABLE` (if set, matches prefix, and not already present in current statement) + +**Examples:** +```sql +SELECT * FROM users JOIN o| +→ orders + +WITH active_users AS (...) +SELECT * FROM users u LEFT JOIN a| +→ active_users +``` + +#### 5c. After table name (space after table) + +**Trigger:** After a complete table name in JOIN clause followed by space + +**Show:** +- Keywords: `AS`, `ON`, `USING` +- `AS` (only if the table doesn't already have an alias) + +**Alias detection (best-effort):** +- SQL allows aliases with or without `AS` keyword (e.g., `JOIN orders o` or `JOIN orders AS o`) +- Autocomplete should treat both forms as "alias present" to avoid suggesting `AS` when alias already exists +- Detection: if token after table name is a valid identifier (not a keyword), treat it as an alias + +**Examples:** +```sql +SELECT * FROM users JOIN orders | +→ AS, ON, USING + (AS included because no alias defined yet) + +SELECT * FROM users u LEFT JOIN products AS p | +→ ON, USING + (AS excluded because alias 'p' already exists) + +SELECT * FROM users u LEFT JOIN products p | +→ ON, USING + (AS excluded because alias 'p' already exists, even without AS keyword) +``` + +--- + +### 5-JOIN_ON. JOIN_ON (Expression Context) + +**JOIN_ON is an expression context.** + +It suggests columns and functions. + +**Column suggestions MUST be restricted strictly to tables in scope:** +- FROM tables +- JOIN tables +- CTEs referenced in the statement +- Derived tables (subquery aliases) + +**Critical restrictions:** + +See **Scope-Restricted Expression Contexts** section for complete rules. + +--- + +#### 5d. After ON (without prefix) + +**Trigger:** After `ON` keyword in JOIN clause + +**Show:** +- FK join-condition hints (complete expressions) first, when a foreign key relationship exists between the current JOIN table and already scoped tables + - Direction: `left.pk = right.fk` when right-side table owns the FK + - Alias-first qualification always applies in hints +- Columns from scope tables only (qualified with alias-first) +- All functions + +**Examples:** +```sql +SELECT * FROM users JOIN orders ON | +→ users.id, users.name, users.email, orders.id, orders.user_id, orders.total, orders.created_at +→ COUNT, SUM, AVG, ... + +SELECT * FROM users u JOIN orders o ON | +→ u.id = o.user_id (FK hint first) +→ u.id, u.name, u.email, o.id, o.user_id, o.total, o.created_at +→ COUNT, SUM, AVG, ... +``` + +#### 5e. After ON (with prefix) + +**Trigger:** After `ON` keyword with prefix in JOIN clause + +**Show:** +- Prefix-matching FK join-condition hints first (if any) +- Columns matching the prefix (see **Generic Prefix Matching for Column Contexts** section) +- Functions matching the prefix + +**Examples:** + +**Generic prefix (no alias exact match):** +```sql +SELECT * FROM users JOIN orders ON u| +→ Context: JOIN_ON (scope-restricted) +→ Table-name expansion: users.* (all columns from scope table 'users') +→ Column-name matching: orders.user_id (scope table column only) +→ Functions: UPPER, UUID, UNIX_TIMESTAMP +→ (Database-wide columns excluded - scope restriction active) +``` + +**Alias exact match:** +```sql +SELECT * FROM users u JOIN orders o ON u| +→ Context: JOIN_ON (scope-restricted) +→ Alias-exact-match mode (u == alias 'u') +→ u.id, u.name, u.email, u.password, u.is_enabled, u.created_at +→ UPPER, UUID, UNIX_TIMESTAMP +``` + +#### 5f. After comparison operator + +**Trigger:** After `=`, `!=`, `<`, `>`, etc. in ON clause + +**Show:** +- Literal keywords: `NULL`, `TRUE`, `FALSE` +- Columns from scope tables only (unqualified if single table, qualified with alias-first if multiple tables) (filtered by prefix if present) +- All functions (filtered by prefix if present) + +**Operator context rule applies:** The column **immediately to the LEFT** of the current operator MUST NOT be suggested (see **Scope-Restricted Expression Contexts** section for details). + +**Column ranking (HeidiSQL-like UX):** +- Prioritize columns from the **other-side table** (typically the table being joined) +- Then columns from other tables in scope +- This helps users quickly find the matching column + +**Other-side table determination:** +- If left side of operator has qualified column (e.g., `u.id = |`) → other-side = all other tables in scope, prioritizing tables introduced by current JOIN +- If left side is from derived table/CTE → other-side = same logic +- If left side is not recognizable → fallback to scope table ordering (FROM > JOIN) + +**Critical:** Database-wide columns and `CURRENT_TABLE` are excluded (scope restriction active). + +**Examples:** +```sql +SELECT * FROM users JOIN orders ON users.id = | +→ NULL, TRUE, FALSE +→ orders.user_id, orders.id, orders.total, orders.created_at, ... (orders columns prioritized - other-side table) +→ users.name, users.email, users.password, users.is_enabled, users.created_at, ... (users columns after) +→ (users.id excluded - immediately left of operator) + +SELECT * FROM users u JOIN orders o ON u.id = | +→ NULL, TRUE, FALSE +→ o.user_id, o.id, o.total, o.created_at, ... (orders columns prioritized - other-side table) +→ u.name, u.email, u.password, u.is_enabled, u.created_at, ... (users columns after) +→ (u.id excluded - immediately left of operator) + +SELECT * FROM users u JOIN orders o ON u.id = o| +→ o.user_id, o.id +``` + +#### 5g. After complete expression (logical operators) + +**Trigger:** After a complete condition/expression followed by space in ON clause + +**Show:** +- Logical keywords: `AND`, `OR`, `NOT` +- Other keywords: `WHERE`, `ORDER BY`, `GROUP BY`, `LIMIT` + +**Examples:** +```sql +SELECT * FROM users JOIN orders ON users.id = orders.user_id | +→ AND, OR, WHERE, ORDER BY, LIMIT + +SELECT * FROM users u JOIN orders o ON u.id = o.user_id | +→ AND, OR, NOT, WHERE, ORDER BY, LIMIT +``` + +--- + +### 6. WHERE_CLAUSE (After WHERE) + +**Trigger:** After `WHERE`, `AND`, `OR` + +**Important:** Only show columns from tables specified in FROM and JOIN clauses (using alias if defined, otherwise table name). + +#### 6a. Without prefix + +**Show:** +- All columns (unqualified if single table, qualified with alias-first if multiple tables) + - **Note:** With prefix, see **Generic Prefix Matching** section for table-name expansion rules +- All functions + +**Examples:** +```sql +SELECT * FROM users WHERE | +→ id, name, email, password, is_enabled, created_at, ... (single table: unqualified, schema order) +→ COUNT, SUM, AVG, ... + +SELECT * FROM users u WHERE | +→ id, name, email, password, is_enabled, created_at, ... (single table: unqualified, schema order) +→ COUNT, SUM, AVG, ... +``` + +#### 6b. With prefix + +**Show:** +- Columns matching the prefix (see **Generic Prefix Matching for Column Contexts** section) +- Functions matching the prefix + +**Examples:** +```sql +SELECT * FROM users WHERE u| +→ Context: WHERE (scope-restricted) +→ Scope: [users] +→ Prefix: "u" +→ Column-name match: (none - no columns start with 'u') +→ Table-name expansion: users.* (table name starts with 'u' → all columns qualified) +→ users.id, users.name, users.email, users.password, users.is_enabled, users.created_at (schema order) +→ Functions: UPPER, UUID, UNIX_TIMESTAMP +→ (DB-wide columns excluded - scope restriction active) + +SELECT * FROM users WHERE n| +→ Context: WHERE (scope-restricted) +→ Prefix: "n" +→ Column-name match: name (column starting with 'n', single table: unqualified) +→ Functions: (none starting with 'n') + +SELECT * FROM items WHERE i| +→ Context: WHERE (scope-restricted) +→ Scope: [items] +→ Prefix: "i" +→ Prefix matches BOTH column names AND table name +→ Column-name match unqualified: id, item_name (FIRST - schema order) +→ Column-name match qualified: items.id, items.item_name (SECOND - schema order) +→ Table-name expansion remaining: items.stock, items.price (THIRD - schema order, excluding id/item_name) +→ Functions: IF, IFNULL (FOURTH) +``` + +#### 6c. After comparison operator + +**Trigger:** After `=`, `!=`, `<`, `>`, `<=`, `>=`, `LIKE`, `IN`, etc. in WHERE clause + +**Show:** +- Literal keywords: `NULL`, `TRUE`, `FALSE`, `CURRENT_DATE`, `CURRENT_TIME`, `CURRENT_TIMESTAMP` +- All columns (unqualified if single table, qualified with alias-first if multiple tables) (filtered by prefix if present) +- All functions (filtered by prefix if present) + +**Operator context rule applies:** The column **immediately to the LEFT** of the current operator MUST NOT be suggested (see **Scope-Restricted Expression Contexts** section for details). + +**Examples:** +```sql +SELECT * FROM users WHERE id = | +→ NULL, TRUE, FALSE +→ name, email, password, is_enabled, created_at, ... (single table: unqualified) +→ COUNT, SUM, ... +→ (id excluded - immediately left of operator) + +SELECT * FROM users WHERE is_enabled = | +→ NULL, TRUE, FALSE +→ id, name, email, password, created_at, ... (single table: unqualified) +→ COUNT, SUM, ... +→ (is_enabled excluded - immediately left of operator) + +SELECT * FROM users WHERE created_at > | +→ CURRENT_DATE, CURRENT_TIME, CURRENT_TIMESTAMP +→ id, name, email, password, is_enabled, ... (single table: unqualified) +→ (created_at excluded - immediately left of operator) +``` + +**Note:** User can also type string literals (`'...'`) or numbers directly. Future enhancement: suggest `'...'` as snippet. + +#### 6d. After complete expression (logical operators) + +**Trigger:** After a complete condition/expression followed by space + +**Show:** +- Logical keywords: `AND`, `OR`, `NOT`, `EXISTS`, `IN`, `BETWEEN`, `LIKE`, `IS NULL`, `IS NOT NULL` +- Other keywords: `ORDER BY`, `GROUP BY`, `LIMIT`, `HAVING` + +**Examples:** +```sql +SELECT * FROM users WHERE id = 1 | +→ AND, OR, ORDER BY, GROUP BY, LIMIT + +SELECT * FROM users WHERE status = 'active' | +→ AND, OR, NOT, ORDER BY, LIMIT + +SELECT * FROM users WHERE id > 10 | +→ AND, OR, BETWEEN, ORDER BY, LIMIT +``` + +--- + +### 7. ORDER_BY_CLAUSE (After ORDER BY) + +**Trigger:** After `ORDER BY` + +**Important:** Only show columns from tables specified in FROM and JOIN clauses (using alias if defined, otherwise table name). + +**MUST NOT suggest literals:** Do NOT suggest `NULL`, `TRUE`, `FALSE`, `CURRENT_DATE`, `CURRENT_TIME`, `CURRENT_TIMESTAMP` in ORDER BY context. Ordering by constants is meaningless as all rows would have the same sort value. + +#### 7a. Without prefix + +**Show:** +- Columns in scope (unqualified if single table, qualified with alias-first if multiple tables) + - **Note:** With prefix, see **Generic Prefix Matching** section for table-name expansion rules +- Functions + +**MUST NOT suggest sort direction keywords:** Do NOT suggest `ASC`, `DESC`, `NULLS FIRST`, `NULLS LAST` in `ORDER BY |` without a column specified. These keywords are only meaningful after a column/expression (see section 7c). + +**Ordering:** Columns first, then functions. Users must choose the column before specifying sort direction. + +**Examples:** +```sql +SELECT * FROM users ORDER BY | +→ id, name, email, password, is_enabled, created_at, ... (single table: unqualified, columns first) +→ COUNT, SUM, AVG, ... (functions) +→ NOT: ASC, DESC (no column specified yet) + +SELECT * FROM users u JOIN orders o ON u.id = o.user_id ORDER BY | +→ u.id, u.name, o.total, o.created_at, ... (columns first) +→ COUNT, SUM, AVG, ... (functions) +→ NOT: ASC, DESC (no column specified yet) +``` + +#### 7b. With prefix + +**Show:** +- Columns matching the prefix (see **Generic Prefix Matching for Column Contexts** section) +- Functions matching the prefix +- Keywords matching the prefix + +**Examples:** +```sql +SELECT * FROM users ORDER BY c| +→ Context: ORDER_BY (scope-restricted) +→ Scope: [users] +→ Table-name expansion: (none - no scope table starts with 'c') +→ Column-name matching: users.created_at (scope table column only) +→ Functions: COUNT, CONCAT, COALESCE +→ Keywords: (none starting with 'c') +→ (DB-wide columns excluded - scope restriction active) +``` + +#### 7c. After column (space after column) + +**Show:** +- Keywords: `ASC`, `DESC`, `NULLS FIRST`, `NULLS LAST` + +**Examples:** +```sql +SELECT * FROM users ORDER BY created_at | +→ ASC, DESC, NULLS FIRST, NULLS LAST +``` + +#### 7d. After comma (multiple sort keys) + +**Trigger:** After comma in ORDER BY clause + +**Show:** +- Columns in scope (unqualified if single table, qualified with alias-first if multiple tables) (filtered by prefix if present) +- Functions (filtered by prefix if present) + +**Examples:** +```sql +SELECT * FROM users ORDER BY created_at DESC, | +→ id, name, email, password, is_enabled, created_at, ... (single table: unqualified) +→ COUNT, SUM, AVG, ... + +SELECT * FROM users u ORDER BY created_at DESC, n| +→ name (single table: unqualified) +``` + +--- + +### 8. GROUP_BY_CLAUSE (After GROUP BY) + +**Trigger:** After `GROUP BY` + +**Important:** Only show columns from tables specified in FROM and JOIN clauses (using alias if defined, otherwise table name). + +#### 8a. Without prefix + +**Show:** +- Columns in scope (unqualified if single table, qualified with alias-first if multiple tables) + - **Note:** With prefix, see **Generic Prefix Matching** section for table-name expansion rules +- Functions + +**Examples:** +```sql +SELECT COUNT(*) FROM users GROUP BY | +→ id, name, email, password, is_enabled, created_at, ... (single table: unqualified) +→ DATE, YEAR, MONTH, ... + +SELECT COUNT(*) FROM users u JOIN orders o ON u.id = o.user_id GROUP BY | +→ u.id, u.name, u.email, o.status, ... +→ DATE, YEAR, MONTH, ... +``` + +#### 8b. With prefix + +**Show:** +- Columns matching the prefix (see **Generic Prefix Matching for Column Contexts** section) +- Functions matching the prefix + +**Examples:** +```sql +SELECT COUNT(*) FROM users GROUP BY s| +→ Table-name expansion: (none - no tables starting with 's') +→ Column-name matching: status (column starting with 's', single table: unqualified) +→ Functions: SUM, SUBSTR +``` + +#### 8c. After comma (multiple group keys) + +**Trigger:** After comma in GROUP BY clause + +**Show:** +- Columns in scope (unqualified if single table, qualified with alias-first if multiple tables) (filtered by prefix if present) + - **MUST NOT suggest columns already present in the GROUP BY clause** +- Functions (filtered by prefix if present) + +**Examples:** +```sql +SELECT COUNT(*) FROM users GROUP BY status, | +→ id, name, email, password, is_enabled, created_at, ... (single table: unqualified) +→ NOT: status (already present in GROUP BY) +→ DATE, YEAR, MONTH, ... + +SELECT COUNT(*) FROM users u GROUP BY is_enabled, c| +→ created_at (single table: unqualified) +→ NOT: is_enabled (already present) +``` + +--- + +### 9. HAVING_CLAUSE (After HAVING) + +**Trigger:** After `HAVING` + +**Important:** Only show columns from tables specified in FROM and JOIN clauses. Focus on aggregate functions. + +**Aggregate functions definition:** Predefined set of functions per SQL dialect that perform aggregation operations. Standard set includes: `COUNT`, `SUM`, `AVG`, `MAX`, `MIN`. Vendor-specific additions: `GROUP_CONCAT` (MySQL), `STRING_AGG` (PostgreSQL), `LISTAGG` (Oracle), `ARRAY_AGG`, etc. This list is dialect-dependent and should be maintained as a constant set in the implementation. + +#### 9a. Without prefix + +**Show:** +- Aggregate functions (prioritized): from the predefined aggregate functions set for current dialect +- Columns in scope (unqualified if single table, qualified with alias-first if multiple tables) + - **Note:** With prefix, see **Generic Prefix Matching** section for table-name expansion rules +- Other functions (non-aggregate) + +**Ordering:** Aggregate functions first (alphabetical), then columns (schema order - NOT alphabetical), then other functions (alphabetical). + +**Rationale:** HAVING typically filters aggregates; prioritizing aggregate functions reduces keystrokes and improves UX. + +**Note:** Columns preserve their table definition order (ordinal_position), consistent with global ordering rules. + +**Examples:** +```sql +SELECT status, COUNT(*) FROM users GROUP BY status HAVING | +→ COUNT, SUM, AVG, MAX, MIN, ... (aggregate functions first, alphabetical) +→ id, name, email, ... (single table: unqualified, schema order) +→ CONCAT, UPPER, LOWER, ... (other functions, alphabetical) +``` + +#### 9b. With prefix + +**Show:** +- Aggregate functions matching the prefix (prioritized): from the predefined aggregate functions set for current dialect +- Columns matching the prefix (see **Generic Prefix Matching for Column Contexts** section) +- Other functions matching the prefix (non-aggregate) + +**Ordering:** Aggregate functions first (alphabetical), then columns (schema order - NOT alphabetical), then other functions (alphabetical). + +**Note:** Columns preserve their table definition order (ordinal_position), consistent with global ordering rules. + +**Examples:** +```sql +SELECT status, COUNT(*) FROM users GROUP BY status HAVING c| +→ COUNT (aggregate function first, alphabetical) +→ created_at (single table: unqualified, scope column matching prefix 'c') +→ CONCAT, COALESCE (other functions, alphabetical) +→ (DB-wide columns excluded - HAVING is scope-restricted expression context) +``` + +#### 9c. After comparison operator + +**Show:** +- Literal keywords: `NULL`, `TRUE`, `FALSE` +- Aggregate functions +- Columns +- Numbers (user types directly) + +**Examples:** +```sql +SELECT status, COUNT(*) FROM users GROUP BY status HAVING COUNT(*) > | +→ NULL, TRUE, FALSE +→ COUNT, SUM, AVG, ... +→ (user can type number) +``` + +#### 9d. After complete expression (logical operators) + +**Trigger:** After a complete condition/expression followed by space + +**Show:** +- Logical keywords: `AND`, `OR`, `NOT`, `EXISTS` +- Other keywords: `ORDER BY`, `LIMIT` + +**Examples:** +```sql +SELECT status, COUNT(*) FROM users GROUP BY status HAVING COUNT(*) > 10 | +→ AND, OR, ORDER BY, LIMIT + +SELECT status, COUNT(*) FROM users GROUP BY status HAVING SUM(total) > 1000 | +→ AND, OR, NOT, ORDER BY, LIMIT +``` + +--- + +### 10. LIMIT_OFFSET_CLAUSE (After LIMIT or OFFSET) + +**Trigger:** After `LIMIT` or `OFFSET` + +**Show:** +- Nothing (user types number directly) + +**Examples:** +```sql +SELECT * FROM users LIMIT | +→ (no suggestions - user types number) + +SELECT * FROM users LIMIT 10 OFFSET | +→ (no suggestions - user types number) +``` + +**Note:** No autocomplete suggestions in this context. User types numeric values freely. This avoids noise and keeps the implementation simple. + +--- + +## Ordering Rules + +Suggestions are always ordered by priority: + +**Ordering Rules apply after applying scope restrictions.** + +**CURRENT_TABLE group inclusion is context-dependent:** +- **Expression contexts (JOIN_ON, WHERE, ORDER_BY, GROUP_BY, HAVING):** CURRENT_TABLE group MUST be omitted unless `CURRENT_TABLE` is in scope +- **SELECT_LIST without scope tables:** CURRENT_TABLE group MUST be included (if set) +- **SELECT_LIST with scope tables:** CURRENT_TABLE group MUST be included ONLY if `CURRENT_TABLE` is in scope +- **Table-selection contexts (FROM_CLAUSE, JOIN_CLAUSE):** Not applicable (these suggest tables, not columns) + +**Column display format:** +- **Single table in scope:** Use unqualified column names (e.g., `id`, `name`) +- **Multiple tables in scope:** Use `alias.column` when the table has an alias in the current statement; otherwise use `table.column` +- **Exception:** Dot-completion always returns unqualified column names (see **Dot-Completion** section) + +**Exception:** In HAVING clause context, aggregate functions are prioritized before columns (see section 9a, 9b for details). This is the only context where functions appear before columns. + +**Column ordering reminder:** See "Important Note on Column Ordering in Examples" section at the beginning of this document. All columns preserve schema order (ordinal_position), NOT alphabetical order. + +1. **Columns from CURRENT_TABLE** (if set in context, e.g., table editor) + - **Single table in scope:** Unqualified (e.g., `id`, `name`) + - **Multiple tables in scope:** Use `alias.column` format if the table has an alias in the current query, otherwise `table.column` + - Columns preserve their definition order (ordinal position in the table schema). They must NOT be reordered alphabetically. + +2. **Columns from tables in FROM clause** (if any) + - Includes: derived tables (subquery aliases), CTEs, physical tables + - Priority within FROM: derived tables > CTEs > physical tables (see **Tables in Scope Definition** section) + - **Single table in scope:** Unqualified (e.g., `id`, `name`) + - **Multiple tables in scope:** Use `alias.column` format if the table has an alias, otherwise `table.column` + - Columns preserve their definition order (ordinal position in the table schema). They must NOT be reordered alphabetically. + - When multiple FROM tables exist, follow their appearance order in the query; within each table, preserve column definition order. + +3. **Columns from tables in JOIN clause** (if any) + - Includes: derived tables (subquery aliases), CTEs, physical tables + - Priority within JOIN: derived tables > CTEs > physical tables (see **Tables in Scope Definition** section) + - **Multiple tables in scope:** Use `alias.column` format if the table has an alias, otherwise `table.column` + - Columns preserve their definition order (ordinal position in the table schema). They must NOT be reordered alphabetically. + - When multiple JOIN tables exist, follow their appearance order in the query; within each table, preserve column definition order. + - **Note:** JOIN clause always implies multiple tables in scope (FROM + JOIN), so columns are always qualified + +4. **All table.column from database** (all other tables not in FROM/JOIN) + - **CRITICAL: Group 4 eligibility is context-dependent:** + - ✅ **Eligible in SELECT_LIST when NO scope tables exist** (and only with prefix - guardrail against noise) + - ❌ **NOT eligible in SELECT_LIST when scope tables exist** (scope restriction active) + - ❌ **NOT eligible in scope-restricted expression contexts** (WHERE, JOIN_ON, ORDER_BY, GROUP_BY, HAVING) + - ❌ **NOT applicable in table-selection contexts** (FROM_CLAUSE, JOIN_CLAUSE suggest tables, not columns) + - Always use `table.column` format (no aliases for tables not in query) + - Columns preserve their definition order (ordinal position in the table schema). They must NOT be reordered alphabetically. + - Database-wide tables follow a deterministic stable order (schema order or internal stable ordering); within each table, preserve column definition order. + - **Performance guardrail (applies ONLY to this group when eligible):** If no prefix and total suggestions exceed threshold (400 items), skip this group to avoid lag in large databases + - **No prefix definition:** prefix is `None` OR empty string after trimming whitespace + - The cap applies only to group 4 (DB-wide columns). Groups 1-3 (CURRENT_TABLE, FROM, JOIN) are always included in full (already loaded/scoped). + - With prefix: always include this group when eligible (filtered results are manageable) + +5. **Functions** + - Alphabetically within this group + +6. **Out-of-Scope Table Hints** (SELECT_LIST with scope only) + - Format: `table_name (+ Add via FROM or JOIN)` + - Only when prefix matches DB-wide tables but no scope tables/columns + - See **Out-of-Scope Table Hints** section for details + +7. **Keywords** + - Alphabetically within this group + +--- + +### Alias Prefix Disambiguation + +**Applies in expression contexts** (WHERE, ON, ORDER BY, GROUP BY, HAVING, SELECT_LIST) + +**Note:** In SELECT_LIST, alias-prefix disambiguation applies only when FROM/JOIN tables are available in the current statement. Without FROM/JOIN, SELECT_LIST shows functions + keywords (see section 3a). + +**Note:** In ORDER BY / GROUP BY, exact alias match still activates alias-prefix mode (same as other contexts). However, this is most relevant when the prefix is immediately followed by `.` (dot-completion) or when the typed token exactly equals an alias. Otherwise generic prefix matching applies (column names are common in these contexts). + +**Critical: Exact Match Rule** + +Alias-prefix mode activates **only if the token exactly equals an alias** (not startswith). This avoids ambiguity with multiple aliases. + +**Rule:** +- `token == alias` → alias-prefix mode ✅ +- `token.startswith(alias)` → generic prefix mode ❌ + +**Why exact match?** +- Avoids ambiguity with multiple aliases (e.g., `u` and `us`) +- Prevents false positives (e.g., `user|` should not trigger alias `u`) + +**Behavior:** +- If prefix **exactly equals** an alias: show only that alias' columns first (e.g., `u.id`, `u.name`) +- If prefix does NOT exactly match an alias: treat as generic prefix (match table name, column name, or function name) + +**Deduplication in alias-exact-match mode:** +- When alias-exact-match mode is active, do NOT also emit the same columns qualified with the base table name +- Deduplicate by underlying column identity (e.g., if showing `u.id`, do not also show `users.id`) +- This avoids redundancy and keeps suggestions clean + +**Interaction with CURRENT_TABLE:** +- In alias-prefix mode, CURRENT_TABLE priority is ignored; alias columns are always ranked first +- This avoids unexpected behavior in table editor when using aliases + +**Examples:** + +**Exact match - Alias-prefix mode:** +```sql +SELECT * FROM users u JOIN orders o WHERE u| +→ token = "u" +→ alias "u" exists → exact match ✅ +→ u.id, u.name, u.email, ... (alias 'u' columns prioritized) +→ UPPER, UUID (functions matching 'u') +→ (do not show users.id, users.name - avoid redundancy) +``` + +**No exact match - Generic prefix mode:** +```sql +SELECT * FROM users u JOIN orders o WHERE us| +→ token = "us" +→ aliases: u, o +→ "us" != "u" and "us" != "o" → no exact match ❌ +→ users.id, users.name, users.email, users.password, users.is_enabled, users.created_at, ... (table starts with 'us') +→ (generic prefix matching) + +SELECT * FROM users u WHERE user| +→ token = "user" +→ alias "u" exists but "user" != "u" → no exact match ❌ +→ Columns: (none - single table, no columns start with 'user') (single table: unqualified) +→ Functions: (none starting with 'user') +→ (generic prefix matching, NOT alias-prefix) +→ (DB-wide columns excluded - WHERE is scope-restricted) + +SELECT * FROM users u WHERE up| +→ token = "up" +→ alias "u" exists but "up" != "u" → no exact match ❌ +→ UPPER (function starts with 'up') +→ (generic prefix matching - user is typing a function, NOT using the alias) +``` + +**No alias in query - Generic prefix mode:** +```sql +SELECT * FROM users JOIN orders ON u| +→ token = "u" +→ Context: JOIN_ON (scope-restricted) +→ no aliases defined → generic prefix +→ users.id, users.name, users.email, users.password, users.is_enabled, users.created_at (multiple tables: qualified) +→ orders.user_id (scope table column starts with 'u') +→ UPPER, UUID (functions start with 'u') +→ (Database-wide columns excluded - scope restriction active) +``` + +**Note:** This rule applies only to tokens without dot. `u.|` triggers Dot-Completion, not alias-prefix disambiguation. + +--- + +### Generic Prefix Matching for Column Contexts + +**Applies to all column-expression contexts:** SELECT_LIST, WHERE_CLAUSE, JOIN_ON, ORDER_BY, GROUP_BY, HAVING, and any additional expression contexts where columns can be inserted. + +**When NOT in dot-completion and NOT in alias-exact-match mode:** + +Given a prefix P (token immediately before cursor, without '.'): + +**Return column suggestions that include BOTH:** + +**B) Column-name match:** +- For EVERY column C (from all tables in scope and all other database tables) whose column name startswith(P), return it as a column suggestion +- **Qualification:** + - **Single table in scope:** Unqualified (e.g., `name`) + - **Multiple tables in scope:** Qualified with `alias.column` if table has alias, otherwise `table.column` + +**A) Table-name match expansion:** +- For EVERY table T whose name startswith(P), return ALL columns of T as column suggestions +- **Qualification:** Always qualified with `alias.column` if table has alias, otherwise `table.column` (even for single table) +- **Rationale:** Qualified names indicate the match is from table name, helping users discover dot-completion + +**Order when BOTH B and A match (prefix matches both column names AND table name):** +1. **Column-name matches unqualified** (rule B, single table only) +2. **Column-name matches qualified** (same columns as #1, but qualified) +3. **Table-name expansion remaining columns qualified** (rule A, excluding columns already shown in #1 and #2) +4. **Functions** + +**Order when ONLY B matches (prefix matches column names but NOT table name):** +1. **Column-name matches** (unqualified if single table, qualified if multiple tables) +2. **Functions** + +**Order when ONLY A matches (prefix matches table name but NOT column names):** +1. **Table-name expansion all columns qualified** (rule A) +2. **Functions** + +**Scope restriction:** + +**Scope-restricted expression contexts (WHERE, JOIN_ON, ORDER_BY, GROUP_BY, HAVING):** + +**Hard line:** In scope-restricted expression contexts, both table-name expansion and column-name matching MUST be computed over scope tables only. + +See **Scope-Restricted Expression Contexts** section for complete rules. + +**SELECT_LIST without scope tables:** +- `CURRENT_TABLE` columns MUST be included first (if set) +- Database-wide table-name expansion and column-name matching are included **ONLY when prefix exists** +- **CRITICAL: When no prefix exists, DB-wide columns MUST NOT be shown (guardrail against noise)** +- **With prefix matching order:** + 1. **CURRENT_TABLE table-name expansion** (if CURRENT_TABLE name matches prefix) + 2. **Other DB-wide table-name expansions** (tables whose names match prefix) + 3. **Column-name matching from all DB tables** (columns whose names match prefix) + 4. **Functions** + +**SELECT_LIST with scope tables:** +- `CURRENT_TABLE` columns MUST be included ONLY if `CURRENT_TABLE` is in scope +- Database-wide columns MUST NOT be suggested (regardless of prefix) +- Scope table columns are included with alias-first qualification +- **Exception:** If prefix matches DB-wide tables but no scope tables/columns, suggest Out-of-Scope Table Hints instead + +**Important rules:** +- Do NOT suggest bare table names in column-expression contexts; only columns (qualified) + - **Exception:** Out-of-Scope Table Hints (see **Out-of-Scope Table Hints** section) are a special suggestion kind +- Deduplicate identical suggestions (if a column appears via both A and B, show it once) +- Apply global Ordering Rules (CURRENT_TABLE > FROM > JOIN > DB > FUNCTIONS > TABLE_HINTS > KEYWORDS) + - **Note:** DB group only applies when no scope exists; TABLE_HINTS only in SELECT_LIST with scope +- Performance guardrail: see Ordering Rules group 4 (applies only to DB-wide columns when no prefix) + +**Examples:** + +**SELECT_LIST without scope, with CURRENT_TABLE and prefix:** +```sql +-- Assume CURRENT_TABLE = users, prefix = "u" +SELECT u| +→ Prefix: "u" +→ Context: SELECT_LIST (no scope → DB-wide columns allowed with prefix) +→ CURRENT_TABLE table-name expansion: users.id, users.name, users.email, users.status, users.created_at (FIRST) +→ Other table-name expansion: user_sessions.id, user_sessions.user_id, user_sessions.session_token, user_sessions.expires_at (SECOND) +→ Column-name matching: orders.user_id, products.unit_price (THIRD) +→ Functions: UNIX_TIMESTAMP, UPPER, UUID (FOURTH) +``` + +**SELECT_LIST with scope tables (database-wide columns excluded):** +```sql +SELECT u| FROM orders +→ Prefix: "u" +→ Context: SELECT_LIST (scope exists → database-wide columns excluded) +→ Scope column-name matching: orders.user_id (scope table column starts with 'u') +→ Functions: UPPER, UUID, UNIX_TIMESTAMP +→ Out-of-Scope Table Hints: users + Add via FROM/JOIN (prefix matches DB table but not in scope) +→ Combined: orders.user_id, UPPER, UUID, UNIX_TIMESTAMP, users (hint) +``` + +**WHERE with scope tables (database-wide columns excluded):** +```sql +SELECT * FROM users u WHERE us| +→ Prefix: "us" +→ Context: WHERE (scope tables exist → database-wide columns disabled) +→ Alias "u" exists but "us" != "u" → NOT alias-exact-match mode +→ Table-name expansion: users table starts with 'us' → u.id, u.name, u.email, u.password, u.is_enabled, u.created_at (uses alias) +→ Column-name matching: restricted to scope tables only (none in this example) +→ Combined: u.id, u.name, u.email, u.password, u.is_enabled, u.created_at +``` + +**Deduplication example:** +```sql +SELECT u| FROM users +→ Table-name expansion: users.* (all columns) +→ Column-name matching: users.updated_at (if such column exists and starts with 'u') +→ Deduplication: users.updated_at appears in both → show once +``` + +**Applies to all column contexts:** +- SELECT_LIST: `SELECT u|` or `SELECT u| FROM users` +- WHERE: `SELECT * FROM users WHERE u|` +- JOIN ON: `SELECT * FROM users u JOIN orders o ON u.id = o.u|` +- ORDER BY: `SELECT * FROM users ORDER BY u|` +- GROUP BY: `SELECT * FROM users GROUP BY u|` +- HAVING: `SELECT status, COUNT(*) FROM users GROUP BY status HAVING u|` + +**Example - Alias-prefix overrides CURRENT_TABLE:** +```sql +-- CURRENT_TABLE = users (in table editor) +SELECT * FROM users u JOIN orders o WHERE u| +→ token = "u" +→ exact match with alias "u" → alias-prefix mode ✅ +→ u.id, u.name, u.email, ... (alias columns first) +→ CURRENT_TABLE priority ignored in this case +``` + +**Example in table editor context (CURRENT_TABLE = users, no alias):** +```sql +SELECT u| +→ Context: SELECT_LIST without scope (no FROM/JOIN) +→ Prefix: "u" +→ users.id (CURRENT_TABLE column starting with 'u') +→ users.name (CURRENT_TABLE column - shown for context) +→ orders.user_id (DB-wide column starting with 'u' - allowed because no scope AND prefix exists) +→ products.unit_price (DB-wide column starting with 'u') +→ UPPER (function starting with 'u') +→ UUID (function starting with 'u') +→ UPDATE (keyword starting with 'u') +``` + +**Example in multi-query context (CURRENT_TABLE = users, second query has no scope):** +```sql +SELECT * FROM users u WHERE id = 1; SELECT u| +→ Context: SELECT_LIST without scope (second query has no FROM/JOIN) +→ Prefix: "u" +→ users.id (CURRENT_TABLE column starting with 'u') +→ users.name (CURRENT_TABLE column - shown for context) +→ orders.user_id (DB-wide column starting with 'u' - allowed because no scope AND prefix exists) +→ products.unit_price (DB-wide column starting with 'u') +→ UPPER (function starting with 'u') +→ UUID (function starting with 'u') +→ UPDATE (keyword starting with 'u') +``` + +**Example in query with FROM:** +```sql +SELECT * FROM users WHERE u| +→ Columns: (none - no columns start with 'u') (single table: unqualified) +→ UPPER (function starting with 'u') +→ UPDATE (keyword starting with 'u') +→ (DB-wide columns excluded - WHERE is scope-restricted expression context) +``` + +**Example in query with JOIN (alias-exact-match mode):** +```sql +SELECT * FROM users u JOIN orders o WHERE u| +→ Context: WHERE (scope-restricted) +→ Prefix: "u" +→ Alias-exact-match mode (u == alias 'u') +→ u.id, u.name, u.email, u.password, u.is_enabled, u.created_at (all columns from alias 'u', multiple tables: qualified) +→ UPPER, UUID, UNIX_TIMESTAMP (functions starting with 'u') +→ (DB-wide columns excluded - WHERE is scope-restricted expression context) +→ UPDATE (KEYWORD) +``` + +--- + +### Out-of-Scope Table Hints (SELECT_LIST with Scope) + +**Applies ONLY in SELECT_LIST when scope tables already exist (FROM/JOIN present).** + +**Purpose:** Keep SELECT scope-safe (no DB-wide columns), while still allowing controlled table discovery. + +--- + +#### Trigger Conditions + +In SELECT_LIST with scope tables: + +If prefix P satisfies ALL of: +- No alias-exact-match +- No scope table startswith(P) +- No scope column startswith(P) +- BUT one or more physical tables in the database startswith(P) + +Then: +- DO NOT suggest DB-wide columns +- Instead, suggest each matching table as an individual hint item + +--- + +#### Suggestion Format + +Each table is a separate suggestion item: + +``` +users + Add via FROM/JOIN +customers + Add via FROM/JOIN +``` + +--- + +#### Behavior Rules + +- Each table is a separate suggestion item +- Suggestion kind: `TABLE_HINT_OUT_OF_SCOPE` +- No column suggestions for out-of-scope tables +- Selecting this item MUST NOT auto-insert JOIN type +- **Minimal v1 behavior:** + - Either insert just the table name + - Or act as a non-insert hint (implementation choice) +- JOIN type (INNER/LEFT/RIGHT) remains user decision +- **No badges:** Badges are reserved for column data types (INT, VARCHAR, etc.) + +--- + +#### Ordering (within SELECT_LIST with scope) + +1. Functions matching prefix +2. Scope columns matching prefix +3. Out-of-scope table hints +4. Keywords + +**Important:** Functions MUST appear before table hints. + +**Rationale:** When typing `SELECT c| FROM orders`, the user most likely intends `COUNT, COALESCE, CONCAT`, not `customers` (new table). Therefore, functions are prioritized over discovery hints. + +--- + +#### Example + +**Assume:** +- Scope = [orders] +- Database tables = [orders, users, customers] + +**Query:** +```sql +SELECT u| FROM orders +``` + +**Suggestions:** +``` +→ UPPER +→ UUID +→ users + Add via FROM/JOIN +→ UPDATE +``` + +**NOT suggested:** +``` +❌ users.id +❌ customers.name +❌ any DB-wide columns +``` + +--- + +#### Important Constraints + +- Applies ONLY to SELECT_LIST with existing scope +- Does NOT apply to WHERE, JOIN_ON, GROUP_BY, HAVING, ORDER_BY +- Does NOT apply when no scope exists (normal DB-wide allowed case) +- Dot-completion behavior remains unchanged +- Badges are reserved for column data types (INT, VARCHAR, etc.) + +--- + +## Context Rules Summary Matrix + +**In case of ambiguity, detailed context sections override this summary matrix.** + +This table provides a quick reference for implementers to understand the behavior of each context. + +| Context | Scope Required | DB-wide Columns | CURRENT_TABLE | Table Hints | +|---------|---------------|-----------------|---------------|-------------| +| **SELECT_LIST (no scope)** | No | Only with prefix | Yes (if set) | No | +| **SELECT_LIST (with scope)** | Yes | No | Only if in scope | Yes (if prefix matches) | +| **FROM_CLAUSE** | Scope building | N/A | Yes (if set, not present) | N/A | +| **JOIN_CLAUSE** | Scope extension | N/A | Yes (if set, not present) | N/A | +| **JOIN_ON** | Yes | No | Only if in scope | No | +| **WHERE** | Yes | No | Only if in scope | No | +| **ORDER_BY** | Yes | No | Only if in scope | No | +| **GROUP_BY** | Yes | No | Only if in scope | No | +| **HAVING** | Yes | No | Only if in scope | No | + +**Legend:** +- **Scope Required:** Whether the context requires scope tables to exist +- **DB-wide Columns:** Whether columns from tables outside scope can be suggested +- **CURRENT_TABLE:** Whether CURRENT_TABLE columns can be suggested +- **Table Hints:** Whether out-of-scope table hints can be suggested + +**Notes:** +- SELECT_LIST without scope: DB-wide columns included only when prefix exists (guardrail against noise) +- SELECT_LIST with scope: DB-wide columns excluded; Out-of-Scope Table Hints shown when prefix matches DB tables but no scope tables/columns +- FROM_CLAUSE and JOIN_CLAUSE are table-selection contexts (scope building/extension), not column contexts +- All scope-restricted expression contexts (JOIN_ON, WHERE, ORDER_BY, GROUP_BY, HAVING) follow the same rules (see **Scope-Restricted Expression Contexts** section) +- Performance guardrail applies only to DB-wide columns group when no prefix (see Ordering Rules group 4) + +--- + +## Implementation Notes + +- Context detection uses `sqlglot.parse_one()` with `ErrorLevel.IGNORE` for incomplete SQL +- Dialect is retrieved from `CURRENT_CONNECTION.get_value().engine.value.dialect` +- `CURRENT_TABLE` is an observable: `CURRENT_TABLE.get_value() -> Optional[SQLTable]` + - Used to prioritize columns from the current table when set + - Can be `None` if no table is currently selected +- Fallback to regex-based context detection if sqlglot parsing fails + +--- + +### Architecture Notes + +**Critical:** Centralize resolution logic to avoid duplication, but distinguish between table-selection and expression contexts. + +**Two distinct resolution functions are needed:** + +#### 1. Table Selection (FROM_CLAUSE, JOIN_CLAUSE) + +```python +def resolve_tables_for_table_selection( + context: SQLContext, + scope: QueryScope, + current_table: Optional[SQLTable] = None, + prefix: Optional[str] = None +) -> List[TableSuggestion]: + """ + Resolve table candidates for FROM/JOIN clauses. + + Returns tables in priority order: + 1. CTE names (if available from WITH clause) + 2. Physical tables from database + 3. CURRENT_TABLE (if set and not already present in current statement) - convenience shortcut + + Filtering: + - If prefix provided, filter by startswith(prefix) + - Exclude tables already present in the current statement (query separated by separator) + + Note: This is table-selection, not column resolution. + CURRENT_TABLE can appear even if scope tables already exist. + """ + pass +``` + +#### 2. Expression Contexts (SELECT_LIST, WHERE, JOIN_ON, ORDER_BY, GROUP_BY, HAVING) + +```python +def resolve_columns_for_expression( + context: SQLContext, + scope: QueryScope, + current_table: Optional[SQLTable] = None, + prefix: Optional[str] = None +) -> List[ColumnSuggestion]: + """ + Resolve columns for expression contexts with scope-aware restrictions. + + Behavior depends on context and scope: + + SCOPE-RESTRICTED contexts (WHERE, JOIN_ON, HAVING, ORDER_BY, GROUP_BY): + - See Scope-Restricted Expression Contexts section for complete rules + - Priority: FROM tables > JOIN tables + + SELECT_LIST context: + - If NO scope tables: + * Include CURRENT_TABLE columns (if set) + * Include database-wide columns (only with prefix - guardrail against noise) + - If scope tables exist: + * CURRENT_TABLE included only if in scope; otherwise ignored + * Include scope table columns + * Database-wide columns EXCLUDED (scope restriction active) + * Exception: Out-of-Scope Table Hints if prefix matches DB tables but no scope columns + + All columns use alias.column format when alias exists, otherwise table.column. + """ + pass +``` + +**Benefits:** +- Clear separation between table-selection and expression contexts +- Enforces scope restriction rules consistently +- Single source of truth for each context type +- Easier to test and maintain +- Avoids logic duplication + +**Architectural improvement (optional):** + +For cleaner architecture, consider using a `QueryScope` object instead of passing multiple parameters: + +```python +@dataclass +class QueryScope: + from_tables: List[TableReference] + join_tables: List[TableReference] + derived_tables: List[DerivedTable] + ctes: List[CTE] + current_table: Optional[SQLTable] + aliases: Dict[str, TableReference] # alias -> table mapping + +def resolve_columns_in_scope( + scope: QueryScope, + prefix: Optional[str] = None +) -> List[ColumnSuggestion]: + """Pure function - no global context dependency.""" + pass +``` + +This makes the function pure and easier to test. + +--- + +**Tables in Scope Definition (with CTEs and Derived Tables):** + +With CTEs and subquery aliases, "tables in scope" is not just physical tables from FROM/JOIN. The priority order is: + +``` +tables_in_scope = [ + 1. Derived tables (subquery alias) in FROM/JOIN + 2. CTEs referenced in FROM/JOIN + 3. Physical tables in FROM/JOIN +] +``` + +**Column resolution follows this order:** + +**Important:** Include CURRENT_TABLE columns only when allowed by context rules: +- SELECT_LIST with no scope tables, OR +- CURRENT_TABLE is in scope (present in FROM/JOIN) + +Otherwise omit CURRENT_TABLE entirely. + +1. **CURRENT_TABLE columns** (if allowed by context rules) - use alias if table has alias in query +2. **Derived table columns** - use alias (only sensible name) +3. **CTE columns** - use CTE name (acts as alias) +4. **Physical table columns from FROM** - use alias if defined +5. **Physical table columns from JOIN** - use alias if defined +6. **Database columns** (all other tables, with guardrail - only in SELECT_LIST or when no scope restriction) + +**Example:** +```sql +WITH active_users AS (SELECT id, name FROM users WHERE status = 'active') +SELECT * FROM (SELECT id, total FROM orders) AS o +JOIN active_users au ON o.id = au.id +WHERE | +→ o.id, o.total (derived table, priority 2) +→ au.id, au.name (CTE, priority 3) +→ (no physical tables in this query) +``` + +**Note:** Alias-first is fundamental here - for derived tables and CTEs, the alias/CTE name is often the **only** sensible name (no underlying physical table name). + +### Scope Handling (Subqueries and CTEs) + +**Important:** Scope handling for subqueries and CTEs. + +**v1 subquery scope resolution:** +- **v1 supports inner scope when cursor is inside parentheses of a subquery** (sqlglot typically handles this correctly) +- Fallback to outer scope only when parsing/cursor mapping fails +- When subquery has FROM clause, suggest columns from subquery scope (inner scope) +- When cursor is outside subquery parentheses, suggest columns from outer scope + +**Examples:** +```sql +-- Cursor outside subquery: outer query scope +SELECT * FROM users WHERE id IN (SELECT user_id FROM orders) AND | +→ Suggests columns from 'users' (outer query scope) + +-- Cursor inside subquery: inner query scope (v1 supported) +SELECT * FROM users WHERE id IN (SELECT | FROM orders) +→ Suggests columns from 'orders' (inner query scope - subquery has FROM) + +-- Cursor inside subquery WHERE: inner query scope (v1 supported) +SELECT * FROM users WHERE id IN (SELECT user_id FROM orders WHERE |) +→ Suggests columns from 'orders' (inner query scope) +``` + +**CTE Support (WITH clauses):** + +CTEs are increasingly common and should be considered for v1 implementation: + +```sql +WITH active_users AS (SELECT * FROM users WHERE status = 'active') +SELECT * FROM active_users WHERE | +→ active_users.id, active_users.name, ... (CTE columns) +``` + +**Basic CTE support:** +- Treat CTEs as available tables in the query scope +- CTE name acts like a table name in FROM/JOIN contexts +- Columns from CTE should be resolved (if CTE definition is parseable) +- **CTE visibility is limited to the statement where they are defined (standard SQL behavior)** + +**CTE scope and visibility:** + +CTEs follow **standard SQL semantics**: a CTE is visible only within the statement where it is defined. This is the behavior mandated by the SQL standard and implemented by all major databases. + +**Example - CTE scope (standard SQL):** +```sql +WITH a AS (...) +SELECT * FROM a; +SELECT * FROM | +→ CTE 'a' is NOT visible here (different statement, separated by `;`) +→ Show only physical tables +``` + +**Vendor-specific extensions:** + +Some databases offer non-standard CTE extensions (e.g., session-scoped CTEs, persistent CTEs). These are **NOT supported in v1**. The autocomplete system follows standard SQL semantics only. + +**Advanced CTE features (future enhancement):** +- Recursive CTEs (standard SQL, but complex parsing) +- Multiple CTEs with dependencies (standard SQL) +- CTE column aliasing (standard SQL) + +**Subquery Aliases (Derived Tables):** + +Subqueries with aliases (derived tables) should also be considered for v1: + +```sql +SELECT * FROM (SELECT id, name FROM users) AS u WHERE | +→ u.id, u.name (derived table columns) +``` + +**Basic support:** +- Treat aliased subquery as a table in scope +- Resolve columns from the subquery SELECT list (if parseable) +- Alias acts like a table name + +**Note:** This is similar to CTE support but inline. If CTEs are supported, derived tables should follow the same pattern. + +**Window Functions (Future Enhancement):** + +Window functions with OVER clause are common in modern SQL: + +```sql +SELECT *, ROW_NUMBER() OVER | +→ (PARTITION BY, ORDER BY) + +SELECT *, ROW_NUMBER() OVER (PARTITION BY | +→ Columns in scope (qualified, alias-first) + +SELECT *, ROW_NUMBER() OVER (PARTITION BY status ORDER BY | +→ Columns in scope (qualified, alias-first) +``` + +**Future support:** +- Detect OVER clause context +- After `OVER (` suggest keywords: `PARTITION BY`, `ORDER BY` +- After `PARTITION BY` suggest columns in scope +- After `ORDER BY` suggest columns in scope + `ASC`, `DESC` + +**Note:** This is a specialized context that can be added after core functionality is stable. + +--- + +### Potential Challenges + +**sqlglot Parsing of Incomplete SQL:** + +Test thoroughly with partial queries. You might need a hybrid approach that falls back to regex faster than expected. + +**Examples of challenging cases:** +```sql +SELECT id, name FROM users WHERE | +→ sqlglot may parse successfully + +SELECT id, name FROM users WH| +→ sqlglot may fail, need regex fallback + +SELECT * FROM users WHERE status = '| +→ Incomplete string, sqlglot may fail +``` + +**Recommendation:** +- Use `sqlglot.parse_one()` with `ErrorLevel.IGNORE` as primary approach +- Implement robust regex fallback for common patterns +- Test with many incomplete query variations +- Log parsing failures to identify patterns that need special handling + +**Fallback trigger rule:** +- If sqlglot does not produce a useful AST → fallback to regex +- If cursor position cannot be mapped to an AST node → fallback to regex +- Log: `(dialect, snippet_around_cursor, reason)` for building golden test cases + +**Example logging:** +```python +if not ast or not can_map_cursor_to_node(ast, cursor_pos): + logger.debug( + "sqlglot_fallback", + dialect=dialect, + snippet=text[max(0, cursor_pos-50):cursor_pos+50], + reason="no_useful_ast" if not ast else "cursor_mapping_failed" + ) + return regex_based_context_detection(text, cursor_pos) +``` + +**Benefit:** Build real-world golden tests from production edge cases + +**Cursor Position Context:** + +Make sure context detection knows exactly where the cursor is, not just what's before it. + +**Critical distinction:** +```sql +SELECT | FROM users +→ Context: SELECT_LIST (before FROM) +→ Show: columns, functions + +SELECT id| FROM users +→ Context: After column name (before FROM) +→ Show: FROM, AS, etc. (comma is never suggested) +``` + +**Implementation note:** +- Extract text before cursor: `text[:cursor_pos]` +- Extract text after cursor: `text[cursor_pos:]` (for context validation) +- Check if cursor is immediately after a complete token vs in the middle +- Use both left and right context for accurate detection + +--- + +### Performance Optimization + +**Large Schemas:** + +The 400-item guardrail is good, but additional optimizations are recommended: + +**Debouncing:** +- Delay autocomplete trigger by 150-300ms after last keystroke +- Avoids excessive computation while user is typing rapidly +- Cancel pending autocomplete requests if new input arrives + +**Caching:** +- Cache database schema (tables, columns) in memory +- Refresh only when schema changes (DDL operations detected) +- Cache parsed query structure for current statement +- Invalidate cache when query changes significantly + +**Schema cache invalidation triggers:** +- DDL operations: `CREATE`, `ALTER`, `DROP`, `TRUNCATE` +- Database/schema change (e.g., `USE database`) +- Manual refresh (user-triggered) +- Reconnection to database +- **Best-effort approach:** Some engines (e.g., PostgreSQL) support event listeners for schema changes; if not available, invalidate on DDL keyword detection or periodic refresh + +**Lazy Loading:** +- Load column details only when needed (not all upfront) +- For large tables (>100 columns), load columns on-demand +- Consider pagination for very large suggestion lists + +**Example implementation:** +```python +class AutocompleteCache: + def __init__(self): + self._schema_cache = {} # {database: {table: [columns]}} + self._last_query_hash = None + self._parsed_query_cache = None + + def get_columns(self, table: str) -> List[Column]: + if table not in self._schema_cache: + self._schema_cache[table] = fetch_columns(table) + return self._schema_cache[table] + + def invalidate_schema(self): + self._schema_cache.clear() +``` + +--- + +### Statement Separator + +The statement separator is NOT hardcoded. + +It is determined at runtime using: + +``` +effective_separator = user_override or engine_default +``` + +**Separator types:** + +1. **Single-character separators** (most engines): + - MySQL, MariaDB, PostgreSQL, SQLite: `";"` + - User can override with any single character + +2. **Multi-character token separators** (SQL Server and similar): + - SQL Server: `"GO"` (case-insensitive keyword) + - Must be a standalone token (word boundary required) + - Example: `SELECT * FROM users GO SELECT * FROM orders` (valid) + - Example: `SELECT * FROM users_GO SELECT * FROM orders` (invalid - no word boundary) + +**Validation:** + +1. **Trim whitespace** from `user_override` before validation +2. **Single-character override:** If length == 1 after trimming → valid +3. **Multi-character override:** If length >= 2 after trimming: + - **Valid:** ONLY if alphanumeric + underscore `[A-Za-z0-9_]+` + - **Invalid:** If contains symbols (e.g., `//`, `$$`, `GO;`) → reject, fallback to `engine_default` +4. **Invalid override:** If validation fails → ignore override and fallback to `engine_default` + +**Valid override examples:** +- `"GO"` → valid (alphanumeric) +- `"BEGIN"` → valid (alphanumeric) +- `";"` → valid (single char) +- `"END_BLOCK"` → valid (alphanumeric + underscore) + +**Invalid override examples (rejected, use engine_default):** +- `"//"` → invalid (symbols only) +- `"GO;"` → invalid (mixed alphanumeric + symbol) +- `"$$"` → invalid (symbols only) +- `"GO "` → invalid after trim (contains space before trim) + +**Implementation notes:** + +- **Single-character separators:** Simple string split with string/comment awareness +- **Multi-character token separators:** Require word boundary detection using regex `\b{separator}\b` + - Example: `\bGO\b` for SQL Server + - Word boundary `\b` works correctly because multi-char separators are restricted to `[A-Za-z0-9_]+` +- **Both types MUST respect:** + - String literals: `'...'`, `"..."`, `` `...` `` + - Comments: `-- ...`, `/* ... */` + - Dollar-quoted strings (PostgreSQL): `$$...$$`, `$tag$...$tag$` + +All multi-query splitting logic MUST use the effective separator. +Hardcoding `";"` is forbidden. + +--- + +### Multi-Query Support + +**Important:** When multiple queries are present in the editor (separated by the effective statement separator), context detection must operate on the **current query** (where the cursor is), not the entire buffer. + +**Implementation approach:** +1. Find statement boundaries by detecting the effective separator +2. Extract the query containing the cursor position +3. Run context detection only on that query + +**Edge cases:** +- If cursor is on the separator, treat it as "end of previous statement". +- Empty statements are ignored (no context); fallback to EMPTY. +- For token separators (e.g., GO), cursor on the token itself is treated as "end of previous statement" + +**Examples:** + +**Single-character separator (`;`):** +```sql +SELECT * FROM users WHERE id = 1; +SELECT * FROM orders WHERE | ← cursor here +SELECT * FROM products; +``` +Context detection should analyze only: `SELECT * FROM orders WHERE |` + +**Token separator (`GO` for SQL Server):** +```sql +SELECT * FROM users WHERE id = 1 +GO +SELECT * FROM orders WHERE | ← cursor here +GO +SELECT * FROM products +``` +Context detection should analyze only: `SELECT * FROM orders WHERE |` + +**Critical:** +- Do NOT use simple `text.split(effective_separator)` +- The separator must be ignored inside: + - Strings (`'...'`, `"..."`) + - Comments (`--`, `/* */`) + - Dollar-quoted strings (PostgreSQL: `$$...$$`) +- For token separators, word boundaries MUST be respected (e.g., `users_GO` is NOT a separator) + +**Recommended approach:** +- Use sqlglot lexer/tokenizer to find statement boundaries (handles strings/comments correctly) +- For token separators: use regex with word boundaries (e.g., `\bGO\b` case-insensitive) +- Or implement robust separator detection with string/comment awareness + +### Multi-Word Keywords + +Multi-word keywords (e.g., `ORDER BY`, `GROUP BY`, `IS NULL`, `IS NOT NULL`, `NULLS FIRST`) are suggested as a single completion item but inserted verbatim. + +**Matching rule:** Use `startswith()` on normalized text (single spaces, case-insensitive). Normalize both the user input and the keyword before matching. + +**Examples:** +- User types `ORDE|` → normalized input: `"orde"` → matches `ORDER BY` (normalized: `"order by"`) ✅ +- User types `ORDER B|` → normalized input: `"order b"` → matches `ORDER BY` (normalized: `"order by"`) ✅ +- User types `NULLS L|` → normalized input: `"nulls l"` → matches `NULLS LAST` (normalized: `"nulls last"`) ✅ +- User types `IS N|` → normalized input: `"is n"` → matches `IS NULL`, `IS NOT NULL` ✅ + +--- + +## Edge Cases + +This section documents problematic scenarios and their explicit resolutions. + +### 1. Cursor on Separator + +**Scenario:** Cursor is positioned exactly on the statement separator. + +```sql +SELECT * FROM users;| +SELECT * FROM orders; +``` + +**Resolution:** Treat as "end of previous statement". Context detection operates on the first statement (`SELECT * FROM users`), suggesting clause keywords like `WHERE`, `ORDER BY`, `LIMIT`. + +--- + +### 2. Empty Statement + +**Scenario:** Multiple separators with no content between them. + +```sql +SELECT * FROM users;;| +``` + +**Resolution:** Empty statements are ignored. Fallback to EMPTY context, suggesting primary keywords (`SELECT`, `INSERT`, `UPDATE`, etc.). + +--- + +### 3. Incomplete String Literal + +**Scenario:** Cursor inside an unclosed string. + +```sql +SELECT * FROM users WHERE name = '| +``` + +**Resolution:** sqlglot parsing may fail. Fallback to regex-based context detection. Suggest literal keywords (`NULL`, `TRUE`, `FALSE`) and allow user to complete the string. + +--- + +### 4. Separator Inside String/Comment + +**Scenario:** Statement separator appears inside a string or comment. + +```sql +SELECT * FROM users WHERE note = 'Price: $10; Discount: 20%' AND | +``` + +**Resolution:** The `;` inside the string MUST be ignored. Use sqlglot lexer/tokenizer or implement string/comment-aware separator detection. Context: WHERE clause. + +--- + +### 5. Ambiguous Alias with Multiple Matches + +**Scenario:** Prefix could match multiple aliases if using `startswith`. + +```sql +SELECT * FROM users u JOIN user_stats us WHERE u| +``` + +**Resolution:** Use **exact match** rule. `"u" == "u"` → alias-prefix mode for `u`. If user types `us|`, then `"us" == "us"` → alias-prefix mode for `us`. No ambiguity. + +--- + +### 6. CURRENT_TABLE Not in Scope + +**Scenario:** CURRENT_TABLE is set in UI, but not present in query scope. + +```sql +-- CURRENT_TABLE = users (in table editor) +SELECT * FROM orders WHERE | +``` + +**Resolution:** CURRENT_TABLE columns MUST NOT be suggested. Only `orders.*` columns are valid (scope restriction active). CURRENT_TABLE is ignored unless it appears in FROM/JOIN. + +--- + +### 7. Table Name Matches Column Name + +**Scenario:** Prefix matches both a table name and column names. + +```sql +SELECT u| FROM orders +``` + +**Resolution:** Context is SELECT_LIST with scope (orders in FROM). Apply scope-restricted prefix matching: +- Column-name matching: `orders.user_id` (scope table column starting with 'u') +- Functions: `UPPER`, `UUID`, `UNIX_TIMESTAMP` +- Out-of-Scope Table Hint: `users + Add via FROM/JOIN` (prefix matches DB table not in scope) +- ❌ DB-wide columns excluded (scope restriction active - no `users.*` expansion) + +--- + +### 8. Dot-Completion on Non-Existent Table + +**Scenario:** User types dot after a non-existent table/alias. + +```sql +SELECT * FROM users WHERE xyz.| +``` + +**Resolution:** `xyz` is not a valid table or alias in scope. Return empty suggestions. Optionally show error hint: "Table or alias 'xyz' not found". + +--- + +### 9. Multi-Query with Different Separators + +**Scenario:** User has overridden separator, but buffer contains old separator. + +```sql +-- effective_separator = "GO" +SELECT * FROM users; +GO +SELECT * FROM orders WHERE | +``` + +**Resolution:** Only the effective separator (`GO`) is recognized. The `;` is treated as part of the first statement (not a separator). Context detection operates on the second statement. + +--- + +### 10. CTE Referenced Before Definition + +**Scenario:** User references CTE before defining it (incomplete query). + +```sql +SELECT * FROM active_users WHERE | +WITH active_users AS (SELECT * FROM users WHERE status = 'active') +``` + +**Resolution:** sqlglot parsing may fail or produce incorrect AST. Fallback to regex-based context detection. CTE `active_users` is not recognized (defined after usage). Treat as physical table or show error hint. + +--- + +### 11. Nested Subquery Context + +**Scenario A:** Cursor in SELECT list of subquery (subquery has FROM). + +```sql +SELECT * FROM users WHERE id IN (SELECT | FROM orders) +``` + +**Resolution:** **v1 supported**. Context detection recognizes the subquery scope (cursor inside parentheses). Suggest `orders.*` columns (inner scope). The subquery's FROM clause establishes scope. + +**Scenario B:** Cursor in WHERE clause of subquery. + +```sql +SELECT * FROM users WHERE id IN (SELECT user_id FROM orders WHERE |) +``` + +**Resolution:** **v1 supported**. Context detection operates on the subquery scope (cursor inside parentheses). Suggest `orders.*` columns (inner scope). The subquery's FROM clause establishes scope. + +**Scenario C:** Cursor in subquery without FROM (correlated subquery). + +```sql +SELECT * FROM users WHERE id IN (SELECT | WHERE status = 'active') +``` + +**Resolution:** No FROM in subquery → no inner scope established. Fallback to outer scope (`users.*`) or show error depending on parsing success. This is acceptable behavior for v1. + +**Note:** This scenario is **extremely rare** in practice. The vast majority of correlated subqueries still have a table in the FROM clause (e.g., `SELECT user_id FROM orders WHERE orders.user_id = users.id`). Subqueries without FROM are edge cases, making the v1 behavior entirely reasonable. + +--- + +### 12. Column Name Equals Keyword + +**Scenario:** Table has a column named after a SQL keyword. + +```sql +-- Table: users (columns: id, name, select, from) +SELECT * FROM users WHERE | +``` + +**Resolution:** Suggest all columns including `users.select` and `users.from`. Column names take precedence over keywords in expression contexts. User must quote if necessary (e.g., `` `select` `` in MySQL). + +--- + +### 13. Alias Shadows Table Name + +**Scenario:** Alias has the same name as another table. + +```sql +SELECT * FROM users AS orders WHERE | +``` + +**Resolution:** Alias `orders` shadows the physical table `orders`. Suggest `orders.id, orders.name, ...` (columns from `users` table via alias `orders`). Physical table `orders` is not in scope. + +--- + +### 14. Self-Join with Same Table + +**Scenario:** Table joined with itself using different aliases. + +```sql +SELECT * FROM users u1 JOIN users u2 ON u1.id = u2.manager_id WHERE | +``` + +**Resolution:** Suggest columns from both aliases: +- `u1.id, u1.name, u1.email, ...` (first instance) +- `u2.id, u2.name, u2.email, ...` (second instance) + +Both are in scope. No deduplication (different aliases = different logical tables). + +--- + +### 15. Whitespace-Only Prefix + +**Scenario:** User types only whitespace after a keyword. + +```sql +SELECT | +``` + +**Resolution:** Whitespace is trimmed. No prefix exists. Show all columns (if scope exists) + functions + keywords. + +--- + +## Spacing After Completion + +- **Keywords:** Space added after keywords (e.g., `SELECT `, `FROM `, `WHERE `, `JOIN `, `AS `) + - Multi-word keywords are treated as keywords for spacing (space appended after `ORDER BY`, `GROUP BY`, `IS NULL`, etc.) +- **Columns:** No space added (e.g., `users.id|` allows immediate `,` or space) +- **Tables:** No space added (e.g., `users|` allows immediate space or alias) +- **Functions:** No space added by default + - **Future enhancement:** Consider function snippets with cursor positioning (e.g., `COUNT(|)` where `|` is cursor position) + - This would require snippet support in the autocomplete system diff --git a/tests/autocomplete/autocomplete_adapter.py b/tests/autocomplete/autocomplete_adapter.py new file mode 100644 index 0000000..19ffe16 --- /dev/null +++ b/tests/autocomplete/autocomplete_adapter.py @@ -0,0 +1,344 @@ +import json +import re +from dataclasses import dataclass +from typing import Any +from typing import Optional +from unittest.mock import Mock + +import yaml + +from constants import WORKDIR +from structures.engines.database import SQLColumn +from structures.engines.database import SQLDatabase +from structures.engines.database import SQLDataType +from structures.engines.database import SQLForeignKey +from structures.engines.database import SQLTable +from windows.components.stc.autocomplete.auto_complete import SQLCompletionProvider +from windows.components.stc.autocomplete.context_detector import ContextDetector +from windows.components.stc.autocomplete.dot_completion_handler import ( + DotCompletionHandler, +) +from windows.components.stc.autocomplete.sql_context import SQLContext +from windows.components.stc.autocomplete.statement_extractor import StatementExtractor + +SUPPORTED_ENGINE_VERSIONS: dict[str, list[str]] = { + "mysql": ["8", "9"], + "mariadb": ["5", "10", "11", "12"], + "postgresql": ["15", "16", "17", "18"], + "sqlite": ["3"], +} + +AVAILABLE_ENGINES: list[str] = list(SUPPORTED_ENGINE_VERSIONS.keys()) + + +@dataclass(frozen=True) +class AutocompleteRequest: + sql: str + dialect: str + current_table: Optional[str] + schema: dict[str, Any] + engine: str = "mysql" + engine_version: Optional[str] = None + + +@dataclass(frozen=True) +class AutocompleteResponse: + mode: str + context: str + prefix: Optional[str] + suggestions: list[str] + extras: dict[str, Any] + + +def _load_legacy_vocab() -> dict[str, list[str]]: + config_path = WORKDIR / "tests" / "autocomplete" / "test_config.json" + if not config_path.exists(): + return {"functions": [], "keywords": []} + + with open(config_path, encoding="utf-8") as file_handle: + config = json.load(file_handle) + + vocab = config.get("vocab", {}) + return { + "functions": vocab.get("functions_all", []), + "keywords": vocab.get("keywords_all", []), + } + + +def _resolve_engine_version(engine: str, requested_version: Optional[str]) -> str: + versions = SUPPORTED_ENGINE_VERSIONS.get(engine, []) + if not versions: + return "" + + if requested_version and requested_version in versions: + return requested_version + + return versions[0] + + +def _load_yaml(path: Any) -> dict[str, Any]: + if not path.exists(): + return {} + + with open(path, encoding="utf-8") as file_handle: + data = yaml.safe_load(file_handle) + + if not isinstance(data, dict): + return {} + + return data + + +def _merge_spec_lists( + base_items: list[str], add_items: list[str], remove_items: list[str] +) -> list[str]: + removed_values = {item.upper() for item in remove_items} + merged = [item for item in base_items if item.upper() not in removed_values] + + existing_values = {item.upper() for item in merged} + for item in add_items: + if item.upper() not in existing_values: + merged.append(item) + existing_values.add(item.upper()) + + return merged + + +def _extract_names(items: Any) -> list[str]: + if not isinstance(items, list): + return [] + + names: list[str] = [] + for item in items: + if isinstance(item, str): + names.append(item) + continue + if isinstance(item, dict): + name = item.get("name") + if isinstance(name, str): + names.append(name) + return names + + +def _load_engine_vocab( + engine: str, engine_version: Optional[str] +) -> dict[str, list[str]]: + global_spec_path = WORKDIR / "structures" / "engines" / "specification.yaml" + engine_spec_path = ( + WORKDIR / "structures" / "engines" / engine / "specification.yaml" + ) + + global_spec = _load_yaml(global_spec_path) + engine_spec = _load_yaml(engine_spec_path) + + global_common = ( + global_spec.get("common", {}) + if isinstance(global_spec.get("common", {}), dict) + else {} + ) + engine_common = ( + engine_spec.get("common", {}) + if isinstance(engine_spec.get("common", {}), dict) + else {} + ) + + keywords = _extract_names(global_common.get("keywords", [])) + functions = _extract_names(global_common.get("functions", [])) + + keywords = _merge_spec_lists( + keywords, + _extract_names(engine_common.get("keywords", [])), + [], + ) + functions = _merge_spec_lists( + functions, + _extract_names(engine_common.get("functions", [])), + [], + ) + + selected_version = _resolve_engine_version(engine, engine_version) + versions_map = ( + engine_spec.get("versions", {}) + if isinstance(engine_spec.get("versions", {}), dict) + else {} + ) + version_spec = ( + versions_map.get(selected_version, {}) + if isinstance(versions_map.get(selected_version, {}), dict) + else {} + ) + + keywords = _merge_spec_lists( + keywords, + [], + _extract_names(version_spec.get("keywords_remove", [])), + ) + functions = _merge_spec_lists( + functions, + [], + _extract_names(version_spec.get("functions_remove", [])), + ) + + return {"functions": functions, "keywords": keywords} + + +def _select_vocab(request: AutocompleteRequest) -> dict[str, list[str]]: + if request.dialect == "generic": + return _load_legacy_vocab() + + if request.engine not in AVAILABLE_ENGINES: + return _load_legacy_vocab() + + return _load_engine_vocab(request.engine, request.engine_version) + + +def _create_mock_database( + schema: dict[str, Any], vocab: dict[str, list[str]] +) -> SQLDatabase: + mock_database = Mock(spec=SQLDatabase) + mock_database.name = "test_db" + + mock_context = Mock() + mock_context.KEYWORDS = vocab.get("keywords", []) + mock_context.FUNCTIONS = vocab.get("functions", []) + mock_database.context = mock_context + + tables: list[SQLTable] = [] + tables_by_name: dict[str, SQLTable] = {} + for table_data in schema.get("tables", []): + mock_table = Mock(spec=SQLTable) + mock_table.name = table_data["name"] + mock_table.database = mock_database + + columns: list[SQLColumn] = [] + for column_data in table_data.get("columns", []): + mock_column = Mock(spec=SQLColumn) + mock_column.name = column_data["name"] + mock_column.datatype = Mock(spec=SQLDataType) + mock_column.table = mock_table + columns.append(mock_column) + + mock_table.columns = columns + mock_table.foreign_keys = [] + tables.append(mock_table) + tables_by_name[mock_table.name.lower()] = mock_table + + for table_data in schema.get("tables", []): + table_name = table_data.get("name", "") + if not table_name: + continue + + mock_table = tables_by_name.get(table_name.lower()) + if not mock_table: + continue + + foreign_keys: list[SQLForeignKey] = [] + for index, fk_data in enumerate(table_data.get("foreign_keys", []), start=1): + if not isinstance(fk_data, dict): + continue + + mock_fk = Mock(spec=SQLForeignKey) + mock_fk.id = index + mock_fk.name = fk_data.get("name", f"fk_{table_name}_{index}") + mock_fk.table = mock_table + mock_fk.columns = list(fk_data.get("columns", [])) + mock_fk.reference_table = fk_data.get("reference_table", "") + mock_fk.reference_columns = list(fk_data.get("reference_columns", [])) + foreign_keys.append(mock_fk) + + mock_table.foreign_keys = foreign_keys + + mock_database.tables = tables + return mock_database + + +def _resolve_current_table( + database: SQLDatabase, current_table_name: Optional[str] +) -> Optional[SQLTable]: + if not current_table_name: + return None + + for table in database.tables: + if table.name == current_table_name: + return table + + return None + + +def get_suggestions(request: AutocompleteRequest) -> AutocompleteResponse: + vocab = _select_vocab(request) + database = _create_mock_database(request.schema, vocab) + current_table = _resolve_current_table(database, request.current_table) + + provider = SQLCompletionProvider( + get_database=lambda: database, + get_current_table=lambda: current_table, + ) + + cursor_position = request.sql.find("|") + if cursor_position == -1: + cursor_position = len(request.sql) + + text = request.sql.replace("|", "") + + extractor = StatementExtractor() + statement, relative_position = extractor.extract_current_statement( + text, cursor_position + ) + + detector = ContextDetector() + sql_context, scope, _prefix = detector.detect( + statement, relative_position, database + ) + + dot_handler = DotCompletionHandler(database, scope) + if dot_handler.is_dot_completion(statement, relative_position): + sql_context = SQLContext.DOT_COMPLETION + + completion_result = provider.get(text=text, pos=cursor_position) + if completion_result is None: + suggestions: list[str] = [] + completion_prefix: str = "" + else: + suggestions = [item.name for item in completion_result.items] + completion_prefix = completion_result.prefix + + left_statement = statement[:relative_position] + + if sql_context.name == "DOT_COMPLETION": + mode = "DOT" + elif sql_context.name == "EMPTY": + mode = "EMPTY" + elif sql_context.name == "JOIN_AFTER_TABLE": + mode = "AFTER_JOIN_TABLE" + elif sql_context.name in { + "JOIN_ON_AFTER_OPERATOR", + "WHERE_AFTER_OPERATOR", + "HAVING_AFTER_OPERATOR", + }: + mode = "AFTER_OPERATOR" + elif sql_context.name in { + "JOIN_ON_AFTER_EXPRESSION", + "WHERE_AFTER_EXPRESSION", + "HAVING_AFTER_EXPRESSION", + }: + if sql_context.name == "WHERE_AFTER_EXPRESSION" and re.search( + r"\bIS(?:\s+NOT)?\s*$", + left_statement, + re.IGNORECASE, + ): + mode = "CONTEXT" + else: + mode = "AFTER_EXPRESSION" + elif completion_prefix: + mode = "PREFIX" + else: + mode = "CONTEXT" + + return AutocompleteResponse( + mode=mode, + context=sql_context.name, + prefix=completion_prefix or None, + suggestions=suggestions, + extras={}, + ) diff --git a/tests/autocomplete/cases/alias.json b/tests/autocomplete/cases/alias.json new file mode 100644 index 0000000..a6c97af --- /dev/null +++ b/tests/autocomplete/cases/alias.json @@ -0,0 +1,218 @@ +{ + "group": "ALIAS", + "cases": [ + { + "case_id": "ALIAS_001", + "title": "Prefix matches column: suggest unqualified column", + "sql": "SELECT * FROM users u WHERE n|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "WHERE_CLAUSE", + "prefix": "n", + "alias_exact_match": null, + "comment": "Prefix 'n' matches aliased scope column; suggestions remain alias-qualified.", + "suggestions": [ + "u.name", + "NOW", + "NULLIF" + ] + } + }, + { + "case_id": "ALIAS_003", + "title": "SELECT: Prefix matches alias", + "sql": "SELECT o| FROM orders o", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "SELECT_LIST", + "prefix": "o", + "alias_exact_match": "o", + "comment": "Prefix 'o' exactly matches alias 'o'. Trigger alias disambiguation.", + "suggestions": [ + "o.*", + "o.id", + "o.user_id", + "o.total", + "o.status", + "o.created_at" + ] + } + }, + { + "case_id": "ALIAS_004", + "title": "ORDER BY: Prefix matches column", + "sql": "SELECT * FROM products p ORDER BY pr|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "ORDER_BY_CLAUSE", + "prefix": "pr", + "alias_exact_match": null, + "comment": "Prefix 'pr' matches aliased scope column; ORDER BY suggestions remain alias-qualified.", + "suggestions": [ + "p.price" + ] + } + }, + { + "case_id": "ALIAS_005", + "title": "JOIN ON: Prefix matches alias", + "sql": "SELECT * FROM customers c JOIN orders o ON c|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "JOIN_ON", + "prefix": "c", + "alias_exact_match": "c", + "comment": "Prefix 'c' exactly matches alias 'c'. Trigger alias disambiguation.", + "suggestions": [ + "c.id", + "c.name", + "c.email", + "COALESCE", + "CONCAT", + "COUNT", + "CURRENT_DATE", + "CURRENT_TIME", + "CURRENT_TIMESTAMP" + ] + } + }, + { + "case_id": "ALIAS_006", + "title": "GROUP BY: Prefix matches column", + "sql": "SELECT COUNT(*) FROM items i GROUP BY item|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "GROUP_BY_CLAUSE", + "prefix": "item", + "alias_exact_match": null, + "comment": "Prefix 'item' matches aliased scope column; GROUP BY suggestions remain alias-qualified.", + "suggestions": [ + "i.item_name" + ] + } + }, + { + "case_id": "ALIAS_007", + "title": "WHERE: Prefix does not match alias or any column - no suggestions", + "sql": "SELECT * FROM users u WHERE us|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "WHERE_CLAUSE", + "prefix": "us", + "alias_exact_match": null, + "comment": "Prefix 'us' does not match alias 'u' or any column. Table is aliased, cannot use original name. No suggestions.", + "suggestions": [] + } + }, + { + "case_id": "ALIAS_008", + "title": "SELECT: Prefix does not match alias or any column - no suggestions", + "sql": "SELECT ord| FROM orders o", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "SELECT_LIST", + "prefix": "ord", + "alias_exact_match": null, + "comment": "Prefix 'ord' does not match alias 'o' or any column. Table is aliased, cannot use original name. No suggestions.", + "suggestions": [ + "orders.id", + "orders.user_id", + "orders.total", + "orders.status", + "orders.created_at" + ] + } + }, + { + "case_id": "ALIAS_009", + "title": "ORDER BY: Prefix does not match alias or any column - no suggestions", + "sql": "SELECT * FROM products p ORDER BY prod|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "ORDER_BY_CLAUSE", + "prefix": "prod", + "alias_exact_match": null, + "comment": "Prefix 'prod' does not match alias 'p' or any column. Table is aliased, cannot use original name. No suggestions.", + "suggestions": [] + } + }, + { + "case_id": "ALIAS_010", + "title": "JOIN ON: Prefix does not match any alias or column - no suggestions", + "sql": "SELECT * FROM customers c JOIN orders o ON cust|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "JOIN_ON", + "prefix": "cust", + "alias_exact_match": null, + "comment": "Prefix 'cust' matches table name 'customers'. Suggest qualified columns by table-name expansion.", + "suggestions": [ + "customers.id", + "customers.name", + "customers.email" + ] + } + }, + { + "case_id": "ALIAS_011", + "title": "GROUP BY: Prefix does not match alias or any column - no suggestions", + "sql": "SELECT COUNT(*) FROM items i GROUP BY ite|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "GROUP_BY_CLAUSE", + "prefix": "ite", + "alias_exact_match": null, + "comment": "Prefix 'ite' matches aliased scope column; GROUP BY suggestions remain alias-qualified.", + "suggestions": [ + "i.item_name" + ] + } + }, + { + "case_id": "ALIAS_012", + "title": "HAVING: Prefix does not match alias or any column - no suggestions", + "sql": "SELECT COUNT(*) FROM customers c GROUP BY c.name HAVING cust|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "HAVING_CLAUSE", + "prefix": "cust", + "alias_exact_match": null, + "comment": "Prefix 'cust' does not match alias 'c' or any column. Table is aliased, cannot use original name. No suggestions.", + "suggestions": [] + } + } + ] +} diff --git a/tests/autocomplete/cases/alias_prefix_disambiguation.json b/tests/autocomplete/cases/alias_prefix_disambiguation.json new file mode 100644 index 0000000..75f75b5 --- /dev/null +++ b/tests/autocomplete/cases/alias_prefix_disambiguation.json @@ -0,0 +1,164 @@ +{ + "group": "ALIAS_PREFIX_DISAMBIGUATION", + "cases": [ + { + "case_id": "ALIAS_DISAMBIG_001", + "title": "WHERE: alias exact-match with valid c* suggestions", + "sql": "SELECT * FROM customers c WHERE c|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "WHERE_CLAUSE", + "prefix": "c", + "alias_exact_match": "c", + "comment": "Prefix 'c' exactly matches alias 'c'. Alias-prefix mode activated in schema order.", + "suggestions": [ + "c.id", + "c.name", + "c.email", + "COALESCE", + "CONCAT", + "COUNT", + "CURRENT_DATE", + "CURRENT_TIME", + "CURRENT_TIMESTAMP" + ] + } + }, + { + "case_id": "ALIAS_DISAMBIG_003", + "title": "WHERE: CURRENT_TABLE overridden by alias exact-match (pm)", + "sql": "SELECT * FROM payments pm WHERE pm|", + "dialect": "generic", + "current_table": "payments", + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "WHERE_CLAUSE", + "prefix": "pm", + "alias_exact_match": "pm", + "comment": "Alias-exact-match overrides CURRENT_TABLE priority. Prefix is 'pm': only pm.* columns are valid matches; no function fallback.", + "suggestions": [ + "pm.id", + "pm.order_id", + "pm.amount", + "pm.method", + "pm.created_at" + ] + } + }, + { + "case_id": "ALIAS_DISAMBIG_004", + "title": "WHERE: deduplication for us alias (no fallback)", + "sql": "SELECT * FROM user_sessions us WHERE us|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "WHERE_CLAUSE", + "prefix": "us", + "alias_exact_match": "us", + "comment": "Alias-prefix mode. Prefix is 'us': only us.* columns in schema order, no duplicates and no function fallback.", + "suggestions": [ + "us.id", + "us.user_id", + "us.session_token", + "us.expires_at" + ] + } + }, + { + "case_id": "ALIAS_DISAMBIG_005", + "title": "SELECT_LIST: alias exact-match with strict i-prefix", + "sql": "SELECT i| FROM items i", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "SELECT_LIST", + "prefix": "i", + "alias_exact_match": "i", + "comment": "SELECT_LIST with alias-exact-match. Prefix is 'i': only i.* columns in schema order; no extra fallback suggestions.", + "suggestions": [ + "i.*", + "i.id", + "i.item_name", + "i.stock", + "i.price", + "IF", + "IFNULL" + ] + } + }, + { + "case_id": "ALIAS_DISAMBIG_006", + "title": "JOIN_ON: alias exact-match with valid c* scalar functions", + "sql": "SELECT * FROM customers c JOIN payments pm ON c|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "JOIN_ON", + "prefix": "c", + "alias_exact_match": "c", + "comment": "JOIN_ON with alias-exact-match. Prefix 'c' allows c.* columns first, then syntactically valid c* scalar functions.", + "suggestions": [ + "c.id", + "c.name", + "c.email", + "COALESCE", + "CONCAT", + "COUNT", + "CURRENT_DATE", + "CURRENT_TIME", + "CURRENT_TIMESTAMP" + ] + } + }, + { + "case_id": "ALIAS_DISAMBIG_007", + "title": "WHERE: alias exact-match in multi-scope context", + "sql": "SELECT * FROM products p JOIN inventory inv ON p.id = inv.product_id WHERE p|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "WHERE_CLAUSE", + "prefix": "p", + "alias_exact_match": "p", + "comment": "Multi-scope [products as p, inventory as inv]. Alias-exact-match for 'p'. Only p.* columns in schema order, no products.* duplicates.", + "suggestions": [ + "p.id", + "p.name", + "p.price", + "p.unit_price", + "p.stock", + "PI", + "POW", + "POWER" + ] + } + }, + { + "case_id": "ALIAS_DISAMBIG_008", + "title": "WHERE: startswith mismatch keeps generic prefix mode", + "sql": "SELECT * FROM carts ca WHERE cas|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "WHERE_CLAUSE", + "prefix": "cas", + "comment": "Prefix 'cas' does NOT match alias 'ca' or any scope column/function in current behavior.", + "suggestions": [] + } + } + ] +} diff --git a/tests/autocomplete/cases/alx.json b/tests/autocomplete/cases/alx.json new file mode 100644 index 0000000..e54f59e --- /dev/null +++ b/tests/autocomplete/cases/alx.json @@ -0,0 +1,122 @@ +{ + "group": "ALX", + "cases": [ + { + "case_id": "ALX_001", + "title": "Exact alias 'u' triggers alias-prefix mode in WHERE_CLAUSE", + "sql": "SELECT * FROM users u WHERE u|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "WHERE_CLAUSE", + "prefix": "u", + "alias_exact_match": "u", + "suggestions": [ + "u.id", + "u.name", + "u.email", + "u.status", + "u.created_at", + "UNIX_TIMESTAMP", + "UPPER", + "UUID" + ] + } + }, + { + "case_id": "ALX_002", + "title": "Exact alias 'o' triggers alias-prefix mode in JOIN_ON", + "sql": "SELECT * FROM users u JOIN orders o ON o|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "JOIN_ON", + "prefix": "o", + "alias_exact_match": "o", + "suggestions": [ + "o.id", + "o.user_id", + "o.total", + "o.status", + "o.created_at" + ] + } + }, + { + "case_id": "ALX_003", + "title": "Exact alias 'u' triggers alias-prefix mode in ORDER_BY_CLAUSE", + "sql": "SELECT * FROM users u ORDER BY u|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "ORDER_BY_CLAUSE", + "prefix": "u", + "alias_exact_match": "u", + "suggestions": [ + "u.id", + "u.name", + "u.email", + "u.status", + "u.created_at", + "UNIX_TIMESTAMP", + "UPPER", + "UUID" + ] + } + }, + { + "case_id": "ALX_004", + "title": "Exact alias 'u' triggers alias-prefix mode in GROUP_BY_CLAUSE", + "sql": "SELECT * FROM users u GROUP BY u|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "GROUP_BY_CLAUSE", + "prefix": "u", + "alias_exact_match": "u", + "suggestions": [ + "u.id", + "u.name", + "u.email", + "u.status", + "u.created_at", + "UNIX_TIMESTAMP", + "UPPER", + "UUID" + ] + } + }, + { + "case_id": "ALX_005", + "title": "Exact alias 'u' triggers alias-prefix mode in HAVING_CLAUSE", + "sql": "SELECT status, COUNT(*) FROM users u GROUP BY status HAVING u|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "HAVING_CLAUSE", + "prefix": "u", + "alias_exact_match": "u", + "suggestions": [ + "u.id", + "u.name", + "u.email", + "u.status", + "u.created_at", + "UNIX_TIMESTAMP", + "UPPER", + "UUID" + ] + } + } + ] +} diff --git a/tests/autocomplete/cases/cursor_in_token.json b/tests/autocomplete/cases/cursor_in_token.json new file mode 100644 index 0000000..346a609 --- /dev/null +++ b/tests/autocomplete/cases/cursor_in_token.json @@ -0,0 +1,24 @@ +{ + "group": "CURSOR_IN_TOKEN", + "cases": [ + { + "case_id": "CURSOR_001", + "title": "SELECT_LIST no-scope: middle-token cursor uses left fragment as prefix", + "sql": "SELECT na|me", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "SELECT_LIST", + "prefix": "na", + "comment": "Cursor is inside token 'name': only left fragment 'na' is used for prefix matching.", + "suggestions": [ + "users.name", + "products.name", + "customers.name" + ] + } + } + ] +} diff --git a/tests/autocomplete/cases/derived_tables_cte.json b/tests/autocomplete/cases/derived_tables_cte.json new file mode 100644 index 0000000..bb25bcf --- /dev/null +++ b/tests/autocomplete/cases/derived_tables_cte.json @@ -0,0 +1,327 @@ +{ + "group": "DERIVED_TABLES_CTE", + "cases": [ + { + "case_id": "DERIVED_001", + "title": "Derived table columns resolved from subquery alias", + "sql": "SELECT * FROM (SELECT id, total FROM orders) AS o WHERE |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "WHERE_CLAUSE", + "prefix": null, + "comment": "Derived alias in scope keeps WHERE columns alias-qualified.", + "suggestions": [ + "o.id", + "o.total", + "AVG", + "COALESCE", + "CONCAT", + "COUNT", + "CURRENT_DATE", + "CURRENT_TIME", + "CURRENT_TIMESTAMP", + "DATE", + "GROUP_CONCAT", + "IF", + "IFNULL", + "LENGTH", + "LOWER", + "MAX", + "MIN", + "MONTH", + "NOW", + "NULLIF", + "PI", + "POW", + "POWER", + "ROW_NUMBER", + "SUBSTR", + "SUM", + "TRIM", + "UNIX_TIMESTAMP", + "UPPER", + "UUID", + "YEAR" + ] + } + }, + { + "case_id": "DERIVED_002", + "title": "Derived table with JOIN - both scopes available", + "sql": "SELECT * FROM (SELECT id, total FROM orders) AS o JOIN users u ON o.id = u.id WHERE |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "WHERE_CLAUSE", + "prefix": null, + "comment": "Scope includes derived alias in FROM and physical JOIN alias in WHERE.", + "suggestions": [ + "o.id", + "o.total", + "u.id", + "u.name", + "u.email", + "u.status", + "u.created_at", + "AVG", + "COALESCE", + "CONCAT", + "COUNT", + "CURRENT_DATE", + "CURRENT_TIME", + "CURRENT_TIMESTAMP", + "DATE", + "GROUP_CONCAT", + "IF", + "IFNULL", + "LENGTH", + "LOWER", + "MAX", + "MIN", + "MONTH", + "NOW", + "NULLIF", + "PI", + "POW", + "POWER", + "ROW_NUMBER", + "SUBSTR", + "SUM", + "TRIM", + "UNIX_TIMESTAMP", + "UPPER", + "UUID", + "YEAR" + ] + } + }, + { + "case_id": "DERIVED_003", + "title": "Derived table: dot-completion on subquery alias", + "sql": "SELECT * FROM (SELECT id, total FROM orders) AS o WHERE o.|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "DOT", + "context": "DOT_COMPLETION", + "prefix": null, + "comment": "Dot-completion on derived table alias returns projected columns from subquery.", + "suggestions": [ + "id", + "total" + ] + } + }, + { + "case_id": "CTE_001", + "title": "CTE columns available and qualified by CTE name", + "sql": "WITH active_users AS (SELECT id, name FROM users WHERE status='active') SELECT * FROM active_users WHERE |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "WHERE_CLAUSE", + "prefix": null, + "comment": "Scope resolves CTE projected columns in WHERE context.", + "suggestions": [ + "id", + "name", + "AVG", + "COALESCE", + "CONCAT", + "COUNT", + "CURRENT_DATE", + "CURRENT_TIME", + "CURRENT_TIMESTAMP", + "DATE", + "GROUP_CONCAT", + "IF", + "IFNULL", + "LENGTH", + "LOWER", + "MAX", + "MIN", + "MONTH", + "NOW", + "NULLIF", + "PI", + "POW", + "POWER", + "ROW_NUMBER", + "SUBSTR", + "SUM", + "TRIM", + "UNIX_TIMESTAMP", + "UPPER", + "UUID", + "YEAR" + ] + } + }, + { + "case_id": "CTE_002", + "title": "CTE not visible in next statement", + "sql": "WITH active_users AS (SELECT id, name FROM users WHERE status='active') SELECT * FROM active_users; SELECT * FROM |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "FROM_CLAUSE", + "prefix": null, + "comment": "Second statement does not inherit CTE from previous statement.", + "suggestions": [ + "carts", + "customers", + "inventory", + "items", + "orders", + "orders_archive", + "payments", + "products", + "users", + "user_sessions" + ] + } + }, + { + "case_id": "CTE_003", + "title": "CTE with physical table JOIN", + "sql": "WITH au AS (SELECT id, name FROM users) SELECT * FROM au JOIN orders o ON au.id = o.user_id WHERE |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "WHERE_CLAUSE", + "prefix": null, + "comment": "Scope includes CTE alias and physical JOIN alias in WHERE context.", + "suggestions": [ + "au.id", + "au.name", + "o.id", + "o.user_id", + "o.total", + "o.status", + "o.created_at", + "AVG", + "COALESCE", + "CONCAT", + "COUNT", + "CURRENT_DATE", + "CURRENT_TIME", + "CURRENT_TIMESTAMP", + "DATE", + "GROUP_CONCAT", + "IF", + "IFNULL", + "LENGTH", + "LOWER", + "MAX", + "MIN", + "MONTH", + "NOW", + "NULLIF", + "PI", + "POW", + "POWER", + "ROW_NUMBER", + "SUBSTR", + "SUM", + "TRIM", + "UNIX_TIMESTAMP", + "UPPER", + "UUID", + "YEAR" + ] + } + }, + { + "case_id": "CTE_004", + "title": "CTE suggested in FROM_CLAUSE", + "sql": "WITH active_users AS (SELECT id, name FROM users WHERE status='active') SELECT * FROM |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "FROM_CLAUSE", + "prefix": null, + "comment": "CTE 'active_users' available as table candidate.", + "suggestions": [ + "active_users", + "carts", + "customers", + "inventory", + "items", + "orders", + "orders_archive", + "payments", + "products", + "users", + "user_sessions" + ] + } + }, + { + "case_id": "CTE_005", + "title": "FROM with CTE - CTE names suggested first", + "sql": "WITH active_products AS (SELECT * FROM products WHERE price > 0) SELECT * FROM |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "FROM_CLAUSE", + "prefix": null, + "comment": "CTE 'active_products' available. Physical tables also suggested.", + "suggestions": [ + "active_products", + "carts", + "customers", + "inventory", + "items", + "orders", + "orders_archive", + "payments", + "products", + "users", + "user_sessions" + ] + } + }, + { + "case_id": "CTE_006", + "title": "JOIN includes CTE names", + "sql": "WITH active_orders AS (SELECT id FROM orders) SELECT * FROM orders o JOIN |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "JOIN_CLAUSE", + "prefix": null, + "suggestions": [ + "active_orders", + "carts", + "customers", + "inventory", + "items", + "orders", + "orders_archive", + "payments", + "products", + "users", + "user_sessions" + ] + } + } + ] +} diff --git a/tests/autocomplete/cases/dot_completion.json b/tests/autocomplete/cases/dot_completion.json new file mode 100644 index 0000000..44ba401 --- /dev/null +++ b/tests/autocomplete/cases/dot_completion.json @@ -0,0 +1,139 @@ +{ + "group": "DOT_COMPLETION", + "cases": [ + { + "case_id": "DOT_001", + "title": "SELECT: Dot-completion without prefix (alias)", + "sql": "SELECT c.| FROM customers c", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "DOT", + "context": "DOT_COMPLETION", + "prefix": null, + "comment": "Even in SELECT_LIST context, dot-completion returns ONLY columns. NO functions.", + "suggestions": [ + "id", + "name", + "email" + ] + } + }, + { + "case_id": "DOT_002", + "title": "SELECT: Dot-completion without prefix (ignores CURRENT_TABLE)", + "sql": "SELECT items.|", + "dialect": "generic", + "current_table": "users", + "schema_variant": "small", + "expected": { + "mode": "DOT", + "context": "DOT_COMPLETION", + "prefix": null, + "comment": "Dot-completion ignores CURRENT_TABLE. Only items columns.", + "suggestions": [ + "id", + "item_name", + "stock", + "price" + ] + } + }, + { + "case_id": "DOT_003", + "title": "SELECT: Dot-completion with prefix filter (multiple matches)", + "sql": "SELECT items.i|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "DOT", + "context": "DOT_COMPLETION", + "prefix": "i", + "comment": "Dot-completion with prefix 'i' filters to 'id' and 'item_name'.", + "suggestions": [ + "id", + "item_name" + ] + } + }, + { + "case_id": "DOT_004", + "title": "WHERE: Dot-completion without prefix", + "sql": "SELECT * FROM orders o WHERE o.|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "DOT", + "context": "DOT_COMPLETION", + "prefix": null, + "comment": "Dot-completion on alias 'o' returns unqualified columns.", + "suggestions": [ + "id", + "user_id", + "total", + "status", + "created_at" + ] + } + }, + { + "case_id": "DOT_005", + "title": "WHERE: Dot-completion with prefix filter", + "sql": "SELECT * FROM customers c WHERE c.e|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "DOT", + "context": "DOT_COMPLETION", + "prefix": "e", + "comment": "Dot-completion with prefix 'e' filters to email.", + "suggestions": [ + "email" + ] + } + }, + { + "case_id": "DOT_006", + "title": "JOIN ON: Dot-completion without prefix", + "sql": "SELECT * FROM orders o JOIN items i ON o.|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "DOT", + "context": "DOT_COMPLETION", + "prefix": null, + "comment": "Dot-completion in JOIN ON clause.", + "suggestions": [ + "id", + "user_id", + "total", + "status", + "created_at" + ] + } + }, + { + "case_id": "DOT_007", + "title": "ORDER BY: Dot-completion with prefix filter", + "sql": "SELECT * FROM items i ORDER BY i.i|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "DOT", + "context": "DOT_COMPLETION", + "prefix": "i", + "comment": "Dot-completion with prefix 'i' in ORDER BY.", + "suggestions": [ + "id", + "item_name" + ] + } + } + ] +} diff --git a/tests/autocomplete/cases/empty.json b/tests/autocomplete/cases/empty.json new file mode 100644 index 0000000..1c6c2da --- /dev/null +++ b/tests/autocomplete/cases/empty.json @@ -0,0 +1,34 @@ +{ + "group": "EMPTY", + "cases": [ + { + "case_id": "EMPTY_001", + "title": "Empty editor shows primary keywords", + "sql": "|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "EMPTY", + "context": "EMPTY", + "prefix": null, + "suggestions": [ + "ALTER", + "CREATE", + "DELETE", + "DESCRIBE", + "DROP", + "EXPLAIN", + "INSERT", + "MERGE", + "REPLACE", + "SELECT", + "SHOW", + "TRUNCATE", + "UPDATE", + "WITH" + ] + } + } + ] +} \ No newline at end of file diff --git a/tests/autocomplete/cases/from.json b/tests/autocomplete/cases/from.json new file mode 100644 index 0000000..3fb1c65 --- /dev/null +++ b/tests/autocomplete/cases/from.json @@ -0,0 +1,236 @@ +{ + "group": "FROM", + "cases": [ + { + "case_id": "FROM_001", + "title": "FROM without prefix suggests tables", + "sql": "SELECT * FROM |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "FROM_CLAUSE", + "prefix": null, + "suggestions": [ + "carts", + "customers", + "inventory", + "items", + "orders", + "orders_archive", + "payments", + "products", + "users", + "user_sessions" + ] + } + }, + { + "case_id": "FROM_002", + "title": "FROM with prefix filters tables", + "sql": "SELECT * FROM u|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "FROM_CLAUSE", + "prefix": "u", + "suggestions": [ + "users", + "user_sessions" + ] + } + }, + { + "case_id": "FROM_003", + "title": "FROM after comma suggests more tables", + "sql": "SELECT * FROM products, |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "FROM_CLAUSE", + "prefix": null, + "suggestions": [ + "carts", + "customers", + "inventory", + "items", + "orders", + "orders_archive", + "payments", + "users", + "user_sessions" + ] + } + }, + { + "case_id": "FROM_004", + "title": "FROM after table + space suggests JOIN/AS/WHERE etc", + "sql": "SELECT * FROM orders |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "FROM_CLAUSE", + "prefix": null, + "suggestions": [ + "JOIN", + "INNER JOIN", + "LEFT JOIN", + "RIGHT JOIN", + "CROSS JOIN", + "AS", + "WHERE", + "GROUP BY", + "ORDER BY", + "LIMIT" + ] + } + }, + { + "case_id": "FROM_006", + "title": "CTE not visible across statements", + "sql": "WITH another AS (SELECT 1) SELECT * FROM another; SELECT * FROM |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "FROM_CLAUSE", + "prefix": null, + "suggestions": [ + "carts", + "customers", + "inventory", + "items", + "orders", + "orders_archive", + "payments", + "products", + "users", + "user_sessions" + ] + } + }, + { + "case_id": "FROM_007", + "title": "After AS with space, no suggestions (user typing alias)", + "sql": "SELECT * FROM items AS |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "FROM_CLAUSE", + "prefix": null, + "comment": "After AS keyword, user is typing an alias name, so no suggestions should be shown", + "suggestions": [] + } + }, + { + "case_id": "FROM_008", + "title": "After AS with partial alias, no suggestions", + "sql": "SELECT * FROM items AS i|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "FROM_CLAUSE", + "prefix": "i", + "comment": "User is typing alias name after AS, so no suggestions should interfere", + "suggestions": [] + } + }, + { + "case_id": "FROM_009", + "title": "After complete alias with space, suggest clause keywords (no AS since alias exists)", + "sql": "SELECT * FROM items AS i |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "FROM_CLAUSE", + "prefix": null, + "comment": "After complete alias definition, suggest next clause keywords. AS is excluded since alias already exists.", + "suggestions": [ + "JOIN", + "INNER JOIN", + "LEFT JOIN", + "RIGHT JOIN", + "CROSS JOIN", + "WHERE", + "GROUP BY", + "ORDER BY", + "LIMIT" + ] + } + }, + { + "case_id": "FROM_010", + "title": "After complete table without trailing space, suggest follow-up clauses", + "sql": "SELECT * FROM users|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "FROM_CLAUSE", + "prefix": "users", + "comment": "Even without trailing space, a completed table token should offer next-clause keywords.", + "suggestions": [ + "JOIN", + "INNER JOIN", + "LEFT JOIN", + "RIGHT JOIN", + "CROSS JOIN", + "AS", + "WHERE", + "GROUP BY", + "ORDER BY", + "LIMIT" + ] + } + }, + { + "case_id": "FROM_011", + "title": "After complete table + prefix, filter follow-up clause keywords", + "sql": "SELECT * FROM users W|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "FROM_CLAUSE", + "prefix": "W", + "comment": "Prefix after a completed FROM table should filter clause keywords, returning WHERE.", + "suggestions": [ + "WHERE" + ] + } + }, + { + "case_id": "FROM_012", + "title": "After aliased table + prefix, suggest WHERE without AS", + "sql": "SELECT u.id FROM users u W|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "FROM_CLAUSE", + "prefix": "W", + "comment": "After alias is already defined, AS is invalid and WHERE should be suggested for prefix W.", + "suggestions": [ + "WHERE" + ] + } + } + ] +} diff --git a/tests/autocomplete/cases/from_clause_current_table.json b/tests/autocomplete/cases/from_clause_current_table.json new file mode 100644 index 0000000..b30e6e6 --- /dev/null +++ b/tests/autocomplete/cases/from_clause_current_table.json @@ -0,0 +1,23 @@ +{ + "group": "FROM_JOIN_CLAUSE_CURRENT_TABLE", + "cases": [ + { + "case_id": "FROM_CURR_002", + "title": "FROM with prefix - CURRENT_TABLE filtered by prefix", + "sql": "SELECT * FROM p|", + "dialect": "generic", + "current_table": "products", + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "FROM_CLAUSE", + "prefix": "p", + "comment": "CURRENT_TABLE=products matches prefix 'p'.", + "suggestions": [ + "payments", + "products" + ] + } + } + ] +} diff --git a/tests/autocomplete/cases/from_clause_prioritization.json b/tests/autocomplete/cases/from_clause_prioritization.json new file mode 100644 index 0000000..85488a0 --- /dev/null +++ b/tests/autocomplete/cases/from_clause_prioritization.json @@ -0,0 +1,57 @@ +{ + "group": "FROM_CLAUSE_PRIORITIZATION", + "cases": [ + { + "case_id": "FROM_PRIO_001", + "title": "FROM clause without prefix: only referenced tables from qualified SELECT columns", + "sql": "SELECT products.id FROM |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "FROM_CLAUSE", + "prefix": null, + "comment": "products is referenced in SELECT (qualified column), so only referenced tables are suggested", + "suggestions": [ + "products" + ] + } + }, + { + "case_id": "FROM_PRIO_002", + "title": "FROM clause with prefix: only referenced tables are considered", + "sql": "SELECT orders.total FROM o|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "FROM_CLAUSE", + "prefix": "o", + "comment": "orders matches prefix 'o' and is referenced in SELECT, so only referenced tables are suggested", + "suggestions": [ + "orders" + ] + } + }, + { + "case_id": "FROM_PRIO_003", + "title": "FROM clause: multiple referenced tables preserve left-to-right SELECT order", + "sql": "SELECT items.id, products.name FROM |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "FROM_CLAUSE", + "prefix": null, + "comment": "Both items and products are referenced in SELECT, so only those tables are suggested in reference order", + "suggestions": [ + "items", + "products" + ] + } + } + ] +} diff --git a/tests/autocomplete/cases/group.json b/tests/autocomplete/cases/group.json new file mode 100644 index 0000000..f900131 --- /dev/null +++ b/tests/autocomplete/cases/group.json @@ -0,0 +1,206 @@ +{ + "group": "GROUP", + "cases": [ + { + "case_id": "GROUP_001", + "title": "GROUP BY without prefix suggests columns + functions", + "sql": "SELECT COUNT(*) FROM products GROUP BY |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "GROUP_BY_CLAUSE", + "prefix": null, + "suggestions": [ + "id", + "name", + "price", + "unit_price", + "stock", + "AVG", + "COALESCE", + "CONCAT", + "COUNT", + "DATE", + "GROUP_CONCAT", + "IF", + "IFNULL", + "LENGTH", + "LOWER", + "MAX", + "MIN", + "MONTH", + "NOW", + "NULLIF", + "ROW_NUMBER", + "SUBSTR", + "SUM", + "TRIM", + "UNIX_TIMESTAMP", + "UPPER", + "UUID", + "YEAR" + ] + } + }, + { + "case_id": "GROUP_002", + "title": "GROUP BY after comma suggests more group keys", + "sql": "SELECT COUNT(*) FROM items GROUP BY item_name, |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "GROUP_BY_CLAUSE", + "prefix": null, + "comment": "MUST NOT suggest item_name (already present in GROUP BY).", + "suggestions": [ + "id", + "stock", + "price", + "AVG", + "COALESCE", + "CONCAT", + "COUNT", + "DATE", + "GROUP_CONCAT", + "IF", + "IFNULL", + "LENGTH", + "LOWER", + "MAX", + "MIN", + "MONTH", + "NOW", + "NULLIF", + "ROW_NUMBER", + "SUBSTR", + "SUM", + "TRIM", + "UNIX_TIMESTAMP", + "UPPER", + "UUID", + "YEAR" + ] + } + }, + { + "case_id": "GROUP_003", + "title": "GROUP_BY with scope - no DB-wide columns", + "sql": "SELECT * FROM items GROUP BY s|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "GROUP_BY_CLAUSE", + "prefix": "s", + "comment": "Scope=[items]. Only scope column items.stock. DB-wide excluded.", + "suggestions": [ + "stock", + "SUBSTR", + "SUM" + ] + } + }, + { + "case_id": "GROUP_004", + "title": "GROUP_BY with CURRENT_TABLE not in scope - CURRENT_TABLE ignored", + "sql": "SELECT * FROM orders GROUP BY s|", + "dialect": "generic", + "current_table": "users", + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "GROUP_BY_CLAUSE", + "prefix": "s", + "comment": "Scope=[orders]. CURRENT_TABLE=users not in scope. Only scope column orders.status.", + "suggestions": [ + "status", + "SUBSTR", + "SUM" + ] + } + }, + { + "case_id": "GROUP_005", + "title": "GROUP BY with two FROM tables and prefix uses qualified column matches", + "sql": "SELECT * FROM orders, products GROUP BY s|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "GROUP_BY_CLAUSE", + "prefix": "s", + "comment": "Multiple tables in scope: column-name matches must be qualified.", + "suggestions": [ + "orders.status", + "products.stock", + "SUBSTR", + "SUM" + ] + } + }, + { + "case_id": "GROUP_006", + "title": "GROUP BY with two FROM tables and no prefix suggests qualified scope columns", + "sql": "SELECT * FROM orders, products GROUP BY |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "GROUP_BY_CLAUSE", + "prefix": null, + "comment": "Multiple tables in scope: columns are qualified with table name.", + "suggestions_contains": [ + "orders.id", + "orders.status", + "products.id", + "products.stock", + "AVG", + "COUNT", + "SUM" + ], + "suggestions_not_contains": [ + "id", + "status", + "stock", + "CURRENT_DATE", + "CURRENT_TIME", + "CURRENT_TIMESTAMP", + "PI", + "POW", + "POWER" + ] + } + }, + { + "case_id": "GROUP_007", + "title": "Qualified SELECT style propagates to GROUP BY columns", + "sql": "SELECT u.id, COUNT(*) FROM users u GROUP BY |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "GROUP_BY_CLAUSE", + "prefix": null, + "comment": "Qualified style in SELECT forces alias-qualified GROUP BY suggestions.", + "suggestions_contains": [ + "u.id", + "u.name", + "u.email" + ], + "suggestions_not_contains": [ + "id", + "name", + "email" + ] + } + } + ] +} diff --git a/tests/autocomplete/cases/having.json b/tests/autocomplete/cases/having.json new file mode 100644 index 0000000..dad828b --- /dev/null +++ b/tests/autocomplete/cases/having.json @@ -0,0 +1,160 @@ +{ + "group": "HAVING", + "cases": [ + { + "case_id": "HAVING_001", + "title": "HAVING without prefix: aggregates first then columns then other functions", + "sql": "SELECT status, COUNT(*) FROM orders GROUP BY status HAVING |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "HAVING_CLAUSE", + "prefix": null, + "suggestions": [ + "AVG", + "COUNT", + "GROUP_CONCAT", + "MAX", + "MIN", + "SUM", + "id", + "user_id", + "total", + "status", + "created_at", + "COALESCE", + "CONCAT", + "DATE", + "IF", + "IFNULL", + "LENGTH", + "LOWER", + "MONTH", + "NOW", + "NULLIF", + "ROW_NUMBER", + "SUBSTR", + "TRIM", + "UNIX_TIMESTAMP", + "UPPER", + "UUID", + "YEAR" + ] + } + }, + { + "case_id": "HAVING_002", + "title": "HAVING with prefix prioritizes aggregate functions matching prefix", + "sql": "SELECT item_name, COUNT(*) FROM items GROUP BY item_name HAVING c|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "HAVING_CLAUSE", + "prefix": "c", + "suggestions": [ + "COUNT", + "COALESCE", + "CONCAT" + ] + } + }, + { + "case_id": "HAVING_003", + "title": "HAVING after operator: NULL/TRUE/FALSE + aggregates + columns", + "sql": "SELECT status, COUNT(*) FROM products GROUP BY status HAVING COUNT(*) > |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "AFTER_OPERATOR", + "context": "HAVING_AFTER_OPERATOR", + "prefix": null, + "suggestions": [ + "NULL", + "TRUE", + "FALSE", + "AVG", + "COUNT", + "GROUP_CONCAT", + "MAX", + "MIN", + "SUM", + "id", + "name", + "price", + "unit_price", + "stock" + ] + } + }, + { + "case_id": "HAVING_004", + "title": "HAVING after expression suggests logical + clauses", + "sql": "SELECT email, COUNT(*) FROM customers GROUP BY email HAVING COUNT(*) > 10 |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "AFTER_EXPRESSION", + "context": "HAVING_AFTER_EXPRESSION", + "prefix": null, + "suggestions": [ + "AND", + "OR", + "NOT", + "EXISTS", + "ORDER BY", + "LIMIT" + ] + } + }, + { + "case_id": "HAVING_005", + "title": "HAVING with scope restriction - no DB-wide columns", + "sql": "SELECT status, COUNT(*) FROM orders GROUP BY status HAVING u|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "HAVING_CLAUSE", + "prefix": "u", + "comment": "Scope=[orders]. DB-wide excluded. Aggregate functions alphabetically, then columns, then other functions.", + "suggestions": [ + "user_id", + "UNIX_TIMESTAMP", + "UPPER", + "UUID" + ] + } + }, + { + "case_id": "HAVING_006", + "title": "Qualified SELECT style propagates to HAVING columns", + "sql": "SELECT u.id, COUNT(*) FROM users u GROUP BY u.id HAVING |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "HAVING_CLAUSE", + "prefix": null, + "comment": "With qualified SELECT/GROUP style, HAVING column suggestions remain alias-qualified.", + "suggestions_contains": [ + "u.id", + "u.name", + "u.email" + ], + "suggestions_not_contains": [ + "id", + "name", + "email" + ] + } + } + ] +} diff --git a/tests/autocomplete/cases/join.json b/tests/autocomplete/cases/join.json new file mode 100644 index 0000000..02f640d --- /dev/null +++ b/tests/autocomplete/cases/join.json @@ -0,0 +1,139 @@ +{ + "group": "JOIN", + "cases": [ + { + "case_id": "JOIN_001", + "title": "JOIN without prefix suggests tables", + "sql": "SELECT * FROM products JOIN |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "JOIN_CLAUSE", + "prefix": null, + "suggestions": [ + "carts", + "customers", + "inventory", + "items", + "orders", + "orders_archive", + "payments", + "users", + "user_sessions" + ] + } + }, + { + "case_id": "JOIN_002", + "title": "JOIN with prefix filters tables", + "sql": "SELECT * FROM products JOIN o|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "JOIN_CLAUSE", + "prefix": "o", + "suggestions": [ + "orders", + "orders_archive" + ] + } + }, + { + "case_id": "JOIN_003", + "title": "JOIN after table + space suggests AS/ON/USING", + "sql": "SELECT * FROM products JOIN items |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "AFTER_JOIN_TABLE", + "context": "JOIN_AFTER_TABLE", + "prefix": null, + "suggestions": [ + "AS", + "ON", + "USING" + ] + } + }, + { + "case_id": "JOIN_004", + "title": "JOIN_CLAUSE - Tables without alias NOT suggested again", + "sql": "SELECT * FROM products JOIN items ON products.id=items.product_id JOIN |", + "dialect": "generic", + "current_table": "products", + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "JOIN_CLAUSE", + "prefix": null, + "comment": "products and items have NO aliases, so they CANNOT be suggested again (self-join requires aliases). CURRENT_TABLE=products also excluded.", + "suggestions": [ + "carts", + "customers", + "inventory", + "orders", + "orders_archive", + "payments", + "users", + "user_sessions" + ] + } + }, + { + "case_id": "JOIN_005", + "title": "KILLER: JOIN with CURRENT_TABLE already present (no alias) - NOT suggested again", + "sql": "SELECT * FROM orders JOIN |", + "dialect": "generic", + "current_table": "orders", + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "JOIN_CLAUSE", + "prefix": null, + "comment": "KILLER test: orders has NO alias, so it CANNOT be suggested again (self-join requires aliases). CURRENT_TABLE=orders also excluded.", + "suggestions": [ + "carts", + "customers", + "inventory", + "items", + "orders_archive", + "payments", + "products", + "users", + "user_sessions" + ] + } + }, + { + "case_id": "JOIN_006", + "title": "JOIN_CLAUSE - Table WITH alias CAN be suggested again (self-join allowed)", + "sql": "SELECT * FROM products p JOIN |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "JOIN_CLAUSE", + "prefix": null, + "comment": "products has alias 'p', so it CAN be suggested again for self-join (e.g., FROM products p JOIN products p2).", + "suggestions": [ + "carts", + "customers", + "inventory", + "items", + "orders", + "orders_archive", + "payments", + "products", + "users", + "user_sessions" + ] + } + } + ] +} \ No newline at end of file diff --git a/tests/autocomplete/cases/join_after_table.json b/tests/autocomplete/cases/join_after_table.json new file mode 100644 index 0000000..7589970 --- /dev/null +++ b/tests/autocomplete/cases/join_after_table.json @@ -0,0 +1,92 @@ +{ + "group": "JOIN_AFTER_TABLE", + "cases": [ + { + "case_id": "JOIN_AFTER_TABLE_001", + "title": "USING keyword suggested after JOIN table name", + "sql": "SELECT * FROM products p JOIN items i |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "AFTER_JOIN_TABLE", + "context": "JOIN_AFTER_TABLE", + "prefix": null, + "suggestions": [ + "ON", + "USING" + ] + } + }, + { + "case_id": "JOIN_AFTER_TABLE_002", + "title": "After JOIN table without alias: AS, ON, USING suggested", + "sql": "SELECT * FROM products JOIN orders |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "AFTER_JOIN_TABLE", + "context": "JOIN_AFTER_TABLE", + "prefix": null, + "suggestions": [ + "AS", + "ON", + "USING" + ] + } + }, + { + "case_id": "JOIN_AFTER_TABLE_003", + "title": "After LEFT JOIN table with alias: ON, USING (no AS)", + "sql": "SELECT * FROM items i LEFT JOIN customers c |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "AFTER_JOIN_TABLE", + "context": "JOIN_AFTER_TABLE", + "prefix": null, + "suggestions": [ + "ON", + "USING" + ] + } + }, + { + "case_id": "JOIN_AFTER_TABLE_004", + "title": "After INNER JOIN table without alias: AS, ON, USING", + "sql": "SELECT * FROM orders INNER JOIN products |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "AFTER_JOIN_TABLE", + "context": "JOIN_AFTER_TABLE", + "prefix": null, + "suggestions": [ + "AS", + "ON", + "USING" + ] + } + }, + { + "case_id": "JOIN_AFTER_TABLE_005", + "title": "After JOIN alias + prefix suggests ON", + "sql": "SELECT u.id FROM users u JOIN orders O|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "JOIN_CLAUSE", + "prefix": "O", + "comment": "When JOIN table alias is complete and user types O, continue with ON keyword.", + "suggestions": [ + "ON" + ] + } + } + ] +} diff --git a/tests/autocomplete/cases/join_on.json b/tests/autocomplete/cases/join_on.json new file mode 100644 index 0000000..b988cfa --- /dev/null +++ b/tests/autocomplete/cases/join_on.json @@ -0,0 +1,297 @@ +{ + "group": "JOIN_ON", + "cases": [ + { + "case_id": "JOIN_ON_001", + "title": "ON without prefix suggests columns in scope + functions", + "sql": "SELECT * FROM products p JOIN orders o ON |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "JOIN_ON", + "prefix": null, + "suggestions": [ + "o.id", + "o.user_id", + "o.total", + "o.status", + "o.created_at", + "p.id", + "p.name", + "p.price", + "p.unit_price", + "p.stock", + "AVG", + "COALESCE", + "CONCAT", + "COUNT", + "DATE", + "GROUP_CONCAT", + "IF", + "IFNULL", + "LENGTH", + "LOWER", + "MAX", + "MIN", + "MONTH", + "NOW", + "NULLIF", + "ROW_NUMBER", + "SUBSTR", + "SUM", + "TRIM", + "UNIX_TIMESTAMP", + "UPPER", + "UUID", + "YEAR" + ] + } + }, + { + "case_id": "JOIN_ON_002", + "title": "ON alias exact match suggests alias columns + matching functions", + "sql": "SELECT * FROM products p JOIN orders o ON p|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "JOIN_ON", + "prefix": "p", + "alias_exact_match": "p", + "suggestions": [ + "p.id", + "p.name", + "p.price", + "p.unit_price", + "p.stock", + "PI", + "POW", + "POWER" + ] + } + }, + { + "case_id": "JOIN_ON_003", + "title": "ON after operator prioritizes other-side table columns", + "sql": "SELECT * FROM products p JOIN orders o ON p.id = |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "AFTER_OPERATOR", + "context": "JOIN_ON_AFTER_OPERATOR", + "prefix": null, + "suggestions": [ + "o.id", + "o.user_id", + "o.total", + "o.status", + "o.created_at", + "p.id", + "p.name", + "p.price", + "p.unit_price", + "p.stock", + "NULL", + "TRUE", + "FALSE", + "AVG", + "COALESCE", + "CONCAT", + "COUNT", + "DATE", + "GROUP_CONCAT", + "IF", + "IFNULL", + "LENGTH", + "LOWER", + "MAX", + "MIN", + "MONTH", + "NOW", + "NULLIF", + "ROW_NUMBER", + "SUBSTR", + "SUM", + "TRIM", + "UNIX_TIMESTAMP", + "UPPER", + "UUID", + "YEAR" + ] + } + }, + { + "case_id": "JOIN_ON_004", + "title": "ON after complete expression suggests logical + next clauses", + "sql": "SELECT * FROM products p JOIN items i ON p.id = i.product_id |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "AFTER_EXPRESSION", + "context": "JOIN_ON_AFTER_EXPRESSION", + "prefix": null, + "suggestions": [ + "AND", + "NOT", + "OR", + "GROUP BY", + "LIMIT", + "ORDER BY", + "WHERE" + ] + } + }, + { + "case_id": "JOIN_ON_SCOPE_001", + "title": "JOIN_ON with scope - no DB-wide columns", + "sql": "SELECT * FROM products p JOIN items i ON p.id = i.product_id JOIN orders o ON o|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "JOIN_ON", + "prefix": "o", + "comment": "Scope=[products as p, items as i, orders as o]. Only scope columns starting with 'o' in schema order. DB-wide excluded.", + "suggestions": [ + "o.id", + "o.user_id", + "o.total", + "o.status", + "o.created_at" + ] + } + }, + { + "case_id": "JOIN_ON_SCOPE_002", + "title": "JOIN_ON with CURRENT_TABLE not in scope - CURRENT_TABLE ignored", + "sql": "SELECT * FROM orders o JOIN products p ON p.id = |", + "dialect": "generic", + "current_table": "users", + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "JOIN_ON", + "prefix": null, + "comment": "Scope=[orders (alias o), products (alias p)]. CURRENT_TABLE=users not in scope. p.id is on left of =, so filtered out.", + "suggestions_contains": [ + "o.id", + "o.user_id", + "p.name", + "COUNT", + "SUM" + ], + "suggestions_not_contains": [ + "p.id", + "users.id", + "users.name", + "customers.id" + ] + } + }, + { + "case_id": "JOIN_ON_007", + "title": "ON after operator with join-side left column prioritizes FROM side", + "sql": "SELECT * FROM orders o JOIN users u ON u.id = |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "JOIN_ON", + "prefix": null, + "comment": "When left column belongs to JOIN side, suggest FROM-side columns first and prepend FK join hint when available.", + "suggestions": [ + "o.user_id = u.id", + "o.id", + "o.user_id", + "o.total", + "o.status", + "o.created_at", + "u.name", + "u.email", + "u.status", + "u.created_at", + "AVG", + "COALESCE", + "CONCAT", + "COUNT", + "DATE", + "GROUP_CONCAT", + "IF", + "IFNULL", + "LENGTH", + "LOWER", + "MAX", + "MIN", + "MONTH", + "NOW", + "NULLIF", + "ROW_NUMBER", + "SUBSTR", + "SUM", + "TRIM", + "UNIX_TIMESTAMP", + "UPPER", + "UUID", + "YEAR" + ] + } + }, + { + "case_id": "JOIN_ON_008", + "title": "JOIN ON suggests FK condition first when relationship exists", + "sql": "SELECT * FROM users u JOIN orders o ON |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "JOIN_ON", + "prefix": null, + "comment": "When FK relation exists between FROM and JOIN tables, suggest complete FK join condition first.", + "suggestions": [ + "u.id = o.user_id", + "o.id", + "o.user_id", + "o.total", + "o.status", + "o.created_at", + "u.id", + "u.name", + "u.email", + "u.status", + "u.created_at", + "AVG", + "COALESCE", + "CONCAT", + "COUNT", + "DATE", + "GROUP_CONCAT", + "IF", + "IFNULL", + "LENGTH", + "LOWER", + "MAX", + "MIN", + "MONTH", + "NOW", + "NULLIF", + "ROW_NUMBER", + "SUBSTR", + "SUM", + "TRIM", + "UNIX_TIMESTAMP", + "UPPER", + "UUID", + "YEAR" + ] + } + } + ] +} diff --git a/tests/autocomplete/cases/join_operator_left_column_filter.json b/tests/autocomplete/cases/join_operator_left_column_filter.json new file mode 100644 index 0000000..dd2e027 --- /dev/null +++ b/tests/autocomplete/cases/join_operator_left_column_filter.json @@ -0,0 +1,221 @@ +{ + "group": "JOIN_OPERATOR_LEFT_COLUMN_FILTER", + "cases": [ + { + "case_id": "JOIN_ON_OP_FILTER_001", + "title": "JOIN ON with = operator: do NOT suggest left column", + "sql": "SELECT * FROM products JOIN items ON products.id = ", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "AFTER_OPERATOR", + "context": "JOIN_ON_AFTER_OPERATOR", + "prefix": null, + "comment": "After operator in JOIN ON: operator-context suggestions with literals and scoped columns.", + "suggestions": [ + "items.id", + "items.item_name", + "items.stock", + "items.price", + "products.id", + "products.name", + "products.price", + "products.unit_price", + "products.stock", + "NULL", + "TRUE", + "FALSE", + "AVG", + "COALESCE", + "CONCAT", + "COUNT", + "DATE", + "GROUP_CONCAT", + "IF", + "IFNULL", + "LENGTH", + "LOWER", + "MAX", + "MIN", + "MONTH", + "NOW", + "NULLIF", + "ROW_NUMBER", + "SUBSTR", + "SUM", + "TRIM", + "UNIX_TIMESTAMP", + "UPPER", + "UUID", + "YEAR" + ] + } + }, + { + "case_id": "JOIN_ON_OP_FILTER_002", + "title": "JOIN ON with != operator: do NOT suggest left column", + "sql": "SELECT * FROM orders o JOIN customers c ON o.user_id != ", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "AFTER_OPERATOR", + "context": "JOIN_ON_AFTER_OPERATOR", + "prefix": null, + "comment": "After operator in JOIN ON: operator-context suggestions with literals and scoped columns.", + "suggestions": [ + "c.id", + "c.name", + "c.email", + "o.id", + "o.user_id", + "o.total", + "o.status", + "o.created_at", + "NULL", + "TRUE", + "FALSE", + "AVG", + "COALESCE", + "CONCAT", + "COUNT", + "DATE", + "GROUP_CONCAT", + "IF", + "IFNULL", + "LENGTH", + "LOWER", + "MAX", + "MIN", + "MONTH", + "NOW", + "NULLIF", + "ROW_NUMBER", + "SUBSTR", + "SUM", + "TRIM", + "UNIX_TIMESTAMP", + "UPPER", + "UUID", + "YEAR" + ] + } + }, + { + "case_id": "JOIN_ON_OP_FILTER_003", + "title": "JOIN ON with < operator: do NOT suggest left column", + "sql": "SELECT * FROM orders o JOIN items i ON o.total < ", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "AFTER_OPERATOR", + "context": "JOIN_ON_AFTER_OPERATOR", + "prefix": null, + "comment": "After operator in JOIN ON: operator-context suggestions with literals and scoped columns.", + "suggestions": [ + "i.id", + "i.item_name", + "i.stock", + "i.price", + "o.id", + "o.user_id", + "o.total", + "o.status", + "o.created_at", + "NULL", + "TRUE", + "FALSE", + "AVG", + "COALESCE", + "CONCAT", + "COUNT", + "DATE", + "GROUP_CONCAT", + "IF", + "IFNULL", + "LENGTH", + "LOWER", + "MAX", + "MIN", + "MONTH", + "NOW", + "NULLIF", + "ROW_NUMBER", + "SUBSTR", + "SUM", + "TRIM", + "UNIX_TIMESTAMP", + "UPPER", + "UUID", + "YEAR" + ] + } + }, + { + "case_id": "JOIN_ON_OP_FILTER_004", + "title": "JOIN ON with prefix after operator: normal behavior (no left-column filtering)", + "sql": "SELECT * FROM products p JOIN items i ON p.id = i", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "JOIN_ON", + "prefix": "i", + "alias_exact_match": "i", + "comment": "With prefix matching alias 'i', suggest i.* columns plus matching functions.", + "suggestions": [ + "i.id", + "i.item_name", + "i.stock", + "i.price", + "IF", + "IFNULL" + ] + } + }, + { + "case_id": "JOIN_ON_OP_FILTER_005", + "title": "JOIN ON with generic prefix after operator: normal behavior", + "sql": "SELECT * FROM orders o JOIN customers c ON o.user_id = c", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "JOIN_ON", + "prefix": "c", + "alias_exact_match": "c", + "comment": "With prefix matching alias 'c', suggest c.* columns plus matching functions.", + "suggestions": [ + "c.id", + "c.name", + "c.email", + "COALESCE", + "CONCAT", + "COUNT", + "CURRENT_DATE", + "CURRENT_TIME", + "CURRENT_TIMESTAMP" + ] + } + }, + { + "case_id": "JOIN_ON_OP_FILTER_006", + "title": "JOIN ON with prefix not matching scope columns: only functions", + "sql": "SELECT * FROM products p JOIN items i ON p.id = z", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "JOIN_ON", + "prefix": "z", + "comment": "Prefix 'z' matches no scope columns (products, items). Only functions suggested.", + "suggestions": [] + } + } + ] +} diff --git a/tests/autocomplete/cases/lex.json b/tests/autocomplete/cases/lex.json new file mode 100644 index 0000000..c5a502f --- /dev/null +++ b/tests/autocomplete/cases/lex.json @@ -0,0 +1,105 @@ +{ + "group": "LEX", + "cases": [ + { + "case_id": "LEX_001", + "title": "Semicolon in string should not split statement", + "sql": "SELECT * FROM users WHERE name = 'a;b' AND |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "WHERE_CLAUSE", + "prefix": null, + "comment": "Semicolon inside string is ignored; scope remains users table.", + "suggestions": [ + "id", + "name", + "email", + "status", + "created_at", + "AVG", + "COALESCE", + "CONCAT", + "COUNT", + "CURRENT_DATE", + "CURRENT_TIME", + "CURRENT_TIMESTAMP", + "DATE", + "GROUP_CONCAT", + "IF", + "IFNULL", + "LENGTH", + "LOWER", + "MAX", + "MIN", + "MONTH", + "NOW", + "NULLIF", + "PI", + "POW", + "POWER", + "ROW_NUMBER", + "SUBSTR", + "SUM", + "TRIM", + "UNIX_TIMESTAMP", + "UPPER", + "UUID", + "YEAR" + ] + } + }, + { + "case_id": "LEX_002", + "title": "Dot-like sequence inside string should not trigger dot-completion", + "sql": "SELECT * FROM users WHERE name = 'u.x' AND |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "WHERE_CLAUSE", + "prefix": null, + "comment": "Dot-like text inside string does not affect lexer context.", + "suggestions": [ + "id", + "name", + "email", + "status", + "created_at", + "AVG", + "COALESCE", + "CONCAT", + "COUNT", + "CURRENT_DATE", + "CURRENT_TIME", + "CURRENT_TIMESTAMP", + "DATE", + "GROUP_CONCAT", + "IF", + "IFNULL", + "LENGTH", + "LOWER", + "MAX", + "MIN", + "MONTH", + "NOW", + "NULLIF", + "PI", + "POW", + "POWER", + "ROW_NUMBER", + "SUBSTR", + "SUM", + "TRIM", + "UNIX_TIMESTAMP", + "UPPER", + "UUID", + "YEAR" + ] + } + } + ] +} diff --git a/tests/autocomplete/cases/limit.json b/tests/autocomplete/cases/limit.json new file mode 100644 index 0000000..41c6f87 --- /dev/null +++ b/tests/autocomplete/cases/limit.json @@ -0,0 +1,60 @@ +{ + "group": "LIMIT", + "cases": [ + { + "case_id": "LIMIT_001", + "title": "LIMIT offers no suggestions", + "sql": "SELECT * FROM products LIMIT |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "LIMIT_OFFSET_CLAUSE", + "prefix": null, + "suggestions": [] + } + }, + { + "case_id": "LIMIT_002", + "title": "OFFSET offers no suggestions", + "sql": "SELECT * FROM orders LIMIT 10 OFFSET |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "LIMIT_OFFSET_CLAUSE", + "prefix": null, + "suggestions": [] + } + }, + { + "case_id": "LIMIT_003", + "title": "LIMIT after number suggests OFFSET", + "sql": "SELECT * FROM customers LIMIT 10 |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "AFTER_LIMIT_NUMBER", + "prefix": null, + "comment": "After LIMIT number: suggest OFFSET keyword.", + "suggestions_contains": [ + "OFFSET" + ], + "suggestions_not_contains": [ + "id", + "name", + "COUNT", + "SUM", + "NULL", + "CURRENT_DATE", + "ASC", + "DESC" + ] + } + } + ] +} \ No newline at end of file diff --git a/tests/autocomplete/cases/mq.json b/tests/autocomplete/cases/mq.json new file mode 100644 index 0000000..92303e6 --- /dev/null +++ b/tests/autocomplete/cases/mq.json @@ -0,0 +1,54 @@ +{ + "group": "MULTI_QUERY_EDGE_CASES", + "cases": [ + { + "case_id": "MQ_EDGE_001", + "title": "Current statement isolation in middle query WHERE clause", + "sql": "SELECT * FROM users; SELECT * FROM orders WHERE |; SELECT * FROM products;", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "WHERE_CLAUSE", + "prefix": null, + "suggestions": [ + "id", + "user_id", + "total", + "status", + "created_at", + "AVG", + "COALESCE", + "CONCAT", + "COUNT", + "CURRENT_DATE", + "CURRENT_TIME", + "CURRENT_TIMESTAMP", + "DATE", + "GROUP_CONCAT", + "IF", + "IFNULL", + "LENGTH", + "LOWER", + "MAX", + "MIN", + "MONTH", + "NOW", + "NULLIF", + "PI", + "POW", + "POWER", + "ROW_NUMBER", + "SUBSTR", + "SUM", + "TRIM", + "UNIX_TIMESTAMP", + "UPPER", + "UUID", + "YEAR" + ] + } + } + ] +} diff --git a/tests/autocomplete/cases/multi_query_support.json b/tests/autocomplete/cases/multi_query_support.json new file mode 100644 index 0000000..9628e0e --- /dev/null +++ b/tests/autocomplete/cases/multi_query_support.json @@ -0,0 +1,281 @@ +{ + "group": "MULTI_QUERY_SUPPORT", + "cases": [ + { + "case_id": "MQ_EXTRACT_001", + "title": "Multi-query - cursor in second statement, analyze only second", + "sql": "SELECT * FROM users; SELECT |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "SELECT_LIST", + "prefix": null, + "comment": "Cursor in second statement. First statement ignored. No scope in second statement.", + "suggestions_contains": [ + "AVG", + "COALESCE", + "DATE", + "NOW", + "UUID" + ], + "suggestions_not_contains": [ + "users.id", + "active_users.id" + ] + } + }, + { + "case_id": "MQ_EXTRACT_002", + "title": "Multi-query - cursor in first statement WHERE clause", + "sql": "SELECT * FROM users WHERE |; SELECT * FROM orders", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "WHERE_CLAUSE", + "prefix": null, + "comment": "Cursor in first statement. Second statement ignored. Scope=[users].", + "suggestions": [ + "id", + "name", + "email", + "status", + "created_at", + "AVG", + "COALESCE", + "CONCAT", + "COUNT", + "CURRENT_DATE", + "CURRENT_TIME", + "CURRENT_TIMESTAMP", + "DATE", + "GROUP_CONCAT", + "IF", + "IFNULL", + "LENGTH", + "LOWER", + "MAX", + "MIN", + "MONTH", + "NOW", + "NULLIF", + "PI", + "POW", + "POWER", + "ROW_NUMBER", + "SUBSTR", + "SUM", + "TRIM", + "UNIX_TIMESTAMP", + "UPPER", + "UUID", + "YEAR" + ] + } + }, + { + "case_id": "MQ_EXTRACT_003", + "title": "Multi-query - second statement has scope from its own FROM", + "sql": "SELECT * FROM users; SELECT * FROM orders WHERE |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "WHERE_CLAUSE", + "prefix": null, + "comment": "Cursor in second statement. Scope=[orders] from second statement only.", + "suggestions": [ + "id", + "user_id", + "total", + "status", + "created_at", + "AVG", + "COALESCE", + "CONCAT", + "COUNT", + "CURRENT_DATE", + "CURRENT_TIME", + "CURRENT_TIMESTAMP", + "DATE", + "GROUP_CONCAT", + "IF", + "IFNULL", + "LENGTH", + "LOWER", + "MAX", + "MIN", + "MONTH", + "NOW", + "NULLIF", + "PI", + "POW", + "POWER", + "ROW_NUMBER", + "SUBSTR", + "SUM", + "TRIM", + "UNIX_TIMESTAMP", + "UPPER", + "UUID", + "YEAR" + ] + } + }, + { + "case_id": "MQ_EXTRACT_004", + "title": "Multi-query - cursor on separator resolves as prefix in next statement", + "sql": "SELECT * FROM users;|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "FROM_CLAUSE", + "prefix": "users", + "comment": "Cursor on separator resolves previous FROM statement and offers post-table clause continuations.", + "suggestions": [ + "JOIN", + "INNER JOIN", + "LEFT JOIN", + "RIGHT JOIN", + "CROSS JOIN", + "AS", + "WHERE", + "GROUP BY", + "ORDER BY", + "LIMIT" + ] + } + }, + { + "case_id": "MQ_EXTRACT_005", + "title": "Multi-query - empty statement between separators", + "sql": "SELECT * FROM users; ; SELECT |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "SELECT_LIST", + "prefix": null, + "comment": "Empty statement ignored. Cursor in third statement.", + "suggestions_contains": [ + "AVG", + "COALESCE", + "DATE", + "NOW", + "UUID" + ], + "suggestions_not_contains": [ + "users.id", + "active_products.id" + ] + } + }, + { + "case_id": "MQ_EXTRACT_007", + "title": "KILLER: Separator inside string literal - NOT a statement separator", + "sql": "SELECT 'test;value' FROM users WHERE |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "WHERE_CLAUSE", + "prefix": null, + "comment": "KILLER: Semicolon inside string literal is NOT a separator. Single statement. Scope=[users].", + "suggestions": [ + "id", + "name", + "email", + "status", + "created_at", + "AVG", + "COALESCE", + "CONCAT", + "COUNT", + "CURRENT_DATE", + "CURRENT_TIME", + "CURRENT_TIMESTAMP", + "DATE", + "GROUP_CONCAT", + "IF", + "IFNULL", + "LENGTH", + "LOWER", + "MAX", + "MIN", + "MONTH", + "NOW", + "NULLIF", + "PI", + "POW", + "POWER", + "ROW_NUMBER", + "SUBSTR", + "SUM", + "TRIM", + "UNIX_TIMESTAMP", + "UPPER", + "UUID", + "YEAR" + ] + } + }, + { + "case_id": "MQ_EXTRACT_008", + "title": "KILLER: Separator inside comment - NOT a statement separator", + "sql": "SELECT * FROM users /* comment; with semicolon */ WHERE |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "WHERE_CLAUSE", + "prefix": null, + "comment": "KILLER: Semicolon inside comment is NOT a separator. Single statement. Scope=[users].", + "suggestions": [ + "id", + "name", + "email", + "status", + "created_at", + "AVG", + "COALESCE", + "CONCAT", + "COUNT", + "CURRENT_DATE", + "CURRENT_TIME", + "CURRENT_TIMESTAMP", + "DATE", + "GROUP_CONCAT", + "IF", + "IFNULL", + "LENGTH", + "LOWER", + "MAX", + "MIN", + "MONTH", + "NOW", + "NULLIF", + "PI", + "POW", + "POWER", + "ROW_NUMBER", + "SUBSTR", + "SUM", + "TRIM", + "UNIX_TIMESTAMP", + "UPPER", + "UUID", + "YEAR" + ] + } + } + ] +} diff --git a/tests/autocomplete/cases/order.json b/tests/autocomplete/cases/order.json new file mode 100644 index 0000000..6b77c1f --- /dev/null +++ b/tests/autocomplete/cases/order.json @@ -0,0 +1,245 @@ +{ + "group": "ORDER", + "cases": [ + { + "case_id": "ORDER_001", + "title": "ORDER BY without prefix: columns, functions, then sort keywords", + "sql": "SELECT * FROM products ORDER BY |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "ORDER_BY_CLAUSE", + "prefix": null, + "suggestions": [ + "id", + "name", + "price", + "unit_price", + "stock", + "AVG", + "COALESCE", + "CONCAT", + "COUNT", + "DATE", + "GROUP_CONCAT", + "IF", + "IFNULL", + "LENGTH", + "LOWER", + "MAX", + "MIN", + "MONTH", + "NOW", + "NULLIF", + "ROW_NUMBER", + "SUBSTR", + "SUM", + "TRIM", + "UNIX_TIMESTAMP", + "UPPER", + "UUID", + "YEAR" + ] + } + }, + { + "case_id": "ORDER_002", + "title": "ORDER BY with prefix filters columns/functions/keywords", + "sql": "SELECT * FROM customers ORDER BY c|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "ORDER_BY_CLAUSE", + "prefix": "c", + "suggestions": [ + "customers.id", + "customers.name", + "customers.email", + "COALESCE", + "CONCAT", + "COUNT" + ] + } + }, + { + "case_id": "ORDER_003", + "title": "ORDER BY after column + space suggests sort direction keywords and clause keywords", + "sql": "SELECT * FROM items ORDER BY stock |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "ORDER_BY_AFTER_COLUMN", + "prefix": null, + "comment": "After column: sort direction keywords (ASC/DESC) and clause keywords (LIMIT, etc.). MUST NOT suggest columns, functions, or literals.", + "suggestions_contains": [ + "ASC", + "DESC", + "NULLS FIRST", + "NULLS LAST", + "LIMIT" + ], + "suggestions_not_contains": [ + "id", + "stock", + "item_name", + "COUNT", + "SUM", + "NULL", + "CURRENT_DATE" + ] + } + }, + { + "case_id": "ORDER_004", + "title": "ORDER BY after comma suggests more sort keys", + "sql": "SELECT * FROM orders ORDER BY created_at DESC, |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "ORDER_BY_CLAUSE", + "prefix": null, + "comment": "After comma: suggest columns and functions for next sort key. MUST NOT suggest ASC/DESC (no column specified yet) or literals.", + "suggestions_contains": [ + "id", + "user_id", + "total", + "status", + "created_at", + "AVG", + "COUNT", + "SUM" + ], + "suggestions_not_contains": [ + "ASC", + "DESC", + "NULLS FIRST", + "NULLS LAST", + "NULL", + "TRUE", + "FALSE", + "CURRENT_DATE" + ] + } + }, + { + "case_id": "ORDER_005", + "title": "ORDER BY scope includes join table columns (aliases)", + "sql": "SELECT * FROM users u JOIN orders o ON u.id=o.user_id ORDER BY |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "ORDER_BY_CLAUSE", + "prefix": null, + "comment": "ORDER BY without column: suggest columns and functions. MUST NOT suggest ASC/DESC (no column specified yet) or literals.", + "suggestions_contains": [ + "u.id", + "u.name", + "u.email", + "u.status", + "u.created_at", + "o.id", + "o.user_id", + "o.total", + "o.status", + "o.created_at", + "AVG", + "COUNT", + "SUM" + ], + "suggestions_not_contains": [ + "ASC", + "DESC", + "NULLS FIRST", + "NULLS LAST", + "NULL", + "TRUE", + "FALSE", + "CURRENT_DATE" + ] + } + }, + { + "case_id": "ORDER_006", + "title": "ORDER_BY after comma in JOIN with aliases", + "sql": "SELECT * FROM products p JOIN items i ON p.id=i.product_id ORDER BY p.name, |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "ORDER_BY_CLAUSE", + "prefix": null, + "comment": "Comma = next-item rules. Scope columns + functions. MUST NOT suggest ASC/DESC or literals.", + "suggestions_contains": [ + "p.id", + "p.name", + "i.id", + "i.item_name", + "AVG", + "COUNT" + ], + "suggestions_not_contains": [ + "ASC", + "DESC", + "NULLS FIRST", + "NULLS LAST", + "NULL", + "TRUE", + "FALSE", + "CURRENT_DATE" + ] + } + }, + { + "case_id": "ORDER_007", + "title": "ORDER BY after column with prefix filters sort keywords", + "sql": "SELECT * FROM users ORDER BY created_at D|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "ORDER_BY_AFTER_COLUMN", + "prefix": "D", + "comment": "After a complete ORDER BY column, prefix filtering should target sort-direction keywords only.", + "suggestions": [ + "DESC" + ] + } + }, + { + "case_id": "ORDER_008", + "title": "Qualified SELECT style propagates to ORDER BY columns", + "sql": "SELECT u.id FROM users u ORDER BY |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "ORDER_BY_CLAUSE", + "prefix": null, + "comment": "Once query style is qualified, ORDER BY columns stay alias-qualified.", + "suggestions_contains": [ + "u.id", + "u.name", + "u.email" + ], + "suggestions_not_contains": [ + "id", + "name", + "email" + ] + } + } + ] +} diff --git a/tests/autocomplete/cases/out_of_scope_hints.json b/tests/autocomplete/cases/out_of_scope_hints.json new file mode 100644 index 0000000..58a49a1 --- /dev/null +++ b/tests/autocomplete/cases/out_of_scope_hints.json @@ -0,0 +1,86 @@ +{ + "group": "OUT_OF_SCOPE_HINTS", + "cases": [ + { + "case_id": "HINT_001", + "title": "SELECT_LIST scoped prefix includes scope matches and DB-wide expansion", + "sql": "SELECT c| FROM orders", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "SELECT_LIST", + "prefix": "c", + "comment": "Scope-first behavior: prefix suggestions from in-scope columns and matching functions only.", + "suggestions": [ + "created_at", + "COALESCE", + "CONCAT", + "COUNT" + ] + } + }, + { + "case_id": "HINT_002", + "title": "KILLER: Out-of-scope hint when prefix matches NO scope columns, only DB table", + "sql": "SELECT u| FROM products", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "SELECT_LIST", + "prefix": "u", + "comment": "Scope-first behavior: keep in-scope column matches plus prefix-matching functions; no out-of-scope table expansion.", + "suggestions": [ + "unit_price", + "UNIX_TIMESTAMP", + "UPPER", + "UUID" + ] + } + }, + { + "case_id": "HINT_003", + "title": "No out-of-scope hint when prefix matches scope columns", + "sql": "SELECT t| FROM orders", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "SELECT_LIST", + "prefix": "t", + "comment": "Prefix matches in-scope column and functions only.", + "suggestions": [ + "total", + "TRIM" + ] + } + }, + { + "case_id": "HINT_004", + "title": "No out-of-scope hint in WHERE context", + "sql": "SELECT * FROM orders WHERE c|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "WHERE_CLAUSE", + "prefix": "c", + "comment": "WHERE is scope-restricted: only in-scope columns and matching functions.", + "suggestions": [ + "created_at", + "COALESCE", + "CONCAT", + "COUNT", + "CURRENT_DATE", + "CURRENT_TIME", + "CURRENT_TIMESTAMP" + ] + } + } + ] +} diff --git a/tests/autocomplete/cases/perf.json b/tests/autocomplete/cases/perf.json new file mode 100644 index 0000000..9a26045 --- /dev/null +++ b/tests/autocomplete/cases/perf.json @@ -0,0 +1,64 @@ +{ + "group": "LARGE_SCHEMA_GUARDRAILS", + "cases": [ + { + "case_id": "GUARDRAIL_001", + "title": "Big schema WHERE without prefix keeps suggestions scope-safe", + "sql": "SELECT * FROM users WHERE |", + "dialect": "generic", + "current_table": null, + "schema_variant": "big", + "expected": { + "mode": "CONTEXT", + "context": "WHERE_CLAUSE", + "prefix": null, + "guardrail_expected": true, + "suggestions": [ + "AVG", + "COALESCE", + "CONCAT", + "COUNT", + "CURRENT_DATE", + "CURRENT_TIME", + "CURRENT_TIMESTAMP", + "DATE", + "GROUP_CONCAT", + "IF", + "IFNULL", + "LENGTH", + "LOWER", + "MAX", + "MIN", + "MONTH", + "NOW", + "NULLIF", + "PI", + "POW", + "POWER", + "ROW_NUMBER", + "SUBSTR", + "SUM", + "TRIM", + "UNIX_TIMESTAMP", + "UPPER", + "UUID", + "YEAR" + ] + } + }, + { + "case_id": "GUARDRAIL_002", + "title": "Big schema WHERE prefix does not leak out-of-scope db-wide columns", + "sql": "SELECT * FROM users WHERE col_0|", + "dialect": "generic", + "current_table": null, + "schema_variant": "big", + "expected": { + "mode": "PREFIX", + "context": "WHERE_CLAUSE", + "prefix": "col_0", + "suggestions": [] + } + } + ] +} diff --git a/tests/autocomplete/cases/prefix_expansion.json b/tests/autocomplete/cases/prefix_expansion.json new file mode 100644 index 0000000..53055af --- /dev/null +++ b/tests/autocomplete/cases/prefix_expansion.json @@ -0,0 +1,137 @@ +{ + "group": "PREFIX_EXPANSION", + "cases": [ + { + "case_id": "PREFIX_EXP_001", + "title": "WHERE single-table: prefix matches BOTH columns and table name", + "sql": "SELECT * FROM items WHERE i|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "WHERE_CLAUSE", + "prefix": "i", + "comment": "Rule BOTH-match (single table): unqualified column-name matches, then qualified versions, then table expansion remaining columns, then functions.", + "suggestions": [ + "id", + "item_name", + "items.id", + "items.item_name", + "items.stock", + "items.price", + "IF", + "IFNULL" + ] + } + }, + { + "case_id": "PREFIX_EXP_002", + "title": "WHERE single-table: prefix matches ONLY table name", + "sql": "SELECT * FROM products WHERE prod|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "WHERE_CLAUSE", + "prefix": "prod", + "comment": "Table-name expansion only: all products.* columns qualified in schema order. No function starts with 'prod'.", + "suggestions": [ + "products.id", + "products.name", + "products.price", + "products.unit_price", + "products.stock" + ] + } + }, + { + "case_id": "PREFIX_EXP_003", + "title": "JOIN_ON generic prefix without aliases", + "sql": "SELECT * FROM users JOIN orders ON u|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "JOIN_ON", + "prefix": "u", + "comment": "Prefix-matching JOIN_ON includes FK complete-condition hints first, then scoped columns and functions.", + "suggestions": [ + "users.id = orders.user_id", + "users.id", + "users.name", + "users.email", + "users.status", + "users.created_at", + "orders.user_id", + "UNIX_TIMESTAMP", + "UPPER", + "UUID" + ] + } + }, + { + "case_id": "PREFIX_EXP_004", + "title": "GROUP_BY single-table BOTH-match with different schema", + "sql": "SELECT COUNT(*) FROM carts GROUP BY c|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "GROUP_BY_CLAUSE", + "prefix": "c", + "comment": "BOTH-match ordering in GROUP BY with single table carts.", + "suggestions": [ + "customer_id", + "created_at", + "cart_total", + "carts.customer_id", + "carts.created_at", + "carts.cart_total", + "carts.id", + "COALESCE", + "CONCAT", + "COUNT" + ] + } + }, + { + "case_id": "PREFIX_EXP_005", + "title": "HAVING: aggregate-first ordering with prefix", + "sql": "SELECT method, COUNT(*) FROM payments GROUP BY method HAVING m|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "HAVING_CLAUSE", + "prefix": "m", + "comment": "HAVING with prefix: aggregate functions first, then columns, then non-aggregate functions.", + "suggestions": [ + "MAX", + "MIN", + "method", + "MONTH" + ] + } + }, + { + "case_id": "PREFIX_EXP_006", + "title": "WHERE with alias mismatch: no invalid table-name qualification", + "sql": "SELECT * FROM user_sessions us WHERE user_s|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "WHERE_CLAUSE", + "prefix": "user_s", + "comment": "Alias is 'us'. Prefix does not exactly match alias and no scoped column/function starts with 'user_s'. Must return empty.", + "suggestions": [] + } + } + ] +} diff --git a/tests/autocomplete/cases/sel.json b/tests/autocomplete/cases/sel.json new file mode 100644 index 0000000..a41fba6 --- /dev/null +++ b/tests/autocomplete/cases/sel.json @@ -0,0 +1,143 @@ +{ + "group": "SEL", + "cases": [ + { + "case_id": "SEL_LIST_001", + "title": "SELECT_LIST without FROM/JOIN shows functions + clause keywords", + "sql": "SELECT |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "SELECT_LIST", + "prefix": null, + "suggestions": [ + "*", + "AVG", + "COALESCE", + "CONCAT", + "COUNT", + "DATE", + "GROUP_CONCAT", + "IF", + "IFNULL", + "LENGTH", + "LOWER", + "MAX", + "MIN", + "MONTH", + "NOW", + "NULLIF", + "PI", + "POW", + "POWER", + "ROW_NUMBER", + "SUBSTR", + "SUM", + "TRIM", + "UNIX_TIMESTAMP", + "UPPER", + "UUID", + "YEAR" + ] + } + }, + { + "case_id": "SEL_LIST_005", + "title": "SELECT_LIST with scope - prefix matches scope table only", + "sql": "SELECT u| FROM users", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "SELECT_LIST", + "prefix": "u", + "suggestions": [ + "users.*", + "users.id", + "users.name", + "users.email", + "users.status", + "users.created_at", + "UNIX_TIMESTAMP", + "UPPER", + "UUID" + ] + } + }, + { + "case_id": "SEL_LIST_006", + "title": "SELECT_LIST after comma suggests columns in scope + functions", + "sql": "SELECT id, | FROM users u JOIN orders o ON u.id = o.user_id", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "SELECT_LIST", + "prefix": null, + "suggestions": [ + "*", + "u.*", + "o.*", + "o.id", + "o.user_id", + "o.total", + "o.status", + "o.created_at", + "u.id", + "u.name", + "u.email", + "u.status", + "u.created_at", + "AVG", + "COALESCE", + "CONCAT", + "COUNT", + "DATE", + "GROUP_CONCAT", + "IF", + "IFNULL", + "LENGTH", + "LOWER", + "MAX", + "MIN", + "MONTH", + "NOW", + "NULLIF", + "PI", + "POW", + "POWER", + "ROW_NUMBER", + "SUBSTR", + "SUM", + "TRIM", + "UNIX_TIMESTAMP", + "UPPER", + "UUID", + "YEAR" + ] + } + }, + { + "case_id": "SEL_LIST_007", + "title": "After complete column + space suggests clause keywords", + "sql": "SELECT users.id |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "SELECT_LIST", + "prefix": null, + "suggestions": [ + "FROM users", + "AS", + "FROM" + ] + } + } + ] +} diff --git a/tests/autocomplete/cases/select_column_behavior.json b/tests/autocomplete/cases/select_column_behavior.json new file mode 100644 index 0000000..5734c28 --- /dev/null +++ b/tests/autocomplete/cases/select_column_behavior.json @@ -0,0 +1,261 @@ +{ + "group": "SELECT_COLUMN_BEHAVIOR", + "cases": [ + { + "case_id": "NO_SCOPED_WHITESPACE_001", + "title": "After column + space suggests clause keywords only", + "sql": "SELECT id |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "prefix": null, + "comment": "Whitespace after column = selection complete. AS for alias, FROM to continue query building.", + "suggestions": [ + "AS", + "FROM" + ], + "mode": "CONTEXT", + "context": "SELECT_LIST" + } + }, + { + "case_id": "NO_SCOPED_WHITESPACE_002", + "title": "After unqualified column + space + prefix: suggest ONLY matching keywords", + "sql": "SELECT id F", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "SELECT_LIST", + "prefix": "F", + "comment": "Whitespace after column = selection complete. With prefix, show ONLY matching keywords.", + "suggestions": [ + "FROM" + ] + } + }, + { + "case_id": "NO_SCOPED_WHITESPACE_003", + "title": "After unqualified column + space + prefix A: suggest AS only", + "sql": "SELECT name A", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "SELECT_LIST", + "prefix": "A", + "comment": "Whitespace after column = selection complete. Prefix A matches AS keyword only.", + "suggestions": [ + "AS" + ] + } + }, + { + "case_id": "NO_SCOPED_WILDCARD_001", + "title": "After wildcard + space + prefix: suggest clause keyword only", + "sql": "SELECT * F", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "SELECT_LIST", + "prefix": "F", + "comment": "Wildcard is a completed SELECT item. Prefix should continue clause keywords, not DB-wide columns.", + "suggestions": [ + "FROM" + ] + } + }, + { + "case_id": "NO_SCOPED_WILDCARD_002", + "title": "After wildcard + space: suggest FROM only", + "sql": "SELECT * |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "SELECT_LIST", + "prefix": null, + "comment": "Wildcard is a completed projection and cannot be aliased with AS.", + "suggestions": [ + "FROM" + ] + } + }, + { + "case_id": "NO_SCOPED_COMMA_001", + "title": "After column + comma suggests next-item (columns + functions)", + "sql": "SELECT id, |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "SELECT_LIST", + "prefix": null, + "comment": "Comma = next-item rules. With no scope and no prefix, expect functions.", + "suggestions_contains": [ + "COUNT", + "UUID" + ], + "suggestions_not_contains": [] + } + }, + { + "case_id": "VIRTUAL_SCOPED_WHITESPACE_001", + "title": "After qualified column + space: suggest ONLY keywords, NOT functions", + "sql": "SELECT products.id ", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "SELECT_LIST", + "prefix": null, + "comment": "Whitespace after qualified column = selection complete. Contextual keyword first, then plain keywords.", + "suggestions": [ + "FROM products", + "AS", + "FROM" + ] + } + }, + { + "case_id": "VIRTUAL_SCOPED_WHITESPACE_002", + "title": "After qualified column + space + prefix: suggest ONLY matching keywords", + "sql": "SELECT products.id F", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "SELECT_LIST", + "prefix": "F", + "comment": "Whitespace after qualified column = selection complete. Even with prefix, show ONLY keywords (not functions/columns).", + "suggestions": [ + "FROM products", + "FROM" + ] + } + }, + { + "case_id": "VIRTUAL_SCOPED_WHITESPACE_003", + "title": "After qualified column + space + prefix W: no valid suggestions", + "sql": "SELECT orders.total W", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "SELECT_LIST", + "prefix": "W", + "comment": "After a qualified select item, WHERE is not valid in this position. No suggestions.", + "suggestions": [] + } + }, + { + "case_id": "VIRTUAL_SCOPED_COMMA_001", + "title": "After qualified column + comma: suggest columns and functions", + "sql": "SELECT items.id, ", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "SELECT_LIST", + "prefix": null, + "comment": "Comma = next item. Previous item is qualified, so suggest columns from the same qualifier even with no scope/prefix.", + "suggestions_contains": [ + "items.item_name", + "items.price", + "COUNT", + "UUID" + ], + "suggestions_not_contains": [] + } + }, + { + "case_id": "VIRTUAL_SCOPED_COMMA_002", + "title": "After qualified column + comma + prefix: suggest matching same-table columns + functions", + "sql": "SELECT items.id, p", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "SELECT_LIST", + "prefix": "p", + "comment": "Comma + prefix. Previous item qualified, so suggest same-table columns starting with 'p' + functions.", + "suggestions_contains": [ + "items.price" + ], + "suggestions_not_contains": [] + } + }, + { + "case_id": "VIRTUAL_SCOPED_CURRENT_TABLE_001", + "title": "VIRTUAL_SCOPED via CURRENT_TABLE + comma: suggest current table columns (qualified) + functions", + "sql": "SELECT id, ", + "dialect": "generic", + "current_table": "orders", + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "SELECT_LIST", + "prefix": null, + "comment": "Comma = next-item. CURRENT_TABLE creates virtual scope. Columns MUST be qualified (table.column) when no FROM/JOIN exists.", + "suggestions_contains": [ + "orders.id", + "orders.user_id", + "orders.total", + "COUNT", + "UUID" + ], + "suggestions_not_contains": [] + } + }, + { + "case_id": "VIRTUAL_SCOPED_CURRENT_TABLE_002", + "title": "VIRTUAL_SCOPED via CURRENT_TABLE + whitespace: suggest contextual keyword + plain keywords", + "sql": "SELECT id ", + "dialect": "generic", + "current_table": "products", + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "SELECT_LIST", + "prefix": null, + "comment": "Whitespace after column = selection complete. CURRENT_TABLE creates virtual scope. Contextual keyword FROM {table} first, then plain keywords.", + "suggestions": [ + "FROM products", + "AS", + "FROM" + ] + } + }, + { + "case_id": "VIRTUAL_SCOPED_WILDCARD_001", + "title": "After alias wildcard + space: suggest real FROM targets, not alias as table", + "sql": "SELECT u.* |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "DOT", + "context": "DOT_COMPLETION", + "prefix": null, + "comment": "Alias wildcard is completed. FROM suggestions must map to real tables and include alias assignment.", + "suggestions": [ + "FROM users u", + "FROM user_sessions u", + "FROM" + ] + } + } + ] +} diff --git a/tests/autocomplete/cases/select_prefix.json b/tests/autocomplete/cases/select_prefix.json new file mode 100644 index 0000000..f8e6627 --- /dev/null +++ b/tests/autocomplete/cases/select_prefix.json @@ -0,0 +1,157 @@ +{ + "group": "SELECT_PREFIX", + "cases": [ + { + "case_id": "SELECT_PREFIX_001", + "title": "SELECT without scope and no CURRENT_TABLE - DB-wide columns allowed", + "sql": "SELECT p|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "SELECT_LIST", + "prefix": "p", + "comment": "No scope. Table-name expansion: products.* and payments.* in schema order (both match prefix 'p').", + "suggestions": [ + "products.id", + "products.name", + "products.price", + "products.unit_price", + "products.stock", + "payments.id", + "payments.order_id", + "payments.amount", + "payments.method", + "payments.created_at", + "items.price", + "inventory.product_id", + "PI", + "POW", + "POWER" + ] + } + }, + { + "case_id": "SELECT_PREFIX_002", + "title": "SELECT without scope - deduplication between table expansion and column match", + "sql": "SELECT u|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "SELECT_LIST", + "prefix": "u", + "comment": "Table-name expansion: users.* in schema order. Column-name matching: orders.user_id, products.unit_price.", + "suggestions": [ + "users.id", + "users.name", + "users.email", + "users.status", + "users.created_at", + "user_sessions.id", + "user_sessions.user_id", + "user_sessions.session_token", + "user_sessions.expires_at", + "orders.user_id", + "products.unit_price", + "UNIX_TIMESTAMP", + "UPPER", + "UUID" + ] + } + }, + { + "case_id": "SELECT_PREFIX_004", + "title": "CURRENT_TABLE behavior is scoped to current statement (multi-query)", + "sql": "SELECT * FROM users u WHERE id = 1; SELECT u|", + "dialect": "generic", + "current_table": "users", + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "SELECT_LIST", + "prefix": "u", + "suggestions": [ + "users.id", + "users.name", + "users.email", + "users.status", + "users.created_at", + "user_sessions.id", + "user_sessions.user_id", + "user_sessions.session_token", + "user_sessions.expires_at", + "orders.user_id", + "products.unit_price", + "UNIX_TIMESTAMP", + "UPPER", + "UUID" + ] + } + }, + { + "case_id": "SELECT_PREFIX_005", + "title": "CURRENT_TABLE table-name expansion competes with other db-wide table expansions", + "sql": "SELECT c|", + "dialect": "generic", + "current_table": "customers", + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "SELECT_LIST", + "prefix": "c", + "suggestions": [ + "customers.id", + "customers.name", + "customers.email", + "carts.id", + "carts.customer_id", + "carts.created_at", + "carts.cart_total", + "users.created_at", + "orders.created_at", + "payments.created_at", + "COALESCE", + "CONCAT", + "COUNT" + ] + } + }, + { + "case_id": "SELECT_PREFIX_006", + "title": "CURRENT_TABLE expansion + db-wide expansion + id column-name match + I* functions", + "sql": "SELECT i|", + "dialect": "generic", + "current_table": "items", + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "SELECT_LIST", + "prefix": "i", + "suggestions": [ + "items.id", + "items.item_name", + "items.stock", + "items.price", + "inventory.id", + "inventory.product_id", + "inventory.quantity", + "inventory.warehouse_location", + "users.id", + "orders.id", + "products.id", + "customers.id", + "payments.id", + "user_sessions.id", + "carts.id", + "orders_archive.id", + "IF", + "IFNULL" + ], + "note": "Ordering: (1) table-name expansion for tables starting with prefix (schema order per table), (2) db-wide column-name matches for prefix (table name schema order), (3) functions filtered by prefix (alphabetical)." + } + } + ] +} diff --git a/tests/autocomplete/cases/select_scoped_current_table.json b/tests/autocomplete/cases/select_scoped_current_table.json new file mode 100644 index 0000000..d82a695 --- /dev/null +++ b/tests/autocomplete/cases/select_scoped_current_table.json @@ -0,0 +1,72 @@ +{ + "group": "SELECT_SCOPED_CURRENT_TABLE", + "cases": [ + { + "case_id": "SCOPED_CURRENT_TABLE_001", + "title": "Single table scope - prefix matches no scope columns, only functions", + "sql": "SELECT p| FROM orders", + "dialect": "generic", + "current_table": "products", + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "SELECT_LIST", + "prefix": "p", + "comment": "Scope=[orders]. Single table. CURRENT_TABLE=products not in scope (ignored). Prefix 'p' matches only out-of-scope tables, so show table hints.", + "suggestions": [ + "PI", + "POW", + "POWER", + "products (+ Add via FROM/JOIN)", + "payments (+ Add via FROM/JOIN)" + ] + } + }, + { + "case_id": "SCOPED_CURRENT_TABLE_002", + "title": "Single table scope - prefix matches BOTH column name AND table name", + "sql": "SELECT p| FROM products", + "dialect": "generic", + "current_table": "orders", + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "SELECT_LIST", + "prefix": "p", + "comment": "Scope=[products]. Single table. CURRENT_TABLE=orders not in scope (ignored). Prefix 'p' returns unqualified match first, then qualified match, then remaining qualified columns in schema order.", + "suggestions": [ + "products.*", + "price", + "products.price", + "products.id", + "products.name", + "products.unit_price", + "products.stock", + "PI", + "POW", + "POWER" + ] + } + }, + { + "case_id": "SCOPED_CURRENT_TABLE_004", + "title": "Single table scope - prefix matches ONLY column name (not table name)", + "sql": "SELECT u| FROM orders", + "dialect": "generic", + "current_table": "items", + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "SELECT_LIST", + "prefix": "u", + "comment": "Scope=[orders]. Single table. CURRENT_TABLE=items not in scope (ignored). Prefix 'u' matches only unqualified column 'user_id', then matching functions.", + "suggestions": [ + "user_id", + "UNIX_TIMESTAMP", + "UPPER", + "UUID" + ] + } + } + ] +} diff --git a/tests/autocomplete/cases/single.json b/tests/autocomplete/cases/single.json new file mode 100644 index 0000000..86a5f3d --- /dev/null +++ b/tests/autocomplete/cases/single.json @@ -0,0 +1,101 @@ +{ + "group": "SINGLE", + "cases": [ + { + "case_id": "SINGLE_001", + "title": "SINGLE_TOKEN keyword completion for SEL|", + "sql": "SEL|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "SINGLE_TOKEN", + "prefix": "SEL", + "suggestions": [ + "SELECT" + ] + } + }, + { + "case_id": "SINGLE_002", + "title": "SINGLE_TOKEN keyword completion for INS|", + "sql": "INS|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "SINGLE_TOKEN", + "prefix": "INS", + "suggestions": [ + "INSERT" + ] + } + }, + { + "case_id": "SINGLE_003", + "title": "SINGLE_TOKEN keyword completion for UPD|", + "sql": "UPD|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "SINGLE_TOKEN", + "prefix": "UPD", + "suggestions": [ + "UPDATE" + ] + } + }, + { + "case_id": "SINGLE_004", + "title": "SINGLE_TOKEN keyword completion for DELE|", + "sql": "DELE|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "SINGLE_TOKEN", + "prefix": "DELE", + "suggestions": [ + "DELETE" + ] + } + }, + { + "case_id": "SINGLE_005", + "title": "SINGLE_TOKEN keyword completion for CRE|", + "sql": "CRE|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "SINGLE_TOKEN", + "prefix": "CRE", + "suggestions": [ + "CREATE" + ] + } + }, + { + "case_id": "SINGLE_006", + "title": "SINGLE_TOKEN keyword completion for WIT|", + "sql": "WIT|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "SINGLE_TOKEN", + "prefix": "WIT", + "suggestions": [ + "WITH" + ] + } + } + ] +} diff --git a/tests/autocomplete/cases/where.json b/tests/autocomplete/cases/where.json new file mode 100644 index 0000000..bc72adb --- /dev/null +++ b/tests/autocomplete/cases/where.json @@ -0,0 +1,400 @@ +{ + "group": "WHERE", + "cases": [ + { + "case_id": "WHERE_001", + "title": "WHERE without prefix suggests columns + functions", + "sql": "SELECT * FROM products WHERE |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "WHERE_CLAUSE", + "prefix": null, + "suggestions": [ + "id", + "name", + "price", + "unit_price", + "stock", + "AVG", + "COALESCE", + "CONCAT", + "COUNT", + "CURRENT_DATE", + "CURRENT_TIME", + "CURRENT_TIMESTAMP", + "DATE", + "GROUP_CONCAT", + "IF", + "IFNULL", + "LENGTH", + "LOWER", + "MAX", + "MIN", + "MONTH", + "NOW", + "NULLIF", + "PI", + "POW", + "POWER", + "ROW_NUMBER", + "SUBSTR", + "SUM", + "TRIM", + "UNIX_TIMESTAMP", + "UPPER", + "UUID", + "YEAR" + ] + } + }, + { + "case_id": "WHERE_002", + "title": "WHERE alias exact match suggests alias columns + matching functions", + "sql": "SELECT * FROM orders o WHERE o|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "WHERE_CLAUSE", + "prefix": "o", + "alias_exact_match": "o", + "suggestions": [ + "o.id", + "o.user_id", + "o.total", + "o.status", + "o.created_at" + ] + } + }, + { + "case_id": "WHERE_003", + "title": "WHERE after operator suggests literals + columns + functions", + "sql": "SELECT * FROM items WHERE quantity = |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "AFTER_OPERATOR", + "context": "WHERE_AFTER_OPERATOR", + "prefix": null, + "suggestions": [ + "NULL", + "TRUE", + "FALSE", + "CURRENT_DATE", + "CURRENT_TIME", + "CURRENT_TIMESTAMP", + "id", + "item_name", + "stock", + "price", + "AVG", + "COALESCE", + "CONCAT", + "COUNT", + "CURRENT_DATE", + "CURRENT_TIME", + "CURRENT_TIMESTAMP", + "DATE", + "GROUP_CONCAT", + "IF", + "IFNULL", + "LENGTH", + "LOWER", + "MAX", + "MIN", + "MONTH", + "NOW", + "NULLIF", + "PI", + "POW", + "POWER", + "ROW_NUMBER", + "SUBSTR", + "SUM", + "TRIM", + "UNIX_TIMESTAMP", + "UPPER", + "UUID", + "YEAR" + ] + } + }, + { + "case_id": "WHERE_004", + "title": "WHERE after expression suggests logical/expression keywords + clauses", + "sql": "SELECT * FROM users WHERE id = 1 |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "AFTER_EXPRESSION", + "context": "WHERE_AFTER_EXPRESSION", + "prefix": null, + "suggestions": [ + "AND", + "OR", + "GROUP BY", + "HAVING", + "LIMIT", + "ORDER BY" + ] + } + }, + { + "case_id": "WHERE_005", + "title": "WHERE with JOIN scope includes both tables and uses aliases", + "sql": "SELECT * FROM products p JOIN items i ON p.id=i.product_id WHERE |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "WHERE_CLAUSE", + "prefix": null, + "suggestions": [ + "p.id", + "p.name", + "p.price", + "p.unit_price", + "p.stock", + "i.id", + "i.item_name", + "i.stock", + "i.price", + "AVG", + "COALESCE", + "CONCAT", + "COUNT", + "CURRENT_DATE", + "CURRENT_TIME", + "CURRENT_TIMESTAMP", + "DATE", + "GROUP_CONCAT", + "IF", + "IFNULL", + "LENGTH", + "LOWER", + "MAX", + "MIN", + "MONTH", + "NOW", + "NULLIF", + "PI", + "POW", + "POWER", + "ROW_NUMBER", + "SUBSTR", + "SUM", + "TRIM", + "UNIX_TIMESTAMP", + "UPPER", + "UUID", + "YEAR" + ] + } + }, + { + "case_id": "WHERE_006", + "title": "WHERE generic prefix with scope restriction - no DB-wide columns", + "sql": "SELECT * FROM orders WHERE u|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "WHERE_CLAUSE", + "prefix": "u", + "comment": "Scope-restricted: single table in scope, so column-name match is unqualified (user_id). DB-wide columns excluded.", + "suggestions": [ + "user_id", + "UNIX_TIMESTAMP", + "UPPER", + "UUID" + ] + } + }, + { + "case_id": "WHERE_007", + "title": "WHERE with = operator: do NOT suggest left column", + "sql": "SELECT * FROM products WHERE products.id = ", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "AFTER_OPERATOR", + "context": "WHERE_AFTER_OPERATOR", + "prefix": null, + "comment": "products.id is on left of =, should suggest literals, then other columns + functions.", + "suggestions": [ + "NULL", + "TRUE", + "FALSE", + "CURRENT_DATE", + "CURRENT_TIME", + "CURRENT_TIMESTAMP", + "id", + "name", + "price", + "unit_price", + "stock", + "AVG", + "COALESCE", + "CONCAT", + "COUNT", + "CURRENT_DATE", + "CURRENT_TIME", + "CURRENT_TIMESTAMP", + "DATE", + "GROUP_CONCAT", + "IF", + "IFNULL", + "LENGTH", + "LOWER", + "MAX", + "MIN", + "MONTH", + "NOW", + "NULLIF", + "PI", + "POW", + "POWER", + "ROW_NUMBER", + "SUBSTR", + "SUM", + "TRIM", + "UNIX_TIMESTAMP", + "UPPER", + "UUID", + "YEAR" + ] + } + }, + { + "case_id": "WHERE_008", + "title": "WHERE with prefix: normal behavior (no filtering)", + "sql": "SELECT * FROM products WHERE products.id = p", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "WHERE_CLAUSE", + "prefix": "p", + "comment": "With prefix, no left-column filtering applied. Normal prefix matching includes columns + functions.", + "suggestions": [ + "price", + "products.price", + "products.id", + "products.name", + "products.unit_price", + "products.stock", + "PI", + "POW", + "POWER" + ] + } + }, + { + "case_id": "WHERE_009", + "title": "WHERE IN clause after comma suggests literals", + "sql": "SELECT * FROM products WHERE id IN (1, |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "WHERE_CLAUSE", + "prefix": null, + "comment": "Comma in IN clause = next value. Suggest literals from vocab, NOT columns or aggregate functions.", + "suggestions_contains": [ + "NULL", + "TRUE", + "FALSE", + "CURRENT_DATE", + "CURRENT_TIME", + "CURRENT_TIMESTAMP" + ], + "suggestions_not_contains": [ + "products.id", + "products.name", + "COUNT", + "SUM" + ] + } + }, + { + "case_id": "WHERE_011", + "title": "After IS keyword suggests NULL, NOT NULL, TRUE, FALSE", + "sql": "SELECT * FROM products WHERE status IS |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "WHERE_AFTER_EXPRESSION", + "prefix": null, + "comment": "After IS, suggest NULL, NOT NULL, TRUE, FALSE (not IS NULL/IS NOT NULL to avoid IS IS NULL).", + "suggestions": [ + "NULL", + "NOT NULL", + "TRUE", + "FALSE" + ] + } + }, + { + "case_id": "WHERE_012", + "title": "Qualified SELECT style propagates to WHERE suggestions", + "sql": "SELECT users.id FROM users WHERE |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "WHERE_CLAUSE", + "prefix": null, + "comment": "When SELECT already uses qualified columns, WHERE suggestions stay qualified for consistency.", + "suggestions_contains": [ + "users.id", + "users.name", + "users.email" + ], + "suggestions_not_contains": [ + "id", + "name", + "email" + ] + } + }, + { + "case_id": "WHERE_013", + "title": "Alias in scope forces qualified WHERE suggestions", + "sql": "SELECT * FROM users u WHERE |", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "WHERE_CLAUSE", + "prefix": null, + "comment": "When table has alias, WHERE columns must stay alias-qualified.", + "suggestions_contains": [ + "u.id", + "u.name", + "u.email" + ], + "suggestions_not_contains": [ + "id", + "name", + "email" + ] + } + } + ] +} diff --git a/tests/autocomplete/cases/where_scoped.json b/tests/autocomplete/cases/where_scoped.json new file mode 100644 index 0000000..11ac6c9 --- /dev/null +++ b/tests/autocomplete/cases/where_scoped.json @@ -0,0 +1,82 @@ +{ + "group": "WHERE_SCOPED", + "cases": [ + { + "case_id": "WHERE_SCOPE_001", + "title": "WHERE with scope - no DB-wide columns, only scope tables", + "sql": "SELECT * FROM items WHERE z|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "WHERE_CLAUSE", + "prefix": "z", + "comment": "Scope=[items]. No scope column starts with 'z'. DB-wide columns excluded. Only functions.", + "suggestions": [] + } + }, + { + "case_id": "WHERE_SCOPE_002", + "title": "WHERE with JOIN scope - only scope table columns", + "sql": "SELECT * FROM products p JOIN items i ON p.id=i.product_id WHERE q|", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "WHERE_CLAUSE", + "prefix": "q", + "comment": "Scope=[products as p, items as i]. No scoped columns/functions start with 'q'. DB-wide excluded.", + "suggestions": [] + } + }, + { + "case_id": "WHERE_SCOPE_003", + "title": "WHERE with CURRENT_TABLE not in scope - CURRENT_TABLE ignored", + "sql": "SELECT * FROM products WHERE c|", + "dialect": "generic", + "current_table": "users", + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "WHERE_CLAUSE", + "prefix": "c", + "comment": "Scope=[products]. CURRENT_TABLE=users not in scope. Functions matching 'c' are still allowed.", + "suggestions": [ + "COALESCE", + "CONCAT", + "COUNT", + "CURRENT_DATE", + "CURRENT_TIME", + "CURRENT_TIMESTAMP" + ] + } + }, + { + "case_id": "WHERE_SCOPE_004", + "title": "WHERE with CURRENT_TABLE in scope - CURRENT_TABLE included", + "sql": "SELECT * FROM customers WHERE c|", + "dialect": "generic", + "current_table": "customers", + "schema_variant": "small", + "expected": { + "mode": "PREFIX", + "context": "WHERE_CLAUSE", + "prefix": "c", + "comment": "Scope=[customers]. Prefix matches table-name expansion, so columns are qualified.", + "suggestions": [ + "customers.id", + "customers.name", + "customers.email", + "COALESCE", + "CONCAT", + "COUNT", + "CURRENT_DATE", + "CURRENT_TIME", + "CURRENT_TIMESTAMP" + ] + } + } + ] +} diff --git a/tests/autocomplete/cases/window_functions_over.json b/tests/autocomplete/cases/window_functions_over.json new file mode 100644 index 0000000..80a9dbb --- /dev/null +++ b/tests/autocomplete/cases/window_functions_over.json @@ -0,0 +1,22 @@ +{ + "group": "WINDOW_FUNCTIONS_OVER", + "cases": [ + { + "case_id": "WINDOW_OVER_001", + "title": "Window function OVER clause suggests partition/order keywords", + "sql": "SELECT *, ROW_NUMBER() OVER | FROM users", + "dialect": "generic", + "current_table": null, + "schema_variant": "small", + "expected": { + "mode": "CONTEXT", + "context": "WINDOW_OVER", + "prefix": null, + "suggestions": [ + "ORDER BY", + "PARTITION BY" + ] + } + } + ] +} diff --git a/tests/autocomplete/test_autocomplete_basic.py b/tests/autocomplete/test_autocomplete_basic.py new file mode 100644 index 0000000..582062f --- /dev/null +++ b/tests/autocomplete/test_autocomplete_basic.py @@ -0,0 +1,246 @@ +from typing import Optional +from unittest.mock import Mock + +from windows.components.stc.autocomplete.auto_complete import SQLCompletionProvider +from windows.components.stc.autocomplete.completion_types import CompletionItemType + + +def create_mock_column(col_id: int, name: str, table): + column = Mock() + column.id = col_id + column.name = name + column.table = table + column.datatype = None + return column + + +def create_mock_table(table_id: int, name: str, database, columns_data): + table = Mock() + table.id = table_id + table.name = name + table.database = database + + columns = [ + create_mock_column(i, col_name, table) + for i, col_name in enumerate(columns_data, 1) + ] + table.columns = columns + + return table + + +def create_mock_database(): + context = Mock() + context.KEYWORDS = [ + "SELECT", + "FROM", + "WHERE", + "INSERT", + "UPDATE", + "DELETE", + "JOIN", + "ORDER BY", + "GROUP BY", + "HAVING", + "LIMIT", + "ASC", + "DESC", + ] + context.FUNCTIONS = [ + "COUNT", + "SUM", + "AVG", + "MAX", + "MIN", + "UPPER", + "LOWER", + "CONCAT", + ] + + database = Mock() + database.id = 1 + database.name = "test_db" + database.context = context + + users_table = create_mock_table( + 1, "users", database, ["id", "name", "email", "created_at", "status"] + ) + + orders_table = create_mock_table( + 2, "orders", database, ["id", "user_id", "total", "status", "created_at"] + ) + + database.tables = [users_table, orders_table] + + return database + + +def test_empty_context(): + database = create_mock_database() + provider = SQLCompletionProvider( + get_database=lambda: database, get_current_table=lambda: None + ) + + result = provider.get(text="", pos=0) + + assert result is not None + assert len(result.items) > 0 + + item_names = [item.name for item in result.items] + assert "SELECT" in item_names + assert "INSERT" in item_names + assert "UPDATE" in item_names + + print("✓ GT-010 EMPTY context test passed") + + +def test_single_token(): + database = create_mock_database() + provider = SQLCompletionProvider( + get_database=lambda: database, get_current_table=lambda: None + ) + + result = provider.get(text="SEL", pos=3) + + assert result is not None + assert len(result.items) > 0 + assert result.prefix == "SEL" + + item_names = [item.name for item in result.items] + assert "SELECT" in item_names + + print("✓ GT-011 SINGLE_TOKEN test passed") + + +def test_select_without_from(): + database = create_mock_database() + provider = SQLCompletionProvider( + get_database=lambda: database, get_current_table=lambda: None + ) + + result = provider.get(text="SELECT ", pos=7) + + assert result is not None + assert len(result.items) > 0 + + item_names = [item.name for item in result.items] + assert "COUNT" in item_names + assert "SUM" in item_names + assert "*" in item_names + + print("✓ GT-020 SELECT without FROM test passed") + + +def test_select_with_from(): + database = create_mock_database() + provider = SQLCompletionProvider( + get_database=lambda: database, get_current_table=lambda: None + ) + + result = provider.get(text="SELECT FROM users", pos=7) + + assert result is not None + assert len(result.items) > 0 + + item_names = [item.name for item in result.items] + + assert "users.id" in item_names + assert "users.name" in item_names + assert "COUNT" in item_names + + print("✓ GT-021 SELECT with FROM test passed") + + +def test_where_basic(): + database = create_mock_database() + provider = SQLCompletionProvider( + get_database=lambda: database, get_current_table=lambda: None + ) + + result = provider.get(text="SELECT * FROM users WHERE ", pos=27) + + assert result is not None + assert len(result.items) > 0 + + item_names = [item.name for item in result.items] + + assert "id" in item_names + assert "name" in item_names + assert "COUNT" in item_names + + print("✓ GT-030 WHERE basic test passed") + + +def test_from_clause(): + database = create_mock_database() + provider = SQLCompletionProvider( + get_database=lambda: database, get_current_table=lambda: None + ) + + result = provider.get(text="SELECT * FROM ", pos=14) + + assert result is not None + assert len(result.items) > 0 + + item_names = [item.name for item in result.items] + assert "users" in item_names + assert "orders" in item_names + + print("✓ FROM clause test passed") + + +def test_dot_completion(): + database = create_mock_database() + provider = SQLCompletionProvider( + get_database=lambda: database, get_current_table=lambda: None + ) + + result = provider.get(text="SELECT users.", pos=13) + + assert result is not None + assert len(result.items) > 0 + + item_names = [item.name for item in result.items] + assert "id" in item_names + assert "name" in item_names + assert "email" in item_names + + for name in item_names: + assert "users." not in name + + print("✓ GT-002 Dot completion test passed") + + +def test_multi_query(): + database = create_mock_database() + provider = SQLCompletionProvider( + get_database=lambda: database, get_current_table=lambda: None + ) + + text = "SELECT * FROM users;\nSELECT * FROM orders WHERE " + pos = len(text) + + result = provider.get(text=text, pos=pos) + + assert result is not None + assert len(result.items) > 0 + + item_names = [item.name for item in result.items] + + assert "id" in item_names + assert "user_id" in item_names + + print("✓ GT-001 Multi-query test passed") + + +if __name__ == "__main__": + test_empty_context() + test_single_token() + test_select_without_from() + test_select_with_from() + test_where_basic() + test_from_clause() + test_dot_completion() + test_multi_query() + + print("\n✅ All basic autocomplete tests passed!") diff --git a/tests/autocomplete/test_config.json b/tests/autocomplete/test_config.json new file mode 100644 index 0000000..e10c61c --- /dev/null +++ b/tests/autocomplete/test_config.json @@ -0,0 +1,137 @@ +{ + "vocab": { + "primary_keywords": [ + "SELECT", "INSERT", "UPDATE", "DELETE", "CREATE", "DROP", "ALTER", + "TRUNCATE", "SHOW", "DESCRIBE", "EXPLAIN", "WITH", "REPLACE", "MERGE" + ], + "keywords_all": [ + "ALTER", "AND", "AS", "ASC", "BETWEEN", "CREATE", "CROSS JOIN", + "DELETE", "DESC", + "DESCRIBE", "DROP", "EXISTS", "EXPLAIN", "FALSE", "FROM", "FULL JOIN", + "GROUP BY", "HAVING", "IN", "INNER JOIN", "INSERT", "IS NOT NULL", + "IS NULL", "JOIN", "LEFT JOIN", "LIKE", "LIMIT", "MERGE", "NOT", "NULL", + "NULLS FIRST", "NULLS LAST", "OFFSET", "ON", "OR", "ORDER BY", "REPLACE", + "RIGHT JOIN", "SELECT", "SHOW", "TRUE", "TRUNCATE", "UPDATE", "USING", + "WHERE", "WITH" + ], + "literals": [ + "NULL", "TRUE", "FALSE", "CURRENT_DATE", "CURRENT_TIME", "CURRENT_TIMESTAMP" + ], + "functions_all": [ + "AVG", "COALESCE", "CONCAT", "COUNT", "DATE", "GROUP_CONCAT", "IF", "IFNULL", + "CURRENT_DATE", "CURRENT_TIME", "CURRENT_TIMESTAMP", "LENGTH", "LOWER", "MAX", "MIN", "MONTH", "NOW", "NULLIF", "ROW_NUMBER", + "PI", "POW", "POWER", "SUBSTR", "SUM", "TRIM", "UNIX_TIMESTAMP", "UPPER", "UUID", "YEAR" + ], + "aggregate_functions": [ + "AVG", "COUNT", "GROUP_CONCAT", "MAX", "MIN", "SUM" + ] + }, + "schema_small": { + "tables": [ + { + "name": "users", + "columns": [ + {"name": "id", "type": "INTEGER"}, + {"name": "name", "type": "VARCHAR"}, + {"name": "email", "type": "VARCHAR"}, + {"name": "status", "type": "VARCHAR"}, + {"name": "created_at", "type": "TIMESTAMP"} + ] + }, + { + "name": "orders", + "columns": [ + {"name": "id", "type": "INTEGER"}, + {"name": "user_id", "type": "INTEGER"}, + {"name": "total", "type": "DECIMAL"}, + {"name": "status", "type": "VARCHAR"}, + {"name": "created_at", "type": "TIMESTAMP"} + ], + "foreign_keys": [ + { + "name": "fk_orders_user_id", + "columns": ["user_id"], + "reference_table": "users", + "reference_columns": ["id"] + } + ] + }, + { + "name": "products", + "columns": [ + {"name": "id", "type": "INTEGER"}, + {"name": "name", "type": "VARCHAR"}, + {"name": "price", "type": "DECIMAL"}, + {"name": "unit_price", "type": "DECIMAL"}, + {"name": "stock", "type": "INTEGER"} + ] + }, + { + "name": "customers", + "columns": [ + {"name": "id", "type": "INTEGER"}, + {"name": "name", "type": "VARCHAR"}, + {"name": "email", "type": "VARCHAR"} + ] + }, + { + "name": "payments", + "columns": [ + {"name": "id", "type": "INTEGER"}, + {"name": "order_id", "type": "INTEGER"}, + {"name": "amount", "type": "DECIMAL"}, + {"name": "method", "type": "VARCHAR"}, + {"name": "created_at", "type": "TIMESTAMP"} + ] + }, + { + "name": "user_sessions", + "columns": [ + {"name": "id", "type": "INTEGER"}, + {"name": "user_id", "type": "INTEGER"}, + {"name": "session_token", "type": "VARCHAR"}, + {"name": "expires_at", "type": "TIMESTAMP"} + ] + }, + { + "name": "items", + "columns": [ + {"name": "id", "type": "INTEGER"}, + {"name": "item_name", "type": "VARCHAR"}, + {"name": "stock", "type": "INTEGER"}, + {"name": "price", "type": "DECIMAL"} + ] + }, + { + "name": "carts", + "columns": [ + {"name": "id", "type": "INTEGER"}, + {"name": "customer_id", "type": "INTEGER"}, + {"name": "created_at", "type": "TIMESTAMP"}, + {"name": "cart_total", "type": "DECIMAL"} + ] + }, + { + "name": "orders_archive", + "columns": [ + {"name": "id", "type": "INTEGER"}, + {"name": "order_id", "type": "INTEGER"}, + {"name": "archived_at", "type": "TIMESTAMP"}, + {"name": "original_total", "type": "DECIMAL"} + ] + }, + { + "name": "inventory", + "columns": [ + {"name": "id", "type": "INTEGER"}, + {"name": "product_id", "type": "INTEGER"}, + {"name": "quantity", "type": "INTEGER"}, + {"name": "warehouse_location", "type": "VARCHAR"} + ] + } + ] + }, + "schema_big": { + "tables": [] + } +} diff --git a/tests/autocomplete/test_golden_cases.py b/tests/autocomplete/test_golden_cases.py new file mode 100644 index 0000000..04371dc --- /dev/null +++ b/tests/autocomplete/test_golden_cases.py @@ -0,0 +1,119 @@ +import json +from pathlib import Path +from typing import Any + +import pytest + +from tests.autocomplete.autocomplete_adapter import AutocompleteRequest +from tests.autocomplete.autocomplete_adapter import SUPPORTED_ENGINE_VERSIONS +from tests.autocomplete.autocomplete_adapter import get_suggestions + + +ROOT = Path(__file__).resolve().parent +CASES_DIR = ROOT / "cases" +CONFIG_PATH = ROOT / "test_config.json" + + +def _load_json(path: Path) -> dict[str, Any]: + return json.loads(path.read_text(encoding="utf-8")) + + +def _iter_cases() -> list[tuple[str, dict[str, Any]]]: + cases: list[tuple[str, dict[str, Any]]] = [] + for path in sorted(CASES_DIR.glob("*.json")): + payload = _load_json(path) + for case in payload["cases"]: + cases.append((path.name, case)) + return cases + + +def _iter_engine_targets() -> list[tuple[str, str]]: + targets: list[tuple[str, str]] = [] + for engine, versions in SUPPORTED_ENGINE_VERSIONS.items(): + for version in versions: + targets.append((engine, version)) + return targets + + +def _schema_for_variant(config: dict[str, Any], schema_variant: str) -> dict[str, Any]: + if schema_variant == "small": + return config["schema_small"] + if schema_variant == "big": + return config["schema_big"] + raise ValueError(f"Unknown schema_variant: {schema_variant}") + + +@pytest.mark.parametrize("engine,engine_version", _iter_engine_targets()) +@pytest.mark.parametrize("file_name,case", _iter_cases()) +def test_golden_case( + file_name: str, + case: dict[str, Any], + engine: str, + engine_version: str, +) -> None: + config = _load_json(CONFIG_PATH) + expected = case["expected"] + + if bool(expected.get("xfail", False)): + pytest.xfail("Marked as future enhancement") + + schema = _schema_for_variant(config, case.get("schema_variant", "small")) + + request = AutocompleteRequest( + sql=case["sql"], + dialect=case.get("dialect", "generic"), + current_table=case.get("current_table"), + schema=schema, + engine=engine, + engine_version=engine_version, + ) + + response = get_suggestions(request) + + assert response.mode == expected["mode"], ( + file_name, + case["case_id"], + engine, + engine_version, + ) + assert response.context == expected["context"], ( + file_name, + case["case_id"], + engine, + engine_version, + ) + assert response.prefix == expected.get("prefix"), ( + file_name, + case["case_id"], + engine, + engine_version, + ) + + if "suggestions" in expected: + assert response.suggestions == expected["suggestions"], ( + file_name, + case["case_id"], + engine, + engine_version, + ) + elif "suggestions_contains" in expected and "suggestions_not_contains" in expected: + for needle in expected["suggestions_contains"]: + assert needle in response.suggestions, ( + file_name, + case["case_id"], + engine, + engine_version, + needle, + ) + for needle in expected["suggestions_not_contains"]: + assert needle not in response.suggestions, ( + file_name, + case["case_id"], + engine, + engine_version, + needle, + ) + else: + raise AssertionError( + "Case must define 'suggestions' OR both 'suggestions_contains' AND 'suggestions_not_contains'" + ) diff --git a/tests/conftest.py b/tests/conftest.py index c1775f6..6a8ab1b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,6 +6,56 @@ from structures.configurations import SourceConfiguration +def _engine_from_nodeid(nodeid: str) -> str | None: + parts = nodeid.split("/") + try: + engines_index = parts.index("engines") + except ValueError: + return None + + if engines_index + 1 >= len(parts): + return None + + return parts[engines_index + 1].lower() + + +def _variant_from_nodeid(nodeid: str) -> str | None: + start = nodeid.rfind("[") + if start == -1: + return None + + end = nodeid.find("]", start) + if end == -1: + return None + + return nodeid[start + 1 : end].lower() + + +def pytest_collection_modifyitems(config, items): + for item in items: + marker = item.get_closest_marker("skip_engine") + if marker is None: + continue + + current_engine = _engine_from_nodeid(item.nodeid) + if current_engine is None: + continue + + current_variant = _variant_from_nodeid(item.nodeid) + + selectors = {str(arg).lower() for arg in marker.args} + should_skip = current_engine in selectors + if not should_skip and current_variant is not None: + should_skip = current_variant in selectors + + if should_skip: + item.add_marker( + pytest.mark.skip( + reason=f"{current_engine.capitalize()} has incompatible API for this operation" + ) + ) + + @pytest.fixture(scope="session", autouse=True) def wx_app(): """Initialize wx.App for GUI tests""" @@ -17,8 +67,10 @@ def wx_app(): @pytest.fixture def sqlite_session(): """Provide an in-memory SQLite session for tests""" - config = SourceConfiguration(filename=':memory:') - connection = Connection(id=1, name='test_session', engine=ConnectionEngine.SQLITE, configuration=config) + config = SourceConfiguration(filename=":memory:") + connection = Connection( + id=1, name="test_session", engine=ConnectionEngine.SQLITE, configuration=config + ) session = Session(connection) session.connect() yield session diff --git a/tests/core/test_connection.py b/tests/core/test_connection.py index 9060e78..e2d6b6e 100644 --- a/tests/core/test_connection.py +++ b/tests/core/test_connection.py @@ -1,7 +1,11 @@ import pytest from structures.connection import Connection, ConnectionEngine -from structures.configurations import CredentialsConfiguration, SourceConfiguration, SSHTunnelConfiguration +from structures.configurations import ( + CredentialsConfiguration, + SourceConfiguration, + SSHTunnelConfiguration, +) class TestConnection: @@ -137,6 +141,22 @@ def test_connection_with_ssh_tunnel(self): assert conn.ssh_tunnel.enabled is True assert conn.ssh_tunnel.hostname == "bastion.example.com" + def test_connection_is_valid_with_empty_password(self): + config = CredentialsConfiguration( + hostname="localhost", + username="root", + password="", + port=3306, + ) + conn = Connection( + id=1, + name="Local", + engine=ConnectionEngine.MYSQL, + configuration=config, + ) + + assert conn.is_valid is True + class TestConnectionEngine: """Tests for ConnectionEngine enum.""" diff --git a/tests/core/test_sql_structures.py b/tests/core/test_sql_structures.py index e47b306..720a2c8 100644 --- a/tests/core/test_sql_structures.py +++ b/tests/core/test_sql_structures.py @@ -205,13 +205,13 @@ def test_database_creation(self, sqlite_session): database = SQLiteDatabase(id=1, name="main", context=sqlite_session.context) assert database.name == "main" - def test_database_sql_safe_name(self, sqlite_session): - """Test sql_safe_name property.""" + def test_database_quoted_name(self, sqlite_session): + """Test quoted_name property.""" database = SQLiteDatabase(id=1, name="main", context=sqlite_session.context) - assert database.sql_safe_name == "main" + assert database.quoted_name == "main" def test_database_with_special_name(self, sqlite_session): """Test database with special name.""" database = SQLiteDatabase(id=1, name="my database", context=sqlite_session.context) - quote = sqlite_session.context.IDENTIFIER_QUOTE - assert database.sql_safe_name == f'{quote}my database{quote}' + quote = sqlite_session.context.IDENTIFIER_QUOTE_CHAR + assert database.quoted_name == f'{quote}my database{quote}' diff --git a/tests/core/test_view_editor.py b/tests/core/test_view_editor.py new file mode 100644 index 0000000..6cc3b3d --- /dev/null +++ b/tests/core/test_view_editor.py @@ -0,0 +1,232 @@ +import pytest +from unittest.mock import Mock, MagicMock, patch, PropertyMock +from helpers.observables import Observable + + +class TestEditViewModel: + """Test EditViewModel class for View Editor.""" + + def test_init_creates_observables(self): + """Test that EditViewModel initializes all required observables.""" + from windows.main.tabs.view import EditViewModel + + model = EditViewModel() + + assert isinstance(model.name, Observable) + assert isinstance(model.schema, Observable) + assert isinstance(model.definer, Observable) + assert isinstance(model.sql_security, Observable) + assert isinstance(model.algorithm, Observable) + assert isinstance(model.constraint, Observable) + assert isinstance(model.security_barrier, Observable) + assert isinstance(model.force, Observable) + assert isinstance(model.select_statement, Observable) + + def test_load_view_sets_name_observable(self): + """Test that _load_view sets name observable from view.""" + from windows.main.tabs.view import EditViewModel + + model = EditViewModel() + + mock_view = Mock() + mock_view.name = "test_view" + mock_view.statement = "SELECT * FROM test" + + with patch('windows.main.tabs.view.CURRENT_SESSION') as mock_session: + mock_session.get_value.return_value = None + model._load_view(mock_view) + + assert model.name.get_value() == "test_view" + assert model.select_statement.get_value() == "SELECT * FROM test" + + def test_update_view_sets_name_and_statement(self): + """Test that update_view sets view name and statement from observables.""" + from windows.main.tabs.view import EditViewModel + + model = EditViewModel() + + mock_view = Mock() + mock_view.name = "" + mock_view.statement = "" + + with patch('windows.main.tabs.view.CURRENT_VIEW') as mock_current_view: + mock_current_view.get_value.return_value = mock_view + + model.name.set_value("updated_view") + model.select_statement.set_value("SELECT id FROM users") + + model.update_view(True) + + assert mock_view.name == "updated_view" + assert mock_view.statement == "SELECT id FROM users" + + +class TestViewEditorController: + """Test ViewEditorController class.""" + + @pytest.fixture + def mock_parent(self): + """Create mock parent with all required UI elements.""" + parent = Mock() + + # Text controls + parent.txt_view_name = Mock() + parent.cho_view_schema = Mock() + parent.cmb_view_definer = Mock() + parent.cho_view_sql_security = Mock() + parent.stc_view_select = Mock() + + # Radio buttons + parent.rad_view_algorithm_undefined = Mock() + parent.rad_view_algorithm_merge = Mock() + parent.rad_view_algorithm_temptable = Mock() + parent.rad_view_constraint_none = Mock() + parent.rad_view_constraint_local = Mock() + parent.rad_view_constraint_cascaded = Mock() + parent.rad_view_constraint_check_only = Mock() + parent.rad_view_constraint_read_only = Mock() + + # Checkboxes + parent.chk_view_security_barrier = Mock() + parent.chk_view_force = Mock() + + # Buttons + parent.btn_save_view = Mock() + parent.btn_delete_view = Mock() + parent.btn_cancel_view = Mock() + + # Panels + parent.pnl_view_editor_root = Mock() + parent.panel_views = Mock() + parent.m_notebook7 = Mock() + + return parent + + def test_init_binds_controls(self, mock_parent): + """Test that controller initializes and binds controls.""" + from windows.main.tabs.view import ViewEditorController + + with patch('windows.main.tabs.view.CURRENT_VIEW') as mock_current_view: + with patch('windows.main.tabs.view.wx_call_after_debounce'): + controller = ViewEditorController(mock_parent) + + assert controller.parent == mock_parent + assert controller.model is not None + assert mock_current_view.subscribe.call_count == 2 + + def test_get_original_view_returns_none_for_new_view(self, mock_parent): + """Test that _get_original_view returns None for new views.""" + from windows.main.tabs.view import ViewEditorController + + with patch('windows.main.tabs.view.CURRENT_VIEW'): + with patch('windows.main.tabs.view.wx_call_after_debounce'): + controller = ViewEditorController(mock_parent) + + mock_view = Mock() + type(mock_view).is_new = PropertyMock(return_value=True) + + result = controller._get_original_view(mock_view) + assert result is None + + def test_has_changes_returns_true_for_new_view(self, mock_parent): + """Test that _has_changes returns True for new views.""" + from windows.main.tabs.view import ViewEditorController + + with patch('windows.main.tabs.view.CURRENT_VIEW'): + with patch('windows.main.tabs.view.wx_call_after_debounce'): + controller = ViewEditorController(mock_parent) + + mock_view = Mock() + type(mock_view).is_new = PropertyMock(return_value=True) + + result = controller._has_changes(mock_view) + assert result is True + + def test_update_button_states_disables_all_when_no_view(self, mock_parent): + """Test that update_button_states disables all buttons when no view.""" + from windows.main.tabs.view import ViewEditorController + + with patch('windows.main.tabs.view.CURRENT_VIEW') as mock_current_view: + with patch('windows.main.tabs.view.wx_call_after_debounce'): + mock_current_view.get_value.return_value = None + + controller = ViewEditorController(mock_parent) + controller.update_button_states() + + mock_parent.btn_save_view.Enable.assert_called_with(False) + mock_parent.btn_cancel_view.Enable.assert_called_with(False) + mock_parent.btn_delete_view.Enable.assert_called_with(False) + + def test_update_button_states_enables_save_cancel_for_new_view(self, mock_parent): + """Test that update_button_states enables save/cancel for new views.""" + from windows.main.tabs.view import ViewEditorController + + with patch('windows.main.tabs.view.CURRENT_VIEW') as mock_current_view: + with patch('windows.main.tabs.view.wx_call_after_debounce'): + mock_view = Mock() + type(mock_view).is_new = PropertyMock(return_value=True) + mock_current_view.get_value.return_value = mock_view + + controller = ViewEditorController(mock_parent) + controller.update_button_states() + + mock_parent.btn_save_view.Enable.assert_called_with(True) + mock_parent.btn_cancel_view.Enable.assert_called_with(True) + mock_parent.btn_delete_view.Enable.assert_called_with(False) + + +class TestSQLViewSaveMethod: + """Test SQLView save method and database refresh.""" + + def test_save_calls_create_for_new_view(self): + """Test that save() calls create() for new views.""" + from structures.engines.database import SQLView + + mock_view = Mock() + type(mock_view).is_new = PropertyMock(return_value=True) + mock_view.create = Mock(return_value=True) + mock_view.alter = Mock() + mock_database = Mock() + mock_database.refresh = Mock() + mock_view.database = mock_database + + result = SQLView.save(mock_view) + + mock_view.create.assert_called_once() + mock_view.alter.assert_not_called() + mock_database.refresh.assert_called_once() + assert result is True + + def test_save_calls_alter_for_existing_view(self): + """Test that save() calls alter() for existing views.""" + from structures.engines.database import SQLView + + mock_view = Mock() + type(mock_view).is_new = PropertyMock(return_value=False) + mock_view.create = Mock() + mock_view.alter = Mock(return_value=True) + mock_database = Mock() + mock_database.refresh = Mock() + mock_view.database = mock_database + + result = SQLView.save(mock_view) + + mock_view.create.assert_not_called() + mock_view.alter.assert_called_once() + mock_database.refresh.assert_called_once() + assert result is True + + def test_save_refreshes_database_after_success(self): + """Test that save() refreshes database after successful save.""" + from structures.engines.database import SQLView + + mock_view = Mock() + type(mock_view).is_new = PropertyMock(return_value=True) + mock_view.create = Mock(return_value=True) + mock_database = Mock() + mock_database.refresh = Mock() + mock_view.database = mock_database + + SQLView.save(mock_view) + + mock_database.refresh.assert_called_once() diff --git a/tests/engines/base_check_tests.py b/tests/engines/base_check_tests.py new file mode 100644 index 0000000..ae30832 --- /dev/null +++ b/tests/engines/base_check_tests.py @@ -0,0 +1,99 @@ +import pytest + + +class BaseCheckTests: + def test_check_in_table_definition( + self, session, database, create_users_table, datatype_class + ): + """Test that Check constraints are loaded from table definition.""" + table = create_users_table(database, session) + + # Add a column with a check constraint + age_column = session.context.build_empty_column( + table, + datatype_class.INTEGER + if hasattr(datatype_class, "INTEGER") + else datatype_class.INT, + name="age", + is_nullable=True, + ) + age_column.add() + + # Refresh table to load checks + table.checks.refresh() + checks = table.checks.get_value() + + # Note: Check constraints might be inline in column definition + # or separate objects depending on engine implementation + assert checks is not None + assert isinstance(checks, list) + + table.drop() + + @pytest.mark.skip_engine("sqlite", "mariadb:5") + def test_check_create_and_drop( + self, session, database, create_users_table, datatype_class + ): + """Test creating and dropping a CHECK constraint.""" + table = create_users_table(database, session) + + # Add age column + age_column = session.context.build_empty_column( + table, + datatype_class.INTEGER + if hasattr(datatype_class, "INTEGER") + else datatype_class.INT, + name="age", + is_nullable=True, + ) + age_column.add() + + # Create a CHECK constraint + check = session.context.build_empty_check( + table, name="age_check", expression="age >= 0 AND age <= 150" + ) + assert check.create() is True + + # Verify check exists + table.checks.refresh() + checks = table.checks.get_value() + assert any(c.name == "age_check" for c in checks) + + # Drop the check + check_to_drop = next(c for c in checks if c.name == "age_check") + assert check_to_drop.drop() is True + + # Verify check is gone + table.checks.refresh() + checks = table.checks.get_value() + assert not any(c.name == "age_check" for c in checks) + + table.drop() + + @pytest.mark.skip_engine("sqlite", "mariadb:5") + def test_check_alter(self, session, database, create_users_table, datatype_class): + """Test altering a CHECK constraint (drop + create).""" + table = create_users_table(database, session) + + # Add age column + age_column = session.context.build_empty_column( + table, + datatype_class.INTEGER + if hasattr(datatype_class, "INTEGER") + else datatype_class.INT, + name="age", + is_nullable=True, + ) + age_column.add() + + # Create initial CHECK constraint + check = session.context.build_empty_check( + table, name="age_check", expression="age >= 0" + ) + check.create() + + # Alter the check (change expression) + check.expression = "age >= 18" + assert check.alter() is True + + table.drop() diff --git a/tests/engines/base_column_tests.py b/tests/engines/base_column_tests.py new file mode 100644 index 0000000..672f5d8 --- /dev/null +++ b/tests/engines/base_column_tests.py @@ -0,0 +1,85 @@ +import pytest + + +class BaseColumnTests: + def test_column_add(self, session, database, create_users_table, datatype_class): + table = create_users_table(database, session) + + email_column = session.context.build_empty_column( + table, + datatype_class.VARCHAR, + name="email", + is_nullable=True, + length=255, + ) + assert email_column.add() is True + + table.columns.refresh() + columns = table.columns.get_value() + assert any(c.name == "email" for c in columns) + + table.drop() + + @pytest.mark.skip_engine("sqlite") + def test_column_modify(self, session, database, create_users_table, datatype_class): + table = create_users_table(database, session) + + email_column = session.context.build_empty_column( + table, + datatype_class.VARCHAR, + name="email", + is_nullable=True, + length=100, + ) + email_column.add() + + table.columns.refresh() + columns = table.columns.get_value() + email_col = next((c for c in columns if c.name == "email"), None) + assert email_col is not None + + modified_column = session.context.build_empty_column( + table, + datatype_class.VARCHAR, + name="email", + is_nullable=False, + length=255, + ) + + assert ( + email_col.modify(modified_column) is None + or email_col.modify(modified_column) is True + ) + + table.columns.refresh() + columns = table.columns.get_value() + updated_col = next((c for c in columns if c.name == "email"), None) + assert updated_col is not None + + table.drop() + + @pytest.mark.skip_engine("sqlite") + def test_column_drop(self, session, database, create_users_table, datatype_class): + table = create_users_table(database, session) + + email_column = session.context.build_empty_column( + table, + datatype_class.VARCHAR, + name="email", + is_nullable=True, + length=255, + ) + email_column.add() + + table.columns.refresh() + columns = table.columns.get_value() + email_col = next((c for c in columns if c.name == "email"), None) + assert email_col is not None + + assert email_col.drop() is True + + table.columns.refresh() + columns = table.columns.get_value() + assert not any(c.name == "email" for c in columns) + + table.drop() diff --git a/tests/engines/base_foreignkey_tests.py b/tests/engines/base_foreignkey_tests.py new file mode 100644 index 0000000..694d822 --- /dev/null +++ b/tests/engines/base_foreignkey_tests.py @@ -0,0 +1,76 @@ +import pytest + + +class BaseForeignKeyTests: + + def test_foreignkey_create_and_drop(self, session, database, create_users_table): + users_table = create_users_table(database, session) + + posts_table = session.context.build_empty_table(database, name="posts") + + id_column = session.context.build_empty_column( + posts_table, + self.get_datatype_class().INTEGER, + name="id", + is_auto_increment=True, + is_nullable=False, + ) + + user_id_column = session.context.build_empty_column( + posts_table, + self.get_datatype_class().INTEGER, + name="user_id", + is_nullable=False, + ) + + posts_table.columns.append(id_column) + posts_table.columns.append(user_id_column) + + primary_index = session.context.build_empty_index( + posts_table, + self.get_indextype_class().PRIMARY, + ["id"], + name=self.get_primary_key_name(), + ) + posts_table.indexes.append(primary_index) + + posts_table.create() + database.tables.refresh() + posts_table = next(t for t in database.tables.get_value() if t.name == "posts") + + fk = session.context.build_empty_foreign_key( + posts_table, + ["user_id"], + name="fk_posts_users", + ) + fk.reference_table = "users" + fk.reference_columns = ["id"] + fk.on_delete = "CASCADE" + fk.on_update = "CASCADE" + + assert fk.create() is True + + posts_table.foreign_keys.refresh() + foreign_keys = posts_table.foreign_keys.get_value() + assert len(foreign_keys) > 0, f"No foreign keys found. Expected fk_posts_users" + assert any(fk.name == "fk_posts_users" for fk in foreign_keys), f"Foreign key fk_posts_users not found. Found: {[fk.name for fk in foreign_keys]}" + + created_fk = next((fk for fk in foreign_keys if fk.name == "fk_posts_users"), None) + assert created_fk is not None + assert created_fk.drop() is True + + posts_table.foreign_keys.refresh() + foreign_keys = posts_table.foreign_keys.get_value() + assert not any(fk.name == "fk_posts_users" for fk in foreign_keys) + + posts_table.drop() + users_table.drop() + + def get_datatype_class(self): + raise NotImplementedError("Subclasses must implement get_datatype_class()") + + def get_indextype_class(self): + raise NotImplementedError("Subclasses must implement get_indextype_class()") + + def get_primary_key_name(self) -> str: + raise NotImplementedError("Subclasses must implement get_primary_key_name()") diff --git a/tests/engines/base_function_tests.py b/tests/engines/base_function_tests.py new file mode 100644 index 0000000..e4ebb55 --- /dev/null +++ b/tests/engines/base_function_tests.py @@ -0,0 +1,41 @@ +import pytest + +from structures.session import Session +from structures.engines.database import SQLDatabase + + +class BaseFunctionTests: + + def get_function_statement(self) -> str: + raise NotImplementedError + + def get_function_parameters(self) -> str: + return "x integer" + + def get_function_returns(self) -> str: + return "integer" + + def test_function_create_and_drop(self, session: Session, database: SQLDatabase): + function = session.context.build_empty_function( + database, + name="test_function", + parameters=self.get_function_parameters(), + returns=self.get_function_returns(), + statement=self.get_function_statement() + ) + + assert function.is_new is True + + result = function.create() + assert result is True + + database.functions.refresh() + found = any(f.name == "test_function" for f in database.functions.get_value()) + assert found is True + + result = function.drop() + assert result is True + + database.functions.refresh() + found = any(f.name == "test_function" for f in database.functions.get_value()) + assert found is False diff --git a/tests/engines/base_index_tests.py b/tests/engines/base_index_tests.py new file mode 100644 index 0000000..75b6b39 --- /dev/null +++ b/tests/engines/base_index_tests.py @@ -0,0 +1,27 @@ +import pytest + + +class BaseIndexTests: + + def test_index_create_and_drop(self, session, database, create_users_table, indextype_class): + table = create_users_table(database, session) + + idx_name = session.context.build_empty_index( + table, + indextype_class.INDEX, + ["name"], + name="idx_name", + ) + assert idx_name.create() is True + + table.indexes.refresh() + indexes = table.indexes.get_value() + assert any(i.name == "idx_name" for i in indexes) + + assert idx_name.drop() is True + + table.indexes.refresh() + indexes = table.indexes.get_value() + assert not any(i.name == "idx_name" for i in indexes) + + table.drop() diff --git a/tests/engines/base_procedure_tests.py b/tests/engines/base_procedure_tests.py new file mode 100644 index 0000000..075566f --- /dev/null +++ b/tests/engines/base_procedure_tests.py @@ -0,0 +1,37 @@ +import pytest + +from structures.session import Session +from structures.engines.database import SQLDatabase + + +class BaseProcedureTests: + + def get_procedure_statement(self) -> str: + raise NotImplementedError + + def get_procedure_parameters(self) -> str: + return "" + + def test_procedure_create_and_drop(self, session: Session, database: SQLDatabase): + procedure = session.context.build_empty_procedure( + database, + name="test_procedure", + parameters=self.get_procedure_parameters(), + statement=self.get_procedure_statement() + ) + + assert procedure.is_new is True + + result = procedure.create() + assert result is True + + database.procedures.refresh() + found = any(p.name == "test_procedure" for p in database.procedures.get_value()) + assert found is True + + result = procedure.drop() + assert result is True + + database.procedures.refresh() + found = any(p.name == "test_procedure" for p in database.procedures.get_value()) + assert found is False diff --git a/tests/engines/base_record_tests.py b/tests/engines/base_record_tests.py new file mode 100644 index 0000000..23337ed --- /dev/null +++ b/tests/engines/base_record_tests.py @@ -0,0 +1,56 @@ +import pytest + + +class BaseRecordTests: + + def test_record_insert(self, session, database, create_users_table): + table = create_users_table(database, session) + + table.load_records() + assert len(table.records.get_value()) == 0 + + record = session.context.build_empty_record(table, values={"name": "John Doe"}) + assert record.insert() is True + + table.load_records() + records = table.records.get_value() + assert len(records) == 1 + assert records[0].values["name"] == "John Doe" + + table.drop() + + def test_record_update(self, session, database, create_users_table): + table = create_users_table(database, session) + table.load_records() + + record = session.context.build_empty_record(table, values={"name": "John Doe"}) + record.insert() + + table.load_records() + record = table.records.get_value()[0] + assert record.is_new is False + + record.values["name"] = "Jane Doe" + assert record.update() is True + + table.load_records() + records = table.records.get_value() + assert records[0].values["name"] == "Jane Doe" + + table.drop() + + def test_record_delete(self, session, database, create_users_table): + table = create_users_table(database, session) + table.load_records() + + record = session.context.build_empty_record(table, values={"name": "John Doe"}) + record.insert() + + table.load_records() + record = table.records.get_value()[0] + assert record.delete() is True + + table.load_records() + assert len(table.records.get_value()) == 0 + + table.drop() diff --git a/tests/engines/base_ssh_tests.py b/tests/engines/base_ssh_tests.py new file mode 100644 index 0000000..20c3aaf --- /dev/null +++ b/tests/engines/base_ssh_tests.py @@ -0,0 +1,11 @@ +import pytest + + +class BaseSSHTunnelTests: + + def test_get_version_through_ssh_tunnel(self, ssh_session): + version = ssh_session.context.get_server_version() + + assert version is not None + assert isinstance(version, str) + assert len(version) > 0 diff --git a/tests/engines/base_table_tests.py b/tests/engines/base_table_tests.py new file mode 100644 index 0000000..197945b --- /dev/null +++ b/tests/engines/base_table_tests.py @@ -0,0 +1,51 @@ +import pytest + + +class BaseTableTests: + + def test_table_create_and_drop(self, session, database, create_users_table): + table = create_users_table(database, session) + assert table.is_valid is True + assert table.id >= 0 + + tables = database.tables.get_value() + assert any(t.name == "users" for t in tables) + + assert table.drop() is True + + database.tables.refresh() + tables = database.tables.get_value() + assert not any(t.name == "users" for t in tables) + + def test_table_truncate(self, session, database, create_users_table): + table = create_users_table(database, session) + table.load_records() + + record = session.context.build_empty_record(table, values={"name": "John Doe"}) + record.insert() + + table.load_records() + assert len(table.records.get_value()) == 1 + + assert table.truncate() is True + + table.load_records() + assert len(table.records.get_value()) == 0 + + table.drop() + + def test_table_rename(self, session, database, create_users_table): + table = create_users_table(database, session) + original_name = table.name + + new_name = "users_renamed" + table.rename(table, new_name) + table.name = new_name + + database.tables.refresh() + tables = database.tables.get_value() + assert any(t.name == new_name for t in tables) + assert not any(t.name == original_name for t in tables) + + renamed_table = next(t for t in tables if t.name == new_name) + renamed_table.drop() diff --git a/tests/engines/base_trigger_tests.py b/tests/engines/base_trigger_tests.py new file mode 100644 index 0000000..8bb8389 --- /dev/null +++ b/tests/engines/base_trigger_tests.py @@ -0,0 +1,29 @@ +import pytest + + +class BaseTriggerTests: + + def test_trigger_create_and_drop(self, session, database, create_users_table): + table = create_users_table(database, session) + + trigger = session.context.build_empty_trigger( + database, + name="trg_users_insert", + statement=self.get_trigger_statement(database.name, table.name), + ) + assert trigger.create() is True + + database.triggers.refresh() + triggers = database.triggers.get_value() + assert any(t.name == "trg_users_insert" for t in triggers) + + assert trigger.drop() is True + + database.triggers.refresh() + triggers = database.triggers.get_value() + assert not any(t.name == "trg_users_insert" for t in triggers) + + table.drop() + + def get_trigger_statement(self, db_name: str, table_name: str) -> str: + raise NotImplementedError("Subclasses must implement get_trigger_statement()") diff --git a/tests/engines/base_view_tests.py b/tests/engines/base_view_tests.py new file mode 100644 index 0000000..deb3dd6 --- /dev/null +++ b/tests/engines/base_view_tests.py @@ -0,0 +1,98 @@ +import pytest + + +class BaseViewSaveTests: + + def test_save_creates_new_view_and_refreshes_database(self, session, database): + view = session.context.build_empty_view( + database, + name="test_save_view", + statement=self.get_view_statement(), + ) + + assert view.is_new is True + + result = view.save() + + assert result is True + database.views.refresh() + views = database.views.get_value() + assert any(v.name == "test_save_view" for v in views) + + view.drop() + + def test_save_alters_existing_view_and_refreshes_database(self, session, database): + view = session.context.build_empty_view( + database, + name="test_alter_view", + statement=self.get_simple_view_statement(), + ) + view.create() + + database.views.refresh() + views = database.views.get_value() + created_view = next((v for v in views if v.name == "test_alter_view"), None) + assert created_view is not None + + created_view.statement = self.get_updated_view_statement() + + result = created_view.save() + + assert result is True + database.views.refresh() + views = database.views.get_value() + updated_view = next((v for v in views if v.name == "test_alter_view"), None) + assert updated_view is not None + + created_view.drop() + + def get_view_statement(self) -> str: + raise NotImplementedError("Subclasses must implement get_view_statement()") + + def get_simple_view_statement(self) -> str: + raise NotImplementedError("Subclasses must implement get_simple_view_statement()") + + def get_updated_view_statement(self) -> str: + raise NotImplementedError("Subclasses must implement get_updated_view_statement()") + + +class BaseViewIsNewTests: + + def test_is_new_returns_true_for_new_view(self, session, database): + view = session.context.build_empty_view( + database, + name="new_view", + statement=self.get_simple_view_statement(), + ) + + assert view.is_new is True + + def test_is_new_returns_false_for_existing_view(self, session, database): + view = session.context.build_empty_view( + database, + name="existing_view", + statement=self.get_simple_view_statement(), + ) + view.create() + + database.views.refresh() + views = database.views.get_value() + existing_view = next((v for v in views if v.name == "existing_view"), None) + + assert existing_view is not None + assert existing_view.is_new is False + + existing_view.drop() + + def get_simple_view_statement(self) -> str: + raise NotImplementedError("Subclasses must implement get_simple_view_statement()") + + +class BaseViewDefinerTests: + + def test_get_definers_returns_list(self, session): + definers = session.context.get_definers() + + assert isinstance(definers, list) + assert len(definers) > 0 + assert all('@' in definer for definer in definers) diff --git a/tests/engines/mariadb/conftest.py b/tests/engines/mariadb/conftest.py index a733528..990ddb3 100644 --- a/tests/engines/mariadb/conftest.py +++ b/tests/engines/mariadb/conftest.py @@ -6,28 +6,72 @@ from structures.session import Session from structures.connection import Connection, ConnectionEngine from structures.configurations import CredentialsConfiguration +from structures.engines.mariadb.database import MariaDBTable +from structures.engines.mariadb.datatype import MariaDBDataType +from structures.engines.mariadb.indextype import MariaDBIndexType MARIADB_VERSIONS: list[str] = [ - "mariadb:latest", - "mariadb:11.8", - "mariadb:10.11", - "mariadb:5.5", + "mariadb:12", + "mariadb:11", + "mariadb:10", + "mariadb:5", ] +def create_users_table_mariadb(mariadb_database, mariadb_session) -> MariaDBTable: + ctx = mariadb_session.context + ctx.set_database(mariadb_database) + + table = ctx.build_empty_table(mariadb_database, name="users", engine="InnoDB", collation_name="utf8mb4_general_ci") + + id_column = ctx.build_empty_column( + table, + MariaDBDataType.INT, + name="id", + is_auto_increment=True, + is_nullable=False, + length=11, + ) + + name_column = ctx.build_empty_column( + table, + MariaDBDataType.VARCHAR, + name="name", + is_nullable=False, + length=255, + ) + + table.columns.append(id_column) + table.columns.append(name_column) + + primary_index = ctx.build_empty_index( + table, + MariaDBIndexType.PRIMARY, + ["id"], + name="PRIMARY", + ) + table.indexes.append(primary_index) + + table.create() + mariadb_database.tables.refresh() + return next(t for t in mariadb_database.tables.get_value() if t.name == "users") + + def pytest_generate_tests(metafunc): if "mariadb_version" in metafunc.fixturenames: metafunc.parametrize("mariadb_version", MARIADB_VERSIONS, scope="module") @pytest.fixture(scope="module") -def mariadb_container(mariadb_version): - with MySqlContainer(mariadb_version, name=f"petersql_test_{mariadb_version.replace(":", "_")}", +def mariadb_container(mariadb_version, worker_id): + container = MySqlContainer(mariadb_version, name=f"petersql_test_{worker_id}_{mariadb_version.replace(":", "_")}", mem_limit="768m", memswap_limit="1g", nano_cpus=1_000_000_000, shm_size="256m", - ) as container: + ) + + with container: yield container @@ -63,3 +107,34 @@ def mariadb_database(mariadb_session): for table in database.tables.get_value(): mariadb_session.context.execute(f"DROP TABLE IF EXISTS `testdb`.`{table.name}`") mariadb_session.context.execute("SET FOREIGN_KEY_CHECKS = 1") + + +# Unified fixtures for base test suites +@pytest.fixture +def session(mariadb_session): + """Alias for mariadb_session to match base test suite parameter names.""" + return mariadb_session + + +@pytest.fixture +def database(mariadb_database): + """Alias for mariadb_database to match base test suite parameter names.""" + return mariadb_database + + +@pytest.fixture +def create_users_table(): + """Provide the create_users_table helper function.""" + return create_users_table_mariadb + + +@pytest.fixture +def datatype_class(): + """Provide the engine-specific datatype class.""" + return MariaDBDataType + + +@pytest.fixture +def indextype_class(): + """Provide the engine-specific indextype class.""" + return MariaDBIndexType diff --git a/tests/engines/mariadb/test_context.py b/tests/engines/mariadb/test_context.py index 1fc1ac6..eb7514f 100644 --- a/tests/engines/mariadb/test_context.py +++ b/tests/engines/mariadb/test_context.py @@ -1,6 +1,8 @@ import pytest +@pytest.mark.integration +@pytest.mark.xdist_group("mariadb") class TestMariaDBContext: """Tests for MariaDB context methods.""" @@ -29,15 +31,15 @@ def test_context_get_server_version(self, mariadb_session): assert version is not None assert len(version) > 0 - def test_context_build_sql_safe_name(self, mariadb_session): - """Test building SQL safe names uses IDENTIFIER_QUOTE.""" + def test_context_quote_identifier(self, mariadb_session): + """Test building SQL safe names uses IDENTIFIER_QUOTE_CHAR.""" ctx = mariadb_session.context - quote = ctx.IDENTIFIER_QUOTE + quote = ctx.IDENTIFIER_QUOTE_CHAR # Simple names don't need quoting - assert ctx.build_sql_safe_name("normal") == "normal" - # Names with spaces are quoted using IDENTIFIER_QUOTE - assert ctx.build_sql_safe_name("with space") == f'{quote}with space{quote}' + assert ctx.quote_identifier("normal") == "normal" + # Names with spaces are quoted using IDENTIFIER_QUOTE_CHAR + assert ctx.quote_identifier("with space") == f'{quote}with space{quote}' def test_context_transaction(self, mariadb_session, mariadb_database): """Test transaction context manager.""" diff --git a/tests/engines/mariadb/test_integration.py b/tests/engines/mariadb/test_integration.py deleted file mode 100644 index 1872795..0000000 --- a/tests/engines/mariadb/test_integration.py +++ /dev/null @@ -1,382 +0,0 @@ -import pytest -from structures.ssh_tunnel import SSHTunnel -from structures.engines.mariadb.database import MariaDBTable -from structures.engines.mariadb.datatype import MariaDBDataType -from structures.engines.mariadb.indextype import MariaDBIndexType - - - -def create_users_table(mariadb_database, mariadb_session) -> MariaDBTable: - """Helper: create and save a users table with id and name columns. - - Uses build_empty_* API from context to construct objects. - Returns the persisted table from the database (with proper handlers). - """ - ctx = mariadb_session.context - ctx.execute("USE testdb") - - table = ctx.build_empty_table(mariadb_database, name="users", engine="InnoDB", collation_name="utf8mb4_general_ci") - - id_column = ctx.build_empty_column( - table, - MariaDBDataType.INT, - name="id", - is_auto_increment=True, - is_nullable=False, - length=11, - ) - - name_column = ctx.build_empty_column( - table, - MariaDBDataType.VARCHAR, - name="name", - is_nullable=False, - length=255, - ) - - table.columns.append(id_column) - table.columns.append(name_column) - - primary_index = ctx.build_empty_index( - table, - MariaDBIndexType.PRIMARY, - ["id"], - name="PRIMARY", - ) - table.indexes.append(primary_index) - - # Create table directly via raw SQL - ctx.execute(table.raw_create()) - - # Refresh tables to get the persisted table with proper handlers - mariadb_database.tables.refresh() - return next(t for t in mariadb_database.tables.get_value() if t.name == "users") - - -@pytest.fixture(scope="function") -def ssh_mariadb_session(mariadb_container, mariadb_session): - """Create SSH tunnel session for testing.""" - try: - # Create SSH tunnel to MariaDB container - tunnel = SSHTunnel( - mariadb_container.get_container_host_ip(), - 22, # Assuming SSH access to host - ssh_username=None, - ssh_password=None, - remote_port=mariadb_container.get_exposed_port(3306), - local_bind_address=('localhost', 0) - ) - - tunnel.start(timeout=5) - - # Create connection using tunnel - from structures.session import Session - from structures.connection import Connection, ConnectionEngine - from structures.configurations import CredentialsConfiguration - - config = CredentialsConfiguration( - hostname="localhost", - username="root", - password=mariadb_container.root_password, - port=tunnel.local_port, - ) - - connection = Connection( - id=1, - name="ssh_mariadb_test", - engine=ConnectionEngine.MARIADB, - configuration=config, - ) - - session = Session(connection=connection) - session.connect() - - yield session, tunnel - - except Exception: - pytest.skip("SSH tunnel not available") - - finally: - try: - session.disconnect() - except: - pass - try: - tunnel.stop() - except: - pass - - -class TestMariaDBIntegration: - """Integration tests for MariaDB engine using build_empty_* API.""" - - def test_table_create_and_drop(self, mariadb_session, mariadb_database): - """Test table creation and deletion.""" - table = create_users_table(mariadb_database, mariadb_session) - assert table.is_valid is True - assert table.id >= 0 - - # Verify table exists in database - tables = mariadb_database.tables.get_value() - assert any(t.name == "users" for t in tables) - - assert table.drop() is True - - # Refresh to verify table was deleted - mariadb_database.tables.refresh() - tables = mariadb_database.tables.get_value() - assert not any(t.name == "users" for t in tables) - - def test_ssh_tunnel_basic_operations(self, ssh_mariadb_session, mariadb_database): - """Test basic CRUD operations through SSH tunnel.""" - session, tunnel = ssh_mariadb_session - - # Create table - table = create_users_table(mariadb_database, session) - - # Test INSERT - record = session.context.build_empty_record(table, values={"name": "John Doe"}) - assert record.insert() is True - - # Test SELECT - table.load_records() - records = table.records.get_value() - assert len(records) == 1 - assert records[0].values["name"] == "John Doe" - - # Test UPDATE - record = records[0] - record.values["name"] = "Jane Doe" - assert record.update() is True - - # Verify UPDATE - table.load_records() - records = table.records.get_value() - assert records[0].values["name"] == "Jane Doe" - - # Test DELETE - assert record.delete() is True - - # Verify DELETE - table.load_records() - assert len(table.records.get_value()) == 0 - - table.drop() - - def test_ssh_tunnel_transaction_support(self, ssh_mariadb_session, mariadb_database): - """Test transaction support through SSH tunnel.""" - session, tunnel = ssh_mariadb_session - table = create_users_table(mariadb_database, session) - - # Test successful transaction - with session.context.transaction() as tx: - tx.execute("INSERT INTO testdb.users (name) VALUES (%s)", ("test1",)) - tx.execute("INSERT INTO testdb.users (name) VALUES (%s)", ("test2",)) - - # Verify data was committed - session.context.execute("SELECT COUNT(*) as count FROM testdb.users") - result = session.context.fetchone() - assert result['count'] == 2 - - # Test failed transaction - try: - with session.context.transaction() as tx: - tx.execute("INSERT INTO testdb.users (name) VALUES (%s)", ("test3",)) - tx.execute("INSERT INTO testdb.users (id, name) VALUES (1, 'duplicate')") # Should fail - except: - pass # Expected to fail - - # Verify rollback worked - session.context.execute("SELECT COUNT(*) as count FROM testdb.users") - result = session.context.fetchone() - assert result['count'] == 2 - - table.drop() - - def test_ssh_tunnel_error_handling(self, ssh_mariadb_session, mariadb_database): - """Test error handling through SSH tunnel.""" - session, tunnel = ssh_mariadb_session - table = create_users_table(mariadb_database, session) - - # Test invalid SQL - try: - session.context.execute("INVALID SQL QUERY") - assert False, "Should have raised exception" - except Exception: - pass # Expected - - # Test connection is still working - result = session.context.execute("SELECT 1 as test") - assert result is True - - table.drop() - - def test_record_insert(self, mariadb_session, mariadb_database): - """Test record insertion.""" - table = create_users_table(mariadb_database, mariadb_session) - - table.load_records() - assert len(table.records.get_value()) == 0 - - record = mariadb_session.context.build_empty_record(table, values={"name": "John Doe"}) - assert record.insert() is True - - table.load_records() - records = table.records.get_value() - assert len(records) == 1 - assert records[0].values["name"] == "John Doe" - - table.drop() - - def test_record_update(self, mariadb_session, mariadb_database): - """Test record update.""" - table = create_users_table(mariadb_database, mariadb_session) - table.load_records() - - record = mariadb_session.context.build_empty_record(table, values={"name": "John Doe"}) - record.insert() - - table.load_records() - record = table.records.get_value()[0] - assert record.is_valid() is True - assert record.is_new() is False - - record.values["name"] = "Jane Doe" - assert record.update() is True - - table.load_records() - records = table.records.get_value() - assert records[0].values["name"] == "Jane Doe" - - table.drop() - - def test_record_delete(self, mariadb_session, mariadb_database): - """Test record deletion.""" - table = create_users_table(mariadb_database, mariadb_session) - table.load_records() - - record = mariadb_session.context.build_empty_record(table, values={"name": "John Doe"}) - record.insert() - - table.load_records() - record = table.records.get_value()[0] - assert record.delete() is True - - table.load_records() - assert len(table.records.get_value()) == 0 - - table.drop() - - def test_column_add(self, mariadb_session, mariadb_database): - """Test adding a column to an existing table.""" - table = create_users_table(mariadb_database, mariadb_session) - - email_column = mariadb_session.context.build_empty_column( - table, - MariaDBDataType.VARCHAR, - name="email", - is_nullable=True, - length=255, - ) - assert email_column.add() is True - - # Refresh columns to verify column was added - table.columns.refresh() - columns = table.columns.get_value() - assert any(c.name == "email" for c in columns) - - table.drop() - - def test_table_truncate(self, mariadb_session, mariadb_database): - """Test table truncation.""" - table = create_users_table(mariadb_database, mariadb_session) - table.load_records() - - record = mariadb_session.context.build_empty_record(table, values={"name": "John Doe"}) - record.insert() - - table.load_records() - assert len(table.records.get_value()) == 1 - - assert table.truncate() is True - - table.load_records() - assert len(table.records.get_value()) == 0 - - table.drop() - - def test_index_create_and_drop(self, mariadb_session, mariadb_database): - """Test index creation and deletion.""" - table = create_users_table(mariadb_database, mariadb_session) - - idx_name = mariadb_session.context.build_empty_index( - table, - MariaDBIndexType.INDEX, - ["name"], - name="idx_name", - ) - assert idx_name.create() is True - - # Refresh indexes to verify index was created - table.indexes.refresh() - indexes = table.indexes.get_value() - assert any(i.name == "idx_name" for i in indexes) - - assert idx_name.drop() is True - - # Refresh indexes to verify index was deleted - table.indexes.refresh() - indexes = table.indexes.get_value() - assert not any(i.name == "idx_name" for i in indexes) - - table.drop() - - def test_view_create_and_drop(self, mariadb_session, mariadb_database): - """Test view creation and deletion.""" - table = create_users_table(mariadb_database, mariadb_session) - - view = mariadb_session.context.build_empty_view( - mariadb_database, - name="active_users_view", - sql="SELECT * FROM testdb.users WHERE name IS NOT NULL", - ) - assert view.create() is True - - # Refresh views to verify view was created - mariadb_database.views.refresh() - views = mariadb_database.views.get_value() - assert any(v.name == "active_users_view" for v in views) - - assert view.drop() is True - - # Refresh views to verify view was deleted - mariadb_database.views.refresh() - views = mariadb_database.views.get_value() - assert not any(v.name == "active_users_view" for v in views) - - table.drop() - - def test_trigger_create_and_drop(self, mariadb_session, mariadb_database): - """Test trigger creation and deletion.""" - table = create_users_table(mariadb_database, mariadb_session) - - trigger = mariadb_session.context.build_empty_trigger( - mariadb_database, - name="trg_users_insert", - sql="AFTER INSERT ON testdb.users FOR EACH ROW BEGIN END", - ) - assert trigger.create() is True - - # Refresh triggers to verify trigger was created - mariadb_database.triggers.refresh() - triggers = mariadb_database.triggers.get_value() - assert any(t.name == "trg_users_insert" for t in triggers) - - assert trigger.drop() is True - - # Refresh triggers to verify trigger was deleted - mariadb_database.triggers.refresh() - triggers = mariadb_database.triggers.get_value() - assert not any(t.name == "trg_users_insert" for t in triggers) - - table.drop() diff --git a/tests/engines/mariadb/test_integration_suite.py b/tests/engines/mariadb/test_integration_suite.py new file mode 100644 index 0000000..b025c09 --- /dev/null +++ b/tests/engines/mariadb/test_integration_suite.py @@ -0,0 +1,92 @@ +import pytest +from structures.engines.mariadb.datatype import MariaDBDataType +from structures.engines.mariadb.indextype import MariaDBIndexType + +from tests.engines.base_table_tests import BaseTableTests +from tests.engines.base_record_tests import BaseRecordTests +from tests.engines.base_column_tests import BaseColumnTests +from tests.engines.base_index_tests import BaseIndexTests +from tests.engines.base_foreignkey_tests import BaseForeignKeyTests +from tests.engines.base_check_tests import BaseCheckTests +from tests.engines.base_trigger_tests import BaseTriggerTests +from tests.engines.base_view_tests import BaseViewSaveTests, BaseViewIsNewTests, BaseViewDefinerTests + + +@pytest.mark.integration +@pytest.mark.xdist_group("mariadb") +class TestMariaDBTable(BaseTableTests): + pass + + +@pytest.mark.integration +@pytest.mark.xdist_group("mariadb") +class TestMariaDBRecord(BaseRecordTests): + pass + + +@pytest.mark.integration +@pytest.mark.xdist_group("mariadb") +class TestMariaDBColumn(BaseColumnTests): + pass + + +@pytest.mark.integration +@pytest.mark.xdist_group("mariadb") +class TestMariaDBIndex(BaseIndexTests): + pass + + +@pytest.mark.integration +@pytest.mark.xdist_group("mariadb") +class TestMariaDBForeignKey(BaseForeignKeyTests): + + def get_datatype_class(self): + return MariaDBDataType + + def get_indextype_class(self): + return MariaDBIndexType + + def get_primary_key_name(self) -> str: + return "PRIMARY" + + +@pytest.mark.integration +@pytest.mark.xdist_group("mariadb") +class TestMariaDBCheck(BaseCheckTests): + pass + + +@pytest.mark.integration +@pytest.mark.xdist_group("mariadb") +class TestMariaDBTrigger(BaseTriggerTests): + + def get_trigger_statement(self, db_name: str, table_name: str) -> str: + return f"AFTER INSERT ON {db_name}.{table_name} FOR EACH ROW BEGIN END" + + +@pytest.mark.integration +@pytest.mark.xdist_group("mariadb") +class TestMariaDBViewSave(BaseViewSaveTests): + + def get_view_statement(self) -> str: + return "SELECT 1 as id, 'test' as name" + + def get_simple_view_statement(self) -> str: + return "SELECT 1 as id" + + def get_updated_view_statement(self) -> str: + return "SELECT 1 as id, 'updated' as name" + + +@pytest.mark.integration +@pytest.mark.xdist_group("mariadb") +class TestMariaDBViewIsNew(BaseViewIsNewTests): + + def get_simple_view_statement(self) -> str: + return "SELECT 1 as id" + + +@pytest.mark.integration +@pytest.mark.xdist_group("mariadb") +class TestMariaDBViewDefiner(BaseViewDefinerTests): + pass diff --git a/tests/engines/mariadb/test_ssh_tunnel.py b/tests/engines/mariadb/test_ssh_tunnel.py new file mode 100644 index 0000000..4e5093b --- /dev/null +++ b/tests/engines/mariadb/test_ssh_tunnel.py @@ -0,0 +1,80 @@ +import pytest +from testcontainers.mysql import MySqlContainer + +from structures.session import Session +from structures.connection import Connection, ConnectionEngine +from structures.configurations import CredentialsConfiguration, SSHTunnelConfiguration + +from tests.engines.base_ssh_tests import BaseSSHTunnelTests + + +@pytest.fixture(scope="module") +def mariadb_ssh_container(worker_id): + container = MySqlContainer("mariadb:latest", + name=f"petersql_test_{worker_id}_mariadb_ssh", + mem_limit="768m", + memswap_limit="1g", + nano_cpus=1_000_000_000, + shm_size="256m") + + with container: + install_ssh_commands = [ + "apt-get update", + "apt-get install -y openssh-server", + "mkdir -p /var/run/sshd", + "echo 'root:testpassword' | chpasswd", + "sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config", + "sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config", + "sed -i 's/#ListenAddress 0.0.0.0/ListenAddress 0.0.0.0/' /etc/ssh/sshd_config", + "/usr/sbin/sshd", + ] + + for cmd in install_ssh_commands: + exit_code, output = container.exec(cmd) + if exit_code != 0 and "sshd" not in cmd: + raise RuntimeError(f"Failed to execute: {cmd}\nOutput: {output}") + + container.with_exposed_ports(22) + + yield container + + +@pytest.fixture(scope="module") +def ssh_session(mariadb_ssh_container): + ssh_config = SSHTunnelConfiguration( + enabled=True, + executable="ssh", + hostname=mariadb_ssh_container.get_container_host_ip(), + port=mariadb_ssh_container.get_exposed_port(22), + username="root", + password="testpassword", + local_port=0, + extra_args=["-o ProxyJump=none"] + ) + + db_config = CredentialsConfiguration( + hostname="127.0.0.1", + username="root", + password=mariadb_ssh_container.root_password, + port=3306, + ) + + connection = Connection( + id=1, + name="test_ssh_session", + engine=ConnectionEngine.MARIADB, + configuration=db_config, + ssh_tunnel=ssh_config, + ) + + session = Session(connection=connection) + session.connect() + yield session + session.disconnect() + + +@pytest.mark.integration +@pytest.mark.xdist_group("mariadb") +@pytest.mark.skip(reason="MySqlContainer SSH tunnel not work") +class TestMariaDBSSHTunnel(BaseSSHTunnelTests): + pass diff --git a/tests/engines/mysql/conftest.py b/tests/engines/mysql/conftest.py index d73171e..ac5a701 100644 --- a/tests/engines/mysql/conftest.py +++ b/tests/engines/mysql/conftest.py @@ -6,28 +6,72 @@ from structures.session import Session from structures.connection import Connection, ConnectionEngine from structures.configurations import CredentialsConfiguration +from structures.engines.mysql.database import MySQLTable +from structures.engines.mysql.datatype import MySQLDataType +from structures.engines.mysql.indextype import MySQLIndexType MYSQL_VERSIONS: list[str] = [ - "mysql:latest", - "mysql:8.0", + "mysql:9", + "mysql:8", # "mysql:5.7", # Disabled: too slow and resource-intensive ] +def create_users_table_mysql(mysql_database, mysql_session) -> MySQLTable: + ctx = mysql_session.context + ctx.set_database(mysql_database) + + table = ctx.build_empty_table(mysql_database, name="users", engine="InnoDB", collation_name="utf8mb4_general_ci") + + id_column = ctx.build_empty_column( + table, + MySQLDataType.INT, + name="id", + is_auto_increment=True, + is_nullable=False, + length=11, + ) + + name_column = ctx.build_empty_column( + table, + MySQLDataType.VARCHAR, + name="name", + is_nullable=False, + length=255, + ) + + table.columns.append(id_column) + table.columns.append(name_column) + + primary_index = ctx.build_empty_index( + table, + MySQLIndexType.PRIMARY, + ["id"], + name="PRIMARY", + ) + table.indexes.append(primary_index) + + table.create() + mysql_database.tables.refresh() + return next(t for t in mysql_database.tables.get_value() if t.name == "users") + + def pytest_generate_tests(metafunc): if "mysql_version" in metafunc.fixturenames: metafunc.parametrize("mysql_version", MYSQL_VERSIONS, scope="module") @pytest.fixture(scope="module") -def mysql_container(mysql_version): - with MySqlContainer(mysql_version, name=f"petersql_test_{mysql_version.replace(':', '_')}", +def mysql_container(mysql_version, worker_id): + container = MySqlContainer(mysql_version, name=f"petersql_test_{worker_id}_{mysql_version.replace(':', '_')}", mem_limit="768m", memswap_limit="1g", nano_cpus=1_000_000_000, shm_size="256m", - ) as container: + ) + + with container: yield container @@ -63,3 +107,34 @@ def mysql_database(mysql_session): for table in database.tables.get_value(): mysql_session.context.execute(f"DROP TABLE IF EXISTS `testdb`.`{table.name}`") mysql_session.context.execute("SET FOREIGN_KEY_CHECKS = 1") + + +# Unified fixtures for base test suites +@pytest.fixture +def session(mysql_session): + """Alias for mysql_session to match base test suite parameter names.""" + return mysql_session + + +@pytest.fixture +def database(mysql_database): + """Alias for mysql_database to match base test suite parameter names.""" + return mysql_database + + +@pytest.fixture +def create_users_table(): + """Provide the create_users_table helper function.""" + return create_users_table_mysql + + +@pytest.fixture +def datatype_class(): + """Provide the engine-specific datatype class.""" + return MySQLDataType + + +@pytest.fixture +def indextype_class(): + """Provide the engine-specific indextype class.""" + return MySQLIndexType diff --git a/tests/engines/mysql/test_context.py b/tests/engines/mysql/test_context.py index e1d7c05..13817a0 100644 --- a/tests/engines/mysql/test_context.py +++ b/tests/engines/mysql/test_context.py @@ -1,6 +1,8 @@ import pytest +@pytest.mark.integration +@pytest.mark.xdist_group("mysql") class TestMySQLContext: """Tests for MySQL context methods.""" @@ -29,15 +31,15 @@ def test_context_get_server_version(self, mysql_session): assert version is not None assert len(version) > 0 - def test_context_build_sql_safe_name(self, mysql_session): - """Test building SQL safe names uses IDENTIFIER_QUOTE.""" + def test_context_quote_identifier(self, mysql_session): + """Test building SQL safe names uses IDENTIFIER_QUOTE_CHAR.""" ctx = mysql_session.context - quote = ctx.IDENTIFIER_QUOTE + quote = ctx.IDENTIFIER_QUOTE_CHAR # Simple names don't need quoting - assert ctx.build_sql_safe_name("normal") == "normal" - # Names with spaces are quoted using IDENTIFIER_QUOTE - assert ctx.build_sql_safe_name("with space") == f'{quote}with space{quote}' + assert ctx.quote_identifier("normal") == "normal" + # Names with spaces are quoted using IDENTIFIER_QUOTE_CHAR + assert ctx.quote_identifier("with space") == f'{quote}with space{quote}' def test_context_transaction(self, mysql_session, mysql_database): """Test transaction context manager.""" diff --git a/tests/engines/mysql/test_integration.py b/tests/engines/mysql/test_integration.py deleted file mode 100644 index 3cd2980..0000000 --- a/tests/engines/mysql/test_integration.py +++ /dev/null @@ -1,211 +0,0 @@ -import pytest -from structures.engines.mysql.database import MySQLTable -from structures.engines.mysql.datatype import MySQLDataType -from structures.engines.mysql.indextype import MySQLIndexType -from structures.ssh_tunnel import SSHTunnel - - -def create_users_table(mysql_database, mysql_session) -> MySQLTable: - """Helper: create and save a users table with id and name columns. - - Uses build_empty_* API from context to construct objects. - Returns the persisted table from the database (with proper handlers). - """ - ctx = mysql_session.context - ctx.execute("USE testdb") - - table = ctx.build_empty_table(mysql_database, name="users", engine="InnoDB", collation_name="utf8mb4_general_ci") - - id_column = ctx.build_empty_column( - table, - MySQLDataType.INT, - name="id", - is_auto_increment=True, - is_nullable=False, - length=11, - ) - - name_column = ctx.build_empty_column( - table, - MySQLDataType.VARCHAR, - name="name", - is_nullable=False, - length=255, - ) - - table.columns.append(id_column) - table.columns.append(name_column) - - primary_index = ctx.build_empty_index( - table, - MySQLIndexType.PRIMARY, - ["id"], - name="PRIMARY", - ) - table.indexes.append(primary_index) - - # Create table directly via raw SQL - ctx.execute(table.raw_create()) - - # Refresh tables to get the persisted table with proper handlers - mysql_database.tables.refresh() - return next(t for t in mysql_database.tables.get_value() if t.name == "users") - - -@pytest.fixture(scope="function") -def ssh_mysql_session(mysql_container, mysql_session): - """Create SSH tunnel session for testing.""" - try: - # Create SSH tunnel to MySQL container - tunnel = SSHTunnel( - mysql_container.get_container_host_ip(), - 22, # Assuming SSH access to host - ssh_username=None, - ssh_password=None, - remote_port=mysql_container.get_exposed_port(3306), - local_bind_address=('localhost', 0) - ) - - tunnel.start(timeout=5) - - # Create connection using tunnel - from structures.session import Session - from structures.connection import Connection, ConnectionEngine - from structures.configurations import CredentialsConfiguration - - config = CredentialsConfiguration( - hostname="localhost", - username="root", - password=mysql_container.root_password, - port=tunnel.local_port, - ) - - connection = Connection( - id=1, - name="ssh_mysql_test", - engine=ConnectionEngine.MYSQL, - configuration=config, - ) - - session = Session(connection=connection) - session.connect() - - yield session, tunnel - - except Exception: - pytest.skip("SSH tunnel not available") - - finally: - try: - session.disconnect() - except: - pass - try: - tunnel.stop() - except: - pass - - -class TestMySQLIntegration: - """Integration tests for MySQL engine using build_empty_* API.""" - - def test_table_create_and_drop(self, mysql_session, mysql_database): - """Test table creation and deletion.""" - table = create_users_table(mysql_database, mysql_session) - assert table.is_valid is True - assert table.id >= 0 - - # Verify table exists in database - tables = mysql_database.tables.get_value() - assert any(t.name == "users" for t in tables) - - assert table.drop() is True - - # Refresh to verify table was deleted - mysql_database.tables.refresh() - tables = mysql_database.tables.get_value() - assert not any(t.name == "users" for t in tables) - - def test_ssh_tunnel_basic_operations(self, ssh_mysql_session, mysql_database): - """Test basic CRUD operations through SSH tunnel.""" - session, tunnel = ssh_mysql_session - - # Create table - table = create_users_table(mysql_database, session) - - # Test INSERT - record = session.context.build_empty_record(table, values={"name": "John Doe"}) - assert record.insert() is True - - # Test SELECT - table.load_records() - records = table.records.get_value() - assert len(records) == 1 - assert records[0].values["name"] == "John Doe" - - # Test UPDATE - record = records[0] - record.values["name"] = "Jane Doe" - assert record.update() is True - - # Verify UPDATE - table.load_records() - records = table.records.get_value() - assert records[0].values["name"] == "Jane Doe" - - # Test DELETE - assert record.delete() is True - - # Verify DELETE - table.load_records() - assert len(table.records.get_value()) == 0 - - table.drop() - - def test_ssh_tunnel_transaction_support(self, ssh_mysql_session, mysql_database): - """Test transaction support through SSH tunnel.""" - session, tunnel = ssh_mysql_session - table = create_users_table(mysql_database, session) - - # Test successful transaction - with session.context.transaction() as tx: - tx.execute("INSERT INTO testdb.users (name) VALUES (%s)", ("test1",)) - tx.execute("INSERT INTO testdb.users (name) VALUES (%s)", ("test2",)) - - # Verify data was committed - session.context.execute("SELECT COUNT(*) as count FROM testdb.users") - result = session.context.fetchone() - assert result['count'] == 2 - - # Test failed transaction - try: - with session.context.transaction() as tx: - tx.execute("INSERT INTO testdb.users (name) VALUES (%s)", ("test3",)) - tx.execute("INSERT INTO testdb.users (id, name) VALUES (1, 'duplicate')") # Should fail - except: - pass # Expected to fail - - # Verify rollback worked - session.context.execute("SELECT COUNT(*) as count FROM testdb.users") - result = session.context.fetchone() - assert result['count'] == 2 - - table.drop() - - def test_ssh_tunnel_error_handling(self, ssh_mysql_session, mysql_database): - """Test error handling through SSH tunnel.""" - session, tunnel = ssh_mysql_session - table = create_users_table(mysql_database, session) - - # Test invalid SQL - try: - session.context.execute("INVALID SQL QUERY") - assert False, "Should have raised exception" - except Exception: - pass # Expected - - # Test connection is still working - result = session.context.execute("SELECT 1 as test") - assert result is True - - table.drop() diff --git a/tests/engines/mysql/test_integration_suite.py b/tests/engines/mysql/test_integration_suite.py new file mode 100644 index 0000000..953e72d --- /dev/null +++ b/tests/engines/mysql/test_integration_suite.py @@ -0,0 +1,92 @@ +import pytest +from structures.engines.mysql.datatype import MySQLDataType +from structures.engines.mysql.indextype import MySQLIndexType + +from tests.engines.base_table_tests import BaseTableTests +from tests.engines.base_record_tests import BaseRecordTests +from tests.engines.base_column_tests import BaseColumnTests +from tests.engines.base_index_tests import BaseIndexTests +from tests.engines.base_foreignkey_tests import BaseForeignKeyTests +from tests.engines.base_check_tests import BaseCheckTests +from tests.engines.base_trigger_tests import BaseTriggerTests +from tests.engines.base_view_tests import BaseViewSaveTests, BaseViewIsNewTests, BaseViewDefinerTests + + +@pytest.mark.integration +@pytest.mark.xdist_group("mysql") +class TestMySQLTable(BaseTableTests): + pass + + +@pytest.mark.integration +@pytest.mark.xdist_group("mysql") +class TestMySQLRecord(BaseRecordTests): + pass + + +@pytest.mark.integration +@pytest.mark.xdist_group("mysql") +class TestMySQLColumn(BaseColumnTests): + pass + + +@pytest.mark.integration +@pytest.mark.xdist_group("mysql") +class TestMySQLIndex(BaseIndexTests): + pass + + +@pytest.mark.integration +@pytest.mark.xdist_group("mysql") +class TestMySQLForeignKey(BaseForeignKeyTests): + + def get_datatype_class(self): + return MySQLDataType + + def get_indextype_class(self): + return MySQLIndexType + + def get_primary_key_name(self) -> str: + return "PRIMARY" + + +@pytest.mark.integration +@pytest.mark.xdist_group("mysql") +class TestMySQLCheck(BaseCheckTests): + pass + + +@pytest.mark.integration +@pytest.mark.xdist_group("mysql") +class TestMySQLTrigger(BaseTriggerTests): + + def get_trigger_statement(self, db_name: str, table_name: str) -> str: + return f"AFTER INSERT ON {db_name}.{table_name} FOR EACH ROW BEGIN END" + + +@pytest.mark.integration +@pytest.mark.xdist_group("mysql") +class TestMySQLViewSave(BaseViewSaveTests): + + def get_view_statement(self) -> str: + return "SELECT 1 as id, 'test' as name" + + def get_simple_view_statement(self) -> str: + return "SELECT 1 as id" + + def get_updated_view_statement(self) -> str: + return "SELECT 1 as id, 'updated' as name" + + +@pytest.mark.integration +@pytest.mark.xdist_group("mysql") +class TestMySQLViewIsNew(BaseViewIsNewTests): + + def get_simple_view_statement(self) -> str: + return "SELECT 1 as id" + + +@pytest.mark.integration +@pytest.mark.xdist_group("mysql") +class TestMySQLViewDefiner(BaseViewDefinerTests): + pass diff --git a/tests/engines/mysql/test_ssh_tunnel.py b/tests/engines/mysql/test_ssh_tunnel.py new file mode 100644 index 0000000..c902bf4 --- /dev/null +++ b/tests/engines/mysql/test_ssh_tunnel.py @@ -0,0 +1,82 @@ +import pytest +from testcontainers.mysql import MySqlContainer + +from structures.session import Session +from structures.connection import Connection, ConnectionEngine +from structures.configurations import CredentialsConfiguration, SSHTunnelConfiguration + +from tests.engines.base_ssh_tests import BaseSSHTunnelTests + + +@pytest.fixture(scope="module") +def mysql_ssh_container(worker_id): + container = MySqlContainer("mysql:latest", + name=f"petersql_test_{worker_id}_mysql_ssh", + mem_limit="768m", + memswap_limit="1g", + nano_cpus=1_000_000_000, + shm_size="256m") + container.with_exposed_ports(22) + + with container: + install_ssh_commands = [ + "microdnf install -y openssh-server", + "ssh-keygen -A", + "mkdir -p /var/run/sshd", + "echo 'root:testpassword' | chpasswd", + "sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config", + "sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config", + "sed -i 's/#PermitTunnel no/PermitTunnel yes/' /etc/ssh/sshd_config", + "sed -i 's/#ListenAddress 0.0.0.0/ListenAddress 0.0.0.0/' /etc/ssh/sshd_config", + "sed -i 's/#LogLevel INFO/LogLevel DEBUG/' /etc/ssh/sshd_config", + "cat /etc/ssh/sshd_config", + "/usr/sbin/sshd", + ] + + for cmd in install_ssh_commands: + exit_code, output = container.exec(cmd) + if exit_code != 0 and "sshd" not in cmd: + raise RuntimeError(f"Failed to execute: {cmd}\nOutput: {output}") + + yield container + + +@pytest.fixture(scope="module") +def ssh_session(mysql_ssh_container): + ssh_config = SSHTunnelConfiguration( + enabled=True, + executable="ssh", + hostname=mysql_ssh_container.get_container_host_ip(), + port=mysql_ssh_container.get_exposed_port(22), + username="root", + password="testpassword", + local_port=3307, + extra_args=["-o ProxyJump=none"] + ) + + db_config = CredentialsConfiguration( + hostname="127.0.0.1", + username="root", + password=mysql_ssh_container.root_password, + port=3306, + ) + + connection = Connection( + id=1, + name="test_ssh_session", + engine=ConnectionEngine.MYSQL, + configuration=db_config, + ssh_tunnel=ssh_config, + ) + + session = Session(connection=connection) + session.connect() + yield session + session.disconnect() + + +@pytest.mark.integration +@pytest.mark.xdist_group("mysql") +@pytest.mark.skip(reason="MySqlContainer SSH tunnel not work") +class TestMySQLSSHTunnel(BaseSSHTunnelTests): + pass diff --git a/tests/engines/postgresql/conftest.py b/tests/engines/postgresql/conftest.py index c4ba837..d3e004a 100644 --- a/tests/engines/postgresql/conftest.py +++ b/tests/engines/postgresql/conftest.py @@ -6,29 +6,72 @@ from structures.session import Session from structures.connection import Connection, ConnectionEngine from structures.configurations import CredentialsConfiguration +from structures.engines.postgresql.database import PostgreSQLTable +from structures.engines.postgresql.datatype import PostgreSQLDataType +from structures.engines.postgresql.indextype import PostgreSQLIndexType POSTGRESQL_VERSIONS: list[str] = [ - "postgres:latest", + "postgres:18", + "postgres:17", "postgres:16", "postgres:15", ] +def create_users_table_postgresql(postgresql_database, postgresql_session) -> PostgreSQLTable: + ctx = postgresql_session.context + + table = ctx.build_empty_table(postgresql_database, name="users") + table.schema = "public" + + id_column = ctx.build_empty_column( + table, + PostgreSQLDataType.SERIAL, + name="id", + is_nullable=False, + ) + + name_column = ctx.build_empty_column( + table, + PostgreSQLDataType.VARCHAR, + name="name", + is_nullable=False, + length=255, + ) + + table.columns.append(id_column) + table.columns.append(name_column) + + primary_index = ctx.build_empty_index( + table, + PostgreSQLIndexType.PRIMARY, + ["id"], + name="users_pkey", + ) + table.indexes.append(primary_index) + + table.create() + postgresql_database.tables.refresh() + return next(t for t in postgresql_database.tables.get_value() if t.name == "users") + + def pytest_generate_tests(metafunc): if "postgresql_version" in metafunc.fixturenames: metafunc.parametrize("postgresql_version", POSTGRESQL_VERSIONS, scope="module") @pytest.fixture(scope="module") -def postgresql_container(postgresql_version): - with PostgresContainer( +def postgresql_container(postgresql_version, worker_id): + container = PostgresContainer( postgresql_version, - name=f"petersql_test_{postgresql_version.replace(':', '_')}", + name=f"petersql_test_{worker_id}_{postgresql_version.replace(':', '_')}", mem_limit="512m", memswap_limit="768m", nano_cpus=1_000_000_000, shm_size="128m", - ) as container: + ) + + with container: yield container @@ -51,3 +94,48 @@ def postgresql_session(postgresql_container): session.connect() yield session session.disconnect() + + +@pytest.fixture(scope="function") +def postgresql_database(postgresql_session): + """Fixture that provides a PostgreSQL database for tests.""" + # PostgreSQL uses the 'test' database created by the container + # The 'public' schema is the default schema in that database + postgresql_session.context.databases.refresh() + database = next(db for db in postgresql_session.context.databases.get_value() if db.name == "test") + yield database + # Cleanup: drop all tables in public schema + database.tables.refresh() + for table in database.tables.get_value(): + postgresql_session.context.execute(f"DROP TABLE IF EXISTS public.{table.name} CASCADE") + + +# Unified fixtures for base test suites +@pytest.fixture +def session(postgresql_session): + """Alias for postgresql_session to match base test suite parameter names.""" + return postgresql_session + + +@pytest.fixture +def database(postgresql_database): + """Alias for postgresql_database to match base test suite parameter names.""" + return postgresql_database + + +@pytest.fixture +def create_users_table(): + """Provide the create_users_table helper function.""" + return create_users_table_postgresql + + +@pytest.fixture +def datatype_class(): + """Provide the engine-specific datatype class.""" + return PostgreSQLDataType + + +@pytest.fixture +def indextype_class(): + """Provide the engine-specific indextype class.""" + return PostgreSQLIndexType diff --git a/tests/engines/postgresql/test_context.py b/tests/engines/postgresql/test_context.py index 12e14e5..40e361d 100644 --- a/tests/engines/postgresql/test_context.py +++ b/tests/engines/postgresql/test_context.py @@ -1,6 +1,8 @@ import pytest +@pytest.mark.integration +@pytest.mark.xdist_group("postgresql") class TestPostgreSQLContext: """Tests for PostgreSQL context - focus on reading database structures.""" @@ -35,14 +37,14 @@ def test_context_get_server_uptime(self, postgresql_session): assert uptime is not None assert uptime >= 0 - def test_context_build_sql_safe_name(self, postgresql_session): - """Test building SQL safe names uses IDENTIFIER_QUOTE.""" + def test_context_quote_identifier(self, postgresql_session): + """Test building SQL safe names uses IDENTIFIER_QUOTE_CHAR.""" ctx = postgresql_session.context - quote = ctx.IDENTIFIER_QUOTE + quote = ctx.IDENTIFIER_QUOTE_CHAR assert quote == '"' - assert ctx.build_sql_safe_name("normal") == "normal" - assert ctx.build_sql_safe_name("with space") == f'{quote}with space{quote}' + assert ctx.quote_identifier("normal") == "normal" + assert ctx.quote_identifier("with space") == f'{quote}with space{quote}' def test_context_databases_list(self, postgresql_session): """Test reading databases list from server.""" diff --git a/tests/engines/postgresql/test_integration.py b/tests/engines/postgresql/test_integration.py deleted file mode 100644 index 70bf5e0..0000000 --- a/tests/engines/postgresql/test_integration.py +++ /dev/null @@ -1,250 +0,0 @@ -import pytest -from structures.engines.postgresql.database import PostgreSQLTable -from structures.engines.postgresql.datatype import PostgreSQLDataType -from structures.engines.postgresql.indextype import PostgreSQLIndexType -from structures.ssh_tunnel import SSHTunnel - - -def create_users_table(postgresql_database, postgresql_session) -> PostgreSQLTable: - """Helper: create and save a users table with id and name columns. - - Uses build_empty_* API from context to construct objects. - Returns the persisted table from the database (with proper handlers). - """ - ctx = postgresql_session.context - - table = ctx.build_empty_table(postgresql_database, name="users", schema="public") - - id_column = ctx.build_empty_column( - table, - PostgreSQLDataType.SERIAL, - name="id", - is_nullable=False, - ) - - name_column = ctx.build_empty_column( - table, - PostgreSQLDataType.VARCHAR, - name="name", - is_nullable=False, - length=255, - ) - - table.columns.append(id_column) - table.columns.append(name_column) - - primary_index = ctx.build_empty_index( - table, - PostgreSQLIndexType.PRIMARY, - ["id"], - name="users_pkey", - ) - table.indexes.append(primary_index) - - # Create table directly via raw SQL - ctx.execute(table.raw_create()) - - # Refresh tables to get the persisted table with proper handlers - postgresql_database.tables.refresh() - return next(t for t in postgresql_database.tables.get_value() if t.name == "users") - - -@pytest.fixture(scope="function") -def ssh_postgresql_session(postgresql_container, postgresql_session): - """Create SSH tunnel session for testing.""" - try: - # Create SSH tunnel to PostgreSQL container - tunnel = SSHTunnel( - postgresql_container.get_container_host_ip(), - 22, # Assuming SSH access to host - ssh_username=None, - ssh_password=None, - remote_port=postgresql_container.get_exposed_port(5432), - local_bind_address=('localhost', 0) - ) - - tunnel.start(timeout=5) - - # Create connection using tunnel - from structures.session import Session - from structures.connection import Connection, ConnectionEngine - from structures.configurations import CredentialsConfiguration - - config = CredentialsConfiguration( - hostname="localhost", - username=postgresql_container.username, - password=postgresql_container.password, - port=tunnel.local_port, - ) - - connection = Connection( - id=1, - name="ssh_postgresql_test", - engine=ConnectionEngine.POSTGRESQL, - configuration=config, - ) - - session = Session(connection=connection) - session.connect() - - yield session, tunnel - - except Exception: - pytest.skip("SSH tunnel not available") - - finally: - try: - session.disconnect() - except: - pass - try: - tunnel.stop() - except: - pass - - -class TestPostgreSQLIntegration: - """Integration tests for PostgreSQL engine using build_empty_* API.""" - - def test_read_database_properties(self, postgresql_session): - """Test reading database properties.""" - ctx = postgresql_session.context - databases = ctx.databases.get_value() - - for db in databases: - assert db.id is not None - assert db.name is not None - assert db.total_bytes >= 0 - assert db.context == ctx - - def test_ssh_tunnel_basic_operations(self, ssh_postgresql_session, postgresql_database): - """Test basic CRUD operations through SSH tunnel.""" - session, tunnel = ssh_postgresql_session - - # Create table - table = create_users_table(postgresql_database, session) - - # Test INSERT - record = session.context.build_empty_record(table, values={"name": "John Doe"}) - assert record.insert() is True - - # Test SELECT - table.load_records() - records = table.records.get_value() - assert len(records) == 1 - assert records[0].values["name"] == "John Doe" - - # Test UPDATE - record = records[0] - record.values["name"] = "Jane Doe" - assert record.update() is True - - # Verify UPDATE - table.load_records() - records = table.records.get_value() - assert records[0].values["name"] == "Jane Doe" - - # Test DELETE - assert record.delete() is True - - # Verify DELETE - table.load_records() - assert len(table.records.get_value()) == 0 - - table.drop() - - def test_ssh_tunnel_transaction_support(self, ssh_postgresql_session, postgresql_database): - """Test transaction support through SSH tunnel.""" - session, tunnel = ssh_postgresql_session - table = create_users_table(postgresql_database, session) - - # Test successful transaction - with session.context.transaction() as tx: - tx.execute("INSERT INTO public.users (name) VALUES (%s)", ("test1",)) - tx.execute("INSERT INTO public.users (name) VALUES (%s)", ("test2",)) - - # Verify data was committed - session.context.execute("SELECT COUNT(*) as count FROM public.users") - result = session.context.fetchone() - assert result['count'] == 2 - - # Test failed transaction - try: - with session.context.transaction() as tx: - tx.execute("INSERT INTO public.users (name) VALUES (%s)", ("test3",)) - tx.execute("INVALID SQL") # Should fail - except: - pass # Expected to fail - - # Verify rollback worked - session.context.execute("SELECT COUNT(*) as count FROM public.users") - result = session.context.fetchone() - assert result['count'] == 2 - - table.drop() - - def test_ssh_tunnel_error_handling(self, ssh_postgresql_session, postgresql_database): - """Test error handling through SSH tunnel.""" - session, tunnel = ssh_postgresql_session - table = create_users_table(postgresql_database, session) - - # Test invalid SQL - try: - session.context.execute("INVALID SQL QUERY") - assert False, "Should have raised exception" - except Exception: - pass # Expected - - # Test connection is still working - result = session.context.execute("SELECT 1 as test") - assert result is True - - table.drop() - - def test_read_views_from_database(self, postgresql_session): - """Test reading views from database.""" - ctx = postgresql_session.context - databases = ctx.databases.get_value() - - postgres_db = next((db for db in databases if db.name == "postgres"), None) - assert postgres_db is not None - - views = ctx.get_views(postgres_db) - assert isinstance(views, list) - - def test_read_triggers_from_database(self, postgresql_session): - """Test reading triggers from database.""" - ctx = postgresql_session.context - databases = ctx.databases.get_value() - - postgres_db = next((db for db in databases if db.name == "postgres"), None) - assert postgres_db is not None - - triggers = ctx.get_triggers(postgres_db) - assert isinstance(triggers, list) - - def test_datatype_class(self, postgresql_session): - """Test PostgreSQL datatype class is properly configured.""" - ctx = postgresql_session.context - datatype_class = ctx.DATATYPE - - all_types = datatype_class.get_all() - assert len(all_types) > 0 - - type_names = [t.name.lower() for t in all_types] - assert "integer" in type_names or "int4" in type_names - assert "text" in type_names - assert "boolean" in type_names or "bool" in type_names - - def test_indextype_class(self, postgresql_session): - """Test PostgreSQL indextype class is properly configured.""" - ctx = postgresql_session.context - indextype_class = ctx.INDEXTYPE - - all_types = indextype_class.get_all() - assert len(all_types) > 0 - - def test_quote_identifier(self, postgresql_session): - """Test PostgreSQL uses double quotes for identifiers.""" - ctx = postgresql_session.context - assert ctx.IDENTIFIER_QUOTE == '"' diff --git a/tests/engines/postgresql/test_integration_suite.py b/tests/engines/postgresql/test_integration_suite.py new file mode 100644 index 0000000..e218e9b --- /dev/null +++ b/tests/engines/postgresql/test_integration_suite.py @@ -0,0 +1,132 @@ +""" +PostgreSQL integration tests using base test suites. + +These tests inherit from base test suite classes and are automatically parametrized +with different PostgreSQL versions via conftest.py fixtures. + +NOTE: Currently skipped due to PostgreSQL builder bugs (raw_create, index creation). +These need to be fixed separately before enabling these tests. +""" +import pytest +from structures.engines.postgresql.datatype import PostgreSQLDataType +from structures.engines.postgresql.indextype import PostgreSQLIndexType + +from tests.engines.base_table_tests import BaseTableTests +from tests.engines.base_record_tests import BaseRecordTests +from tests.engines.base_column_tests import BaseColumnTests +from tests.engines.base_index_tests import BaseIndexTests +from tests.engines.base_foreignkey_tests import BaseForeignKeyTests +from tests.engines.base_check_tests import BaseCheckTests +from tests.engines.base_trigger_tests import BaseTriggerTests +from tests.engines.base_function_tests import BaseFunctionTests +from tests.engines.base_procedure_tests import BaseProcedureTests +from tests.engines.base_view_tests import BaseViewSaveTests, BaseViewIsNewTests + + +@pytest.mark.integration +@pytest.mark.xdist_group("postgresql") +class TestPostgreSQLTable(BaseTableTests): + pass + + +@pytest.mark.integration +@pytest.mark.xdist_group("postgresql") +class TestPostgreSQLRecord(BaseRecordTests): + pass + + +@pytest.mark.integration +@pytest.mark.xdist_group("postgresql") +class TestPostgreSQLColumn(BaseColumnTests): + pass + + +@pytest.mark.integration +@pytest.mark.xdist_group("postgresql") +class TestPostgreSQLIndex(BaseIndexTests): + pass + + +@pytest.mark.integration +@pytest.mark.xdist_group("postgresql") +class TestPostgreSQLForeignKey(BaseForeignKeyTests): + + def get_datatype_class(self): + return PostgreSQLDataType + + def get_indextype_class(self): + return PostgreSQLIndexType + + def get_primary_key_name(self) -> str: + return "posts_pkey" + + +@pytest.mark.integration +@pytest.mark.xdist_group("postgresql") +class TestPostgreSQLCheck(BaseCheckTests): + pass + + +@pytest.mark.integration +@pytest.mark.xdist_group("postgresql") +class TestPostgreSQLTrigger(BaseTriggerTests): + + def get_trigger_statement(self, db_name: str, table_name: str) -> str: + return f""" + CREATE OR REPLACE FUNCTION trg_users_insert_func() RETURNS TRIGGER AS $$ + BEGIN + RETURN NEW; + END; + $$ LANGUAGE plpgsql; + + CREATE TRIGGER trg_users_insert + AFTER INSERT ON public.{table_name} + FOR EACH ROW EXECUTE FUNCTION trg_users_insert_func(); + """ + + +@pytest.mark.integration +@pytest.mark.xdist_group("postgresql") +class TestPostgreSQLViewSave(BaseViewSaveTests): + + def get_view_statement(self) -> str: + return "SELECT 1 as id, 'test' as name" + + def get_simple_view_statement(self) -> str: + return "SELECT 1 as id" + + def get_updated_view_statement(self) -> str: + return "SELECT 1 as id, 'updated' as name" + + +@pytest.mark.integration +@pytest.mark.xdist_group("postgresql") +class TestPostgreSQLFunction(BaseFunctionTests): + + def get_function_statement(self) -> str: + return "RETURN x + 1;" + + def get_function_parameters(self) -> str: + return "x integer" + + def get_function_returns(self) -> str: + return "integer" + + +@pytest.mark.integration +@pytest.mark.xdist_group("postgresql") +class TestPostgreSQLProcedure(BaseProcedureTests): + + def get_procedure_statement(self) -> str: + return "RAISE NOTICE 'Hello from procedure';" + + def get_procedure_parameters(self) -> str: + return "" + + +@pytest.mark.integration +@pytest.mark.xdist_group("postgresql") +class TestPostgreSQLViewIsNew(BaseViewIsNewTests): + + def get_simple_view_statement(self) -> str: + return "SELECT 1 as id" diff --git a/tests/engines/sqlite/conftest.py b/tests/engines/sqlite/conftest.py index 2280092..7da165b 100644 --- a/tests/engines/sqlite/conftest.py +++ b/tests/engines/sqlite/conftest.py @@ -1,10 +1,53 @@ +import sqlite3 import pytest from structures.session import Session from structures.connection import Connection, ConnectionEngine from structures.configurations import SourceConfiguration -from structures.engines.sqlite.database import SQLiteDatabase +from structures.engines.sqlite.database import SQLiteDatabase, SQLiteTable +from structures.engines.sqlite.datatype import SQLiteDataType +from structures.engines.sqlite.indextype import SQLiteIndexType + +SQLITE_VERSIONS: list[str] = [ + f"sqlite:{sqlite3.sqlite_version}", +] + + +def create_users_table_sqlite(sqlite_database, sqlite_session) -> SQLiteTable: + ctx = sqlite_session.context + + table = ctx.build_empty_table(sqlite_database, name="users") + + id_column = ctx.build_empty_column( + table, + SQLiteDataType.INTEGER, + name="id", + is_auto_increment=True, + is_nullable=False, + ) + + name_column = ctx.build_empty_column( + table, + SQLiteDataType.TEXT, + name="name", + is_nullable=False, + ) + + table.columns.append(id_column) + table.columns.append(name_column) + + primary_index = ctx.build_empty_index( + table, + SQLiteIndexType.PRIMARY, + ["id"], + name="PRIMARY", + ) + table.indexes.append(primary_index) + + table.create() + sqlite_database.tables.refresh() + return next(t for t in sqlite_database.tables.get_value() if t.name == "users") @pytest.fixture(scope="module") @@ -27,3 +70,47 @@ def sqlite_database(sqlite_session): # Use the database from context which has proper handlers configured databases = sqlite_session.context.get_databases() yield databases[0] + + +# Unified fixtures for base test suites +@pytest.fixture +def session(sqlite_session): + """Alias for sqlite_session to match base test suite parameter names.""" + return sqlite_session + + +@pytest.fixture +def database(sqlite_database): + """Alias for sqlite_database to match base test suite parameter names.""" + return sqlite_database + + +@pytest.fixture(scope="function") +def create_users_table(sqlite_database): + """Provide the create_users_table helper function with cleanup.""" + created_tables = [] + + def _create_and_track(database, session): + table = create_users_table_sqlite(database, session) + created_tables.append(table) + return table + + yield _create_and_track + + for table in created_tables: + try: + table.drop() + except: + pass + + +@pytest.fixture +def datatype_class(): + """Provide the engine-specific datatype class.""" + return SQLiteDataType + + +@pytest.fixture +def indextype_class(): + """Provide the engine-specific indextype class.""" + return SQLiteIndexType diff --git a/tests/engines/sqlite/test_context.py b/tests/engines/sqlite/test_context.py index 0455a8c..8e6a38d 100644 --- a/tests/engines/sqlite/test_context.py +++ b/tests/engines/sqlite/test_context.py @@ -96,12 +96,12 @@ def test_context_get_server_version(self, sqlite_session): assert version is not None assert len(version) > 0 - def test_context_build_sql_safe_name(self, sqlite_session): - """Test building SQL safe names uses IDENTIFIER_QUOTE.""" + def test_context_quote_identifier(self, sqlite_session): + """Test building SQL safe names uses IDENTIFIER_QUOTE_CHAR.""" ctx = sqlite_session.context - quote = ctx.IDENTIFIER_QUOTE + quote = ctx.IDENTIFIER_QUOTE_CHAR # Simple names don't need quoting - assert ctx.build_sql_safe_name("normal") == "normal" - # Names with spaces are quoted using IDENTIFIER_QUOTE - assert ctx.build_sql_safe_name("with space") == f'{quote}with space{quote}' + assert ctx.quote_identifier("normal") == "normal" + # Names with spaces are quoted using IDENTIFIER_QUOTE_CHAR + assert ctx.quote_identifier("with space") == f'{quote}with space{quote}' diff --git a/tests/engines/sqlite/test_integration.py b/tests/engines/sqlite/test_integration.py deleted file mode 100644 index 9cd7246..0000000 --- a/tests/engines/sqlite/test_integration.py +++ /dev/null @@ -1,300 +0,0 @@ -from structures.engines.sqlite.database import ( - SQLiteTable, - SQLiteColumn, - SQLiteIndex, - SQLiteRecord, - SQLiteView, - SQLiteTrigger, -) -from structures.engines.sqlite.datatype import SQLiteDataType -from structures.engines.sqlite.indextype import SQLiteIndexType - - -def create_users_table(sqlite_database, sqlite_session) -> SQLiteTable: - """Helper: create and save a users table with id and name columns. - - Uses build_empty_* API from context to construct objects. - Returns the persisted table from the database (with proper handlers). - """ - ctx = sqlite_session.context - - table = ctx.build_empty_table(sqlite_database, name="users") - - id_column = ctx.build_empty_column( - table, - SQLiteDataType.INTEGER, - name="id", - is_auto_increment=True, - is_nullable=False, - ) - - name_column = ctx.build_empty_column( - table, - SQLiteDataType.TEXT, - name="name", - is_nullable=False, - length=255, - ) - - table.columns.append(id_column) - table.columns.append(name_column) - - primary_index = ctx.build_empty_index( - table, - SQLiteIndexType.PRIMARY, - ["id"], - name="PRIMARY", - ) - table.indexes.append(primary_index) - - # save() calls create() + database.refresh() - table.save() - - # Explicitly refresh tables to get the persisted table with proper handlers - sqlite_database.tables.refresh() - return next(t for t in sqlite_database.tables.get_value() if t.name == "users") - - -class TestSQLiteIntegration: - """Integration tests for SQLite engine.""" - - def test_table_create_and_drop(self, sqlite_session, sqlite_database): - """Test table creation and deletion.""" - # create_users_table uses save() which creates and refreshes - table = create_users_table(sqlite_database, sqlite_session) - assert table.is_valid is True - assert table.id >= 0 # ID should be assigned after save (0-indexed) - - # Verify table exists in database - tables = sqlite_database.tables.get_value() - assert any(t.name == "users" for t in tables) - - assert table.drop() is True - - # Refresh to verify table was deleted - sqlite_database.tables.refresh() - tables = sqlite_database.tables.get_value() - assert not any(t.name == "users" for t in tables) - - def test_record_insert(self, sqlite_session, sqlite_database): - """Test record insertion.""" - table = create_users_table(sqlite_database, sqlite_session) - - table.load_records() - assert len(table.records.get_value()) == 0 - - record = sqlite_session.context.build_empty_record(table, values={"name": "John Doe"}) - assert record.insert() is True - - table.load_records() - records = table.records.get_value() - assert len(records) == 1 - assert records[0].values["name"] == "John Doe" - - table.drop() - - def test_record_update(self, sqlite_session, sqlite_database): - """Test record update.""" - table = create_users_table(sqlite_database, sqlite_session) - table.load_records() # Initialize records before build_empty_record - - record = sqlite_session.context.build_empty_record(table, values={"name": "John Doe"}) - record.insert() - - table.load_records() - record = table.records.get_value()[0] - assert record.is_valid() is True - assert record.is_new() is False - - record.values["name"] = "Jane Doe" - assert record.update() is True - - table.load_records() - records = table.records.get_value() - assert records[0].values["name"] == "Jane Doe" - - table.drop() - - def test_record_delete(self, sqlite_session, sqlite_database): - """Test record deletion.""" - table = create_users_table(sqlite_database, sqlite_session) - table.load_records() # Initialize records before build_empty_record - - record = sqlite_session.context.build_empty_record(table, values={"name": "John Doe"}) - record.insert() - - table.load_records() - record = table.records.get_value()[0] - assert record.delete() is True - - table.load_records() - assert len(table.records.get_value()) == 0 - - table.drop() - - def test_column_add(self, sqlite_session, sqlite_database): - """Test adding a column to an existing table.""" - table = create_users_table(sqlite_database, sqlite_session) - - email_column = sqlite_session.context.build_empty_column( - table, - SQLiteDataType.TEXT, - name="email", - is_nullable=True, - ) - assert email_column.add() is True - - # Refresh columns to verify column was added - table.columns.refresh() - columns = table.columns.get_value() - assert any(c.name == "email" for c in columns) - - table.drop() - - def test_column_rename(self, sqlite_session, sqlite_database): - """Test renaming a column.""" - table = create_users_table(sqlite_database, sqlite_session) - - # Add a column to rename - email_column = sqlite_session.context.build_empty_column( - table, - SQLiteDataType.TEXT, - name="email", - is_nullable=True, - ) - assert email_column.add() is True - - # Refresh columns to get the persisted column - table.columns.refresh() - email_column = next(c for c in table.columns.get_value() if c.name == "email") - - # Rename the column - assert email_column.rename("user_email") is True - - # Refresh columns to verify rename - table.columns.refresh() - columns = table.columns.get_value() - assert any(c.name == "user_email" for c in columns) - assert not any(c.name == "email" for c in columns) - - table.drop() - - def test_column_with_check_constraint(self, sqlite_session, sqlite_database): - """Test column with CHECK constraint.""" - table = create_users_table(sqlite_database, sqlite_session) - table.load_records() # Initialize records before build_empty_record - ctx = sqlite_session.context - - email_column = ctx.build_empty_column( - table, - SQLiteDataType.TEXT, - name="email", - is_nullable=True, - check="email LIKE '%@%'", - ) - email_column.add() - table.columns.set_value(list(table.columns.get_value()) + [email_column]) - - # Valid email should insert - valid_record = ctx.build_empty_record(table, values={"name": "Alice", "email": "alice@example.com"}) - assert valid_record.insert() is True - - # Invalid email should fail - invalid_record = ctx.build_empty_record(table, values={"name": "Bob", "email": "invalidemail"}) - assert invalid_record.insert() is False - - table.drop() - - def test_table_truncate(self, sqlite_session, sqlite_database): - """Test table truncation.""" - table = create_users_table(sqlite_database, sqlite_session) - table.load_records() # Initialize records before build_empty_record - - record = sqlite_session.context.build_empty_record(table, values={"name": "John Doe"}) - record.insert() - - table.load_records() - assert len(table.records.get_value()) == 1 - - assert table.truncate() is True - - table.load_records() - assert len(table.records.get_value()) == 0 - - table.drop() - - def test_index_create_and_drop(self, sqlite_session, sqlite_database): - """Test index creation and deletion.""" - table = create_users_table(sqlite_database, sqlite_session) - - idx_name = sqlite_session.context.build_empty_index( - table, - SQLiteIndexType.INDEX, - ["name"], - name="idx_name", - ) - assert idx_name.create() is True - - # Refresh indexes to verify index was created - table.indexes.refresh() - indexes = table.indexes.get_value() - assert any(i.name == "idx_name" for i in indexes) - - assert idx_name.drop() is True - - # Refresh indexes to verify index was deleted - table.indexes.refresh() - indexes = table.indexes.get_value() - assert not any(i.name == "idx_name" for i in indexes) - - table.drop() - - def test_view_create_and_drop(self, sqlite_session, sqlite_database): - """Test view creation and deletion.""" - table = create_users_table(sqlite_database, sqlite_session) - - view = sqlite_session.context.build_empty_view( - sqlite_database, - name="active_users_view", - sql="SELECT * FROM users WHERE name IS NOT NULL", - ) - assert view.create() is True - - # Refresh views to verify view was created - sqlite_database.views.refresh() - views = sqlite_database.views.get_value() - assert any(v.name == "active_users_view" for v in views) - - assert view.drop() is True - - # Refresh views to verify view was deleted - sqlite_database.views.refresh() - views = sqlite_database.views.get_value() - assert not any(v.name == "active_users_view" for v in views) - - table.drop() - - def test_trigger_create_and_drop(self, sqlite_session, sqlite_database): - """Test trigger creation and deletion.""" - table = create_users_table(sqlite_database, sqlite_session) - - trigger = sqlite_session.context.build_empty_trigger( - sqlite_database, - name="trg_users_insert", - sql="AFTER INSERT ON users BEGIN SELECT 1; END", - ) - assert trigger.create() is True - - # Refresh triggers to verify trigger was created - sqlite_database.triggers.refresh() - triggers = sqlite_database.triggers.get_value() - assert any(t.name == "trg_users_insert" for t in triggers) - - assert trigger.drop() is True - - # Refresh triggers to verify trigger was deleted - sqlite_database.triggers.refresh() - triggers = sqlite_database.triggers.get_value() - assert not any(t.name == "trg_users_insert" for t in triggers) - - table.drop() diff --git a/tests/engines/sqlite/test_integration_suite.py b/tests/engines/sqlite/test_integration_suite.py new file mode 100644 index 0000000..f5cdaad --- /dev/null +++ b/tests/engines/sqlite/test_integration_suite.py @@ -0,0 +1,78 @@ +import pytest +from structures.engines.sqlite.datatype import SQLiteDataType +from structures.engines.sqlite.indextype import SQLiteIndexType + +from tests.engines.base_table_tests import BaseTableTests +from tests.engines.base_record_tests import BaseRecordTests +from tests.engines.base_column_tests import BaseColumnTests +from tests.engines.base_index_tests import BaseIndexTests +from tests.engines.base_foreignkey_tests import BaseForeignKeyTests +from tests.engines.base_check_tests import BaseCheckTests +from tests.engines.base_trigger_tests import BaseTriggerTests +from tests.engines.base_view_tests import BaseViewSaveTests, BaseViewIsNewTests + + +@pytest.mark.integration +class TestSQLiteTable(BaseTableTests): + pass + + +@pytest.mark.integration +class TestSQLiteRecord(BaseRecordTests): + pass + + +@pytest.mark.integration +class TestSQLiteColumn(BaseColumnTests): + pass + + +@pytest.mark.integration +class TestSQLiteIndex(BaseIndexTests): + pass + + +@pytest.mark.integration +@pytest.mark.skip(reason="SQLite requires foreign keys to be defined inline in CREATE TABLE statement") +class TestSQLiteForeignKey(BaseForeignKeyTests): + + def get_datatype_class(self): + return SQLiteDataType + + def get_indextype_class(self): + return SQLiteIndexType + + def get_primary_key_name(self) -> str: + return "PRIMARY" + + +@pytest.mark.integration +class TestSQLiteCheck(BaseCheckTests): + pass + + +@pytest.mark.integration +class TestSQLiteTrigger(BaseTriggerTests): + + def get_trigger_statement(self, db_name: str, table_name: str) -> str: + return f"AFTER INSERT ON {table_name} BEGIN SELECT 1; END" + + +@pytest.mark.integration +class TestSQLiteViewSave(BaseViewSaveTests): + + def get_view_statement(self) -> str: + return "SELECT id, name FROM users WHERE id > 0" + + def get_simple_view_statement(self) -> str: + return "SELECT id FROM users" + + def get_updated_view_statement(self) -> str: + return "SELECT id, name FROM users" + + +@pytest.mark.integration +class TestSQLiteViewIsNew(BaseViewIsNewTests): + + def get_simple_view_statement(self) -> str: + return "SELECT * FROM users" diff --git a/tests/test_column_controller.py b/tests/test_column_controller.py index dba0509..bc475cf 100644 --- a/tests/test_column_controller.py +++ b/tests/test_column_controller.py @@ -3,7 +3,7 @@ from structures.engines.sqlite.context import SQLiteContext from structures.engines.sqlite.database import SQLiteDatabase, SQLiteTable, SQLiteIndex, SQLiteColumn -from windows.main.column import TableColumnsController +from windows.main.tabs.column import TableColumnsController @pytest.fixture @@ -37,9 +37,9 @@ def mock_table(mock_session): @patch('wx.GetApp') -@patch('windows.main.column.CURRENT_SESSION') -@patch('windows.main.column.CURRENT_TABLE') -@patch('windows.main.column.NEW_TABLE') +@patch('windows.main.tabs.column.CURRENT_SESSION') +@patch('windows.main.tabs.column.CURRENT_TABLE') +@patch('windows.main.tabs.column.NEW_TABLE') def test_append_column_index(mock_new_table, mock_current_table, mock_current_session, mock_get_app, mock_session, mock_table): # Setup mocks mock_get_app.return_value = Mock() @@ -72,9 +72,9 @@ def test_append_column_index(mock_new_table, mock_current_table, mock_current_se @patch('wx.GetApp') -@patch('windows.main.column.CURRENT_SESSION') -@patch('windows.main.column.CURRENT_TABLE') -@patch('windows.main.column.NEW_TABLE') +@patch('windows.main.tabs.column.CURRENT_SESSION') +@patch('windows.main.tabs.column.CURRENT_TABLE') +@patch('windows.main.tabs.column.NEW_TABLE') def test_on_column_insert(mock_new_table, mock_current_table, mock_current_session, mock_get_app, mock_session, mock_table): # Setup mocks mock_get_app.return_value = Mock() @@ -115,9 +115,9 @@ def test_on_column_insert(mock_new_table, mock_current_table, mock_current_sessi @patch('wx.GetApp') -@patch('windows.main.column.CURRENT_SESSION') -@patch('windows.main.column.CURRENT_TABLE') -@patch('windows.main.column.NEW_TABLE') +@patch('windows.main.tabs.column.CURRENT_SESSION') +@patch('windows.main.tabs.column.CURRENT_TABLE') +@patch('windows.main.tabs.column.NEW_TABLE') def test_on_column_delete(mock_new_table, mock_current_table, mock_current_session, mock_get_app, mock_session, mock_table): # Setup mocks mock_get_app.return_value = Mock() @@ -163,10 +163,10 @@ def test_on_column_delete(mock_new_table, mock_current_table, mock_current_sessi @patch('wx.GetApp') -@patch('windows.main.column.CURRENT_SESSION') -@patch('windows.main.column.CURRENT_TABLE') -@patch('windows.main.column.CURRENT_COLUMN') -@patch('windows.main.column.NEW_TABLE') +@patch('windows.main.tabs.column.CURRENT_SESSION') +@patch('windows.main.tabs.column.CURRENT_TABLE') +@patch('windows.main.tabs.column.CURRENT_COLUMN') +@patch('windows.main.tabs.column.NEW_TABLE') def test_on_column_move_up(mock_new_table, mock_current_column, mock_current_table, mock_current_session, mock_get_app, mock_session, mock_table): # Setup mocks mock_get_app.return_value = Mock() @@ -205,9 +205,9 @@ def test_on_column_move_up(mock_new_table, mock_current_column, mock_current_tab @patch('wx.GetApp') -@patch('windows.main.column.CURRENT_SESSION') -@patch('windows.main.column.CURRENT_TABLE') -@patch('windows.main.column.NEW_TABLE') +@patch('windows.main.tabs.column.CURRENT_SESSION') +@patch('windows.main.tabs.column.CURRENT_TABLE') +@patch('windows.main.tabs.column.NEW_TABLE') def test_insert_column_index(mock_new_table, mock_current_table, mock_current_session, mock_get_app, mock_session, mock_table): # Setup mocks mock_get_app.return_value = Mock() diff --git a/tests/test_configurations.py b/tests/test_configurations.py index 04f918d..d37f662 100644 --- a/tests/test_configurations.py +++ b/tests/test_configurations.py @@ -1,52 +1,72 @@ import pytest -from structures.configurations import CredentialsConfiguration, SourceConfiguration, SSHTunnelConfiguration +from structures.configurations import ( + CredentialsConfiguration, + SourceConfiguration, + SSHTunnelConfiguration, +) class TestConfigurations: def test_credentials_configuration(self): config = CredentialsConfiguration( - hostname='localhost', - username='user', - password='pass', - port=3306 + hostname="localhost", username="user", password="pass", port=3306 ) - assert config.hostname == 'localhost' - assert config.username == 'user' - assert config.password == 'pass' + assert config.hostname == "localhost" + assert config.username == "user" + assert config.password == "pass" assert config.port == 3306 def test_source_configuration(self): - config = SourceConfiguration(filename='/path/to/db.sqlite') - assert config.filename == '/path/to/db.sqlite' + config = SourceConfiguration(filename="/path/to/db.sqlite") + assert config.filename == "/path/to/db.sqlite" def test_ssh_tunnel_configuration(self): config = SSHTunnelConfiguration( enabled=True, - executable='ssh', - hostname='remote.host', + executable="ssh", + hostname="remote.host", port=22, - username='sshuser', - password='sshpwd', - local_port=3307 + username="sshuser", + password="sshpwd", + local_port=3307, ) assert config.enabled == True - assert config.executable == 'ssh' - assert config.hostname == 'remote.host' + assert config.executable == "ssh" + assert config.hostname == "remote.host" assert config.port == 22 - assert config.username == 'sshuser' - assert config.password == 'sshpwd' + assert config.username == "sshuser" + assert config.password == "sshpwd" assert config.local_port == 3307 assert config.is_enabled == True def test_ssh_tunnel_configuration_disabled(self): config = SSHTunnelConfiguration( enabled=False, - executable='ssh', - hostname='remote.host', + executable="ssh", + hostname="remote.host", port=22, username=None, password=None, - local_port=3307 + local_port=3307, ) assert config.is_enabled == False + + def test_ssh_tunnel_configuration_supports_remote_target_and_identity(self): + config = SSHTunnelConfiguration( + enabled=True, + executable="ssh", + hostname="bastion.example.com", + port=22, + username="sshuser", + password=None, + local_port=0, + remote_host="db.internal", + remote_port=3306, + identity_file="/home/user/.ssh/id_ed25519", + ) + + assert config.is_enabled is True + assert config.remote_host == "db.internal" + assert config.remote_port == 3306 + assert config.identity_file == "/home/user/.ssh/id_ed25519" diff --git a/tests/test_connections.py b/tests/test_connections.py index cda20f9..9289684 100644 --- a/tests/test_connections.py +++ b/tests/test_connections.py @@ -4,8 +4,12 @@ import yaml from structures.connection import Connection, ConnectionEngine, ConnectionDirectory -from structures.configurations import CredentialsConfiguration, SourceConfiguration, SSHTunnelConfiguration -from windows.connections.repository import ConnectionsRepository +from structures.configurations import ( + CredentialsConfiguration, + SourceConfiguration, + SSHTunnelConfiguration, +) +from windows.dialogs.connections.repository import ConnectionsRepository class TestConnectionsRepository: @@ -14,7 +18,8 @@ def temp_yaml(self): """Create a temporary YAML file path for testing.""" import tempfile import os - with tempfile.NamedTemporaryFile(mode='w+', suffix='.yml', delete=False) as tmp: + + with tempfile.NamedTemporaryFile(mode="w+", suffix=".yml", delete=False) as tmp: tmp_path = tmp.name yield tmp_path os.unlink(tmp_path) @@ -27,7 +32,7 @@ def repo(self, temp_yaml, monkeypatch): def test_load_empty_yaml(self, temp_yaml, repo): """Test loading from an empty or non-existent YAML file.""" # Ensure file doesn't exist or is empty - with open(temp_yaml, 'w') as f: + with open(temp_yaml, "w") as f: f.write("") connections = repo.load() @@ -37,33 +42,33 @@ def test_load_connections_from_yaml(self, temp_yaml, repo): """Test loading connections from YAML.""" data = [ { - 'id': 1, - 'name': 'Test SQLite', - 'engine': 'SQLite', - 'configuration': {'filename': ':memory:'}, - 'comments': 'Test connection' + "id": 1, + "name": "Test SQLite", + "engine": "SQLite", + "configuration": {"filename": ":memory:"}, + "comments": "Test connection", }, { - 'id': 2, - 'name': 'Test MySQL', - 'engine': 'MySQL', - 'configuration': { - 'hostname': 'localhost', - 'port': 3306, - 'username': 'user', - 'password': 'pass' + "id": 2, + "name": "Test MySQL", + "engine": "MySQL", + "configuration": { + "hostname": "localhost", + "port": 3306, + "username": "user", + "password": "pass", }, - 'ssh_tunnel': { - 'enabled': True, - 'hostname': 'remote.host', - 'port': 22, - 'username': 'sshuser', - 'password': 'sshpass', - 'local_port': 3307 - } - } + "ssh_tunnel": { + "enabled": True, + "hostname": "remote.host", + "port": 22, + "username": "sshuser", + "password": "sshpass", + "local_port": 3307, + }, + }, ] - with open(temp_yaml, 'w') as f: + with open(temp_yaml, "w") as f: yaml.dump(data, f) connections = repo.load() @@ -72,59 +77,59 @@ def test_load_connections_from_yaml(self, temp_yaml, repo): # Check first connection conn1 = connections[0] assert conn1.id == 1 - assert conn1.name == 'Test SQLite' + assert conn1.name == "Test SQLite" assert conn1.engine == ConnectionEngine.SQLITE assert isinstance(conn1.configuration, SourceConfiguration) - assert conn1.configuration.filename == ':memory:' - assert conn1.comments == 'Test connection' + assert conn1.configuration.filename == ":memory:" + assert conn1.comments == "Test connection" # Check second connection conn2 = connections[1] assert conn2.id == 2 - assert conn2.name == 'Test MySQL' + assert conn2.name == "Test MySQL" assert conn2.engine == ConnectionEngine.MYSQL assert isinstance(conn2.configuration, CredentialsConfiguration) - assert conn2.configuration.hostname == 'localhost' + assert conn2.configuration.hostname == "localhost" assert conn2.configuration.port == 3306 - assert conn2.configuration.username == 'user' - assert conn2.configuration.password == 'pass' + assert conn2.configuration.username == "user" + assert conn2.configuration.password == "pass" assert conn2.ssh_tunnel.enabled is True - assert conn2.ssh_tunnel.hostname == 'remote.host' + assert conn2.ssh_tunnel.hostname == "remote.host" def test_load_directories_from_yaml(self, temp_yaml, repo): """Test loading directories with nested connections.""" data = [ { - 'type': 'directory', - 'name': 'Production', - 'children': [ + "type": "directory", + "name": "Production", + "children": [ { - 'id': 1, - 'name': 'Prod DB', - 'engine': 'PostgreSQL', - 'configuration': { - 'hostname': 'prod.example.com', - 'port': 5432, - 'username': 'produser', - 'password': 'prodpass' - } + "id": 1, + "name": "Prod DB", + "engine": "PostgreSQL", + "configuration": { + "hostname": "prod.example.com", + "port": 5432, + "username": "produser", + "password": "prodpass", + }, } - ] + ], }, { - 'type': 'directory', - 'name': 'Development', - 'children': [ + "type": "directory", + "name": "Development", + "children": [ { - 'id': 2, - 'name': 'Dev DB', - 'engine': 'SQLite', - 'configuration': {'filename': 'dev.db'} + "id": 2, + "name": "Dev DB", + "engine": "SQLite", + "configuration": {"filename": "dev.db"}, } - ] - } + ], + }, ] - with open(temp_yaml, 'w') as f: + with open(temp_yaml, "w") as f: yaml.dump(data, f) items = repo.load() @@ -133,76 +138,88 @@ def test_load_directories_from_yaml(self, temp_yaml, repo): # Check first directory dir1 = items[0] assert isinstance(dir1, ConnectionDirectory) - assert dir1.name == 'Production' + assert dir1.name == "Production" assert len(dir1.children) == 1 conn = dir1.children[0] - assert conn.name == 'Prod DB' + assert conn.name == "Prod DB" assert conn.engine == ConnectionEngine.POSTGRESQL # Check second directory dir2 = items[1] assert isinstance(dir2, ConnectionDirectory) - assert dir2.name == 'Development' + assert dir2.name == "Development" assert len(dir2.children) == 1 conn2 = dir2.children[0] - assert conn2.name == 'Dev DB' + assert conn2.name == "Dev DB" assert conn2.engine == ConnectionEngine.SQLITE def test_add_connection(self, temp_yaml, repo): """Test adding a new connection.""" - config = SourceConfiguration(filename='test.db') + config = SourceConfiguration(filename="test.db") connection = Connection( id=0, - name='New Connection', + name="New Connection", engine=ConnectionEngine.SQLITE, configuration=config, - comments='Added connection' + comments="Added connection", ) conn_id = repo.add_connection(connection) assert conn_id == 0 # Check YAML - with open(temp_yaml, 'r') as f: + with open(temp_yaml, "r") as f: data = yaml.safe_load(f) assert len(data) == 1 - assert data[0]['name'] == 'New Connection' - assert data[0]['id'] == 0 + assert data[0]["name"] == "New Connection" + assert data[0]["id"] == 0 def test_save_connection(self, temp_yaml, repo): """Test saving/updating an existing connection.""" # Start with a connection - data = [{ - 'id': 1, - 'name': 'Original Name', - 'engine': 'SQLite', - 'configuration': {'filename': ':memory:'} - }] - with open(temp_yaml, 'w') as f: + data = [ + { + "id": 1, + "name": "Original Name", + "engine": "SQLite", + "configuration": {"filename": ":memory:"}, + } + ] + with open(temp_yaml, "w") as f: yaml.dump(data, f) # Load and modify connections = repo.load() conn = connections[0] - conn.name = 'Updated Name' + conn.name = "Updated Name" # Save repo.save_connection(conn) # Check YAML was updated - with open(temp_yaml, 'r') as f: + with open(temp_yaml, "r") as f: updated_data = yaml.safe_load(f) - assert updated_data[0]['name'] == 'Updated Name' + assert updated_data[0]["name"] == "Updated Name" def test_delete_connection(self, temp_yaml, repo): """Test deleting a connection.""" # Start with connections data = [ - {'id': 1, 'name': 'Conn1', 'engine': 'SQLite', 'configuration': {'filename': 'db1.db'}}, - {'id': 2, 'name': 'Conn2', 'engine': 'SQLite', 'configuration': {'filename': 'db2.db'}} + { + "id": 1, + "name": "Conn1", + "engine": "SQLite", + "configuration": {"filename": "db1.db"}, + }, + { + "id": 2, + "name": "Conn2", + "engine": "SQLite", + "configuration": {"filename": "db2.db"}, + }, ] - with open(temp_yaml, 'w') as f: + with open(temp_yaml, "w") as f: yaml.dump(data, f) # Load and delete first connection @@ -211,34 +228,34 @@ def test_delete_connection(self, temp_yaml, repo): repo.delete_connection(conn_to_delete) # Check only one connection remains - with open(temp_yaml, 'r') as f: + with open(temp_yaml, "r") as f: updated_data = yaml.safe_load(f) assert len(updated_data) == 1 - assert updated_data[0]['name'] == 'Conn2' + assert updated_data[0]["name"] == "Conn2" def test_add_directory(self, temp_yaml, repo): """Test adding a new directory.""" - with open(temp_yaml, 'w') as f: + with open(temp_yaml, "w") as f: f.write("[]") - directory = ConnectionDirectory(name='New Directory') + directory = ConnectionDirectory(id=-1, name="New Directory") repo.add_directory(directory) # Check YAML - with open(temp_yaml, 'r') as f: + with open(temp_yaml, "r") as f: data = yaml.safe_load(f) assert len(data) == 1 - assert data[0]['type'] == 'directory' - assert data[0]['name'] == 'New Directory' + assert data[0]["type"] == "directory" + assert data[0]["name"] == "New Directory" def test_delete_directory(self, temp_yaml, repo): """Test deleting a directory.""" data = [ - {'type': 'directory', 'name': 'Dir1', 'children': []}, - {'type': 'directory', 'name': 'Dir2', 'children': []} + {"type": "directory", "name": "Dir1", "children": []}, + {"type": "directory", "name": "Dir2", "children": []}, ] - with open(temp_yaml, 'w') as f: + with open(temp_yaml, "w") as f: yaml.dump(data, f) # Load and delete first directory @@ -247,7 +264,7 @@ def test_delete_directory(self, temp_yaml, repo): repo.delete_directory(dir_to_delete) # Check only one directory remains - with open(temp_yaml, 'r') as f: + with open(temp_yaml, "r") as f: updated_data = yaml.safe_load(f) assert len(updated_data) == 1 - assert updated_data[0]['name'] == 'Dir2' + assert updated_data[0]["name"] == "Dir2" diff --git a/tests/ui/test_column_controller.py b/tests/ui/test_column_controller.py index 7913396..209f234 100644 --- a/tests/ui/test_column_controller.py +++ b/tests/ui/test_column_controller.py @@ -2,7 +2,7 @@ from unittest.mock import Mock, patch, call from structures.engines.sqlite.database import SQLiteDatabase, SQLiteTable, SQLiteIndex, SQLiteColumn -from windows.main.column import TableColumnsController +from windows.main.tabs.column import TableColumnsController @pytest.fixture @@ -27,9 +27,9 @@ def mock_table(sqlite_session): @patch('wx.GetApp') -@patch('windows.main.column.CURRENT_SESSION') -@patch('windows.main.column.CURRENT_TABLE') -@patch('windows.main.column.NEW_TABLE') +@patch('windows.main.tabs.column.CURRENT_SESSION') +@patch('windows.main.tabs.column.CURRENT_TABLE') +@patch('windows.main.tabs.column.NEW_TABLE') def test_append_column_index(mock_new_table, mock_current_table, mock_current_session, mock_get_app, sqlite_session, mock_table): mock_get_app.return_value = Mock() mock_current_session.get_value.return_value = sqlite_session @@ -61,9 +61,9 @@ def test_append_column_index(mock_new_table, mock_current_table, mock_current_se @patch('wx.GetApp') -@patch('windows.main.column.CURRENT_SESSION') -@patch('windows.main.column.CURRENT_TABLE') -@patch('windows.main.column.NEW_TABLE') +@patch('windows.main.tabs.column.CURRENT_SESSION') +@patch('windows.main.tabs.column.CURRENT_TABLE') +@patch('windows.main.tabs.column.NEW_TABLE') def test_on_column_insert(mock_new_table, mock_current_table, mock_current_session, mock_get_app, sqlite_session, mock_table): mock_get_app.return_value = Mock() mock_current_session.get_value.return_value = sqlite_session @@ -96,9 +96,9 @@ def test_on_column_insert(mock_new_table, mock_current_table, mock_current_sessi @patch('wx.GetApp') -@patch('windows.main.column.CURRENT_SESSION') -@patch('windows.main.column.CURRENT_TABLE') -@patch('windows.main.column.NEW_TABLE') +@patch('windows.main.tabs.column.CURRENT_SESSION') +@patch('windows.main.tabs.column.CURRENT_TABLE') +@patch('windows.main.tabs.column.NEW_TABLE') def test_on_column_delete(mock_new_table, mock_current_table, mock_current_session, mock_get_app, sqlite_session, mock_table): mock_get_app.return_value = Mock() mock_current_session.get_value.return_value = sqlite_session @@ -136,10 +136,10 @@ def test_on_column_delete(mock_new_table, mock_current_table, mock_current_sessi @patch('wx.GetApp') -@patch('windows.main.column.CURRENT_SESSION') -@patch('windows.main.column.CURRENT_TABLE') -@patch('windows.main.column.CURRENT_COLUMN') -@patch('windows.main.column.NEW_TABLE') +@patch('windows.main.tabs.column.CURRENT_SESSION') +@patch('windows.main.tabs.column.CURRENT_TABLE') +@patch('windows.main.tabs.column.CURRENT_COLUMN') +@patch('windows.main.tabs.column.NEW_TABLE') def test_on_column_move_up(mock_new_table, mock_current_column, mock_current_table, mock_current_session, mock_get_app, sqlite_session, mock_table): mock_get_app.return_value = Mock() mock_current_session.get_value.return_value = sqlite_session @@ -170,9 +170,9 @@ def test_on_column_move_up(mock_new_table, mock_current_column, mock_current_tab @patch('wx.GetApp') -@patch('windows.main.column.CURRENT_SESSION') -@patch('windows.main.column.CURRENT_TABLE') -@patch('windows.main.column.NEW_TABLE') +@patch('windows.main.tabs.column.CURRENT_SESSION') +@patch('windows.main.tabs.column.CURRENT_TABLE') +@patch('windows.main.tabs.column.NEW_TABLE') def test_insert_column_index(mock_new_table, mock_current_table, mock_current_session, mock_get_app, sqlite_session, mock_table): mock_get_app.return_value = Mock() mock_current_session.get_value.return_value = sqlite_session diff --git a/tests/ui/test_connections.py b/tests/ui/test_connections.py index 58c6661..6ae6f5b 100644 --- a/tests/ui/test_connections.py +++ b/tests/ui/test_connections.py @@ -3,8 +3,8 @@ from structures.connection import Connection, ConnectionEngine from structures.configurations import CredentialsConfiguration, SourceConfiguration -from windows.connections.model import ConnectionModel -from windows.connections import CURRENT_CONNECTION, PENDING_CONNECTION +from windows.dialogs.connections.model import ConnectionModel +from windows.dialogs.connections import CURRENT_CONNECTION, PENDING_CONNECTION class TestConnectionModel: diff --git a/tests/ui/test_index_controller.py b/tests/ui/test_index_controller.py index 57463cf..d241349 100644 --- a/tests/ui/test_index_controller.py +++ b/tests/ui/test_index_controller.py @@ -2,7 +2,7 @@ from unittest.mock import Mock, patch, call from structures.engines.sqlite.database import SQLiteDatabase, SQLiteTable, SQLiteIndex -from windows.main.index import TableIndexController +from windows.main.tabs.index import TableIndexController @pytest.fixture @@ -24,9 +24,9 @@ def mock_table(sqlite_session): @patch('wx.GetApp') -@patch('windows.main.index.CURRENT_TABLE') -@patch('windows.main.index.CURRENT_INDEX') -@patch('windows.main.index.NEW_TABLE') +@patch('windows.main.tabs.index.CURRENT_TABLE') +@patch('windows.main.tabs.index.CURRENT_INDEX') +@patch('windows.main.tabs.index.NEW_TABLE') def test_on_index_delete(mock_new_table, mock_current_index, mock_current_table, mock_get_app, sqlite_session, mock_table): mock_get_app.return_value = Mock() mock_current_table.get_value.return_value = mock_table @@ -51,9 +51,9 @@ def test_on_index_delete(mock_new_table, mock_current_index, mock_current_table, @patch('wx.GetApp') -@patch('windows.main.index.CURRENT_TABLE') -@patch('windows.main.index.CURRENT_INDEX') -@patch('windows.main.index.NEW_TABLE') +@patch('windows.main.tabs.index.CURRENT_TABLE') +@patch('windows.main.tabs.index.CURRENT_INDEX') +@patch('windows.main.tabs.index.NEW_TABLE') def test_on_index_clear(mock_new_table, mock_current_index, mock_current_table, mock_get_app, sqlite_session, mock_table): mock_get_app.return_value = Mock() mock_current_table.get_value.return_value = mock_table diff --git a/tests/ui/test_repository.py b/tests/ui/test_repository.py index 8049e59..484de47 100644 --- a/tests/ui/test_repository.py +++ b/tests/ui/test_repository.py @@ -5,8 +5,8 @@ from structures.connection import Connection, ConnectionEngine from structures.configurations import CredentialsConfiguration, SourceConfiguration -from windows.connections import ConnectionDirectory -from windows.connections.repository import ConnectionsRepository +from windows.dialogs.connections import ConnectionDirectory +from windows.dialogs.connections.repository import ConnectionsRepository class TestConnectionsRepository: @@ -131,7 +131,7 @@ def test_delete_connection(self, repository): def test_add_directory(self, repository): """Test adding a directory.""" - directory = ConnectionDirectory(name="Production", children=[]) + directory = ConnectionDirectory(id=-1, name="Production", children=[]) repository.add_directory(directory) @@ -142,7 +142,7 @@ def test_add_directory(self, repository): def test_add_connection_to_directory(self, repository): """Test adding a connection inside a directory.""" - directory = ConnectionDirectory(name="Development", children=[]) + directory = ConnectionDirectory(id=-1, name="Development", children=[]) repository.add_directory(directory) connection = Connection( @@ -163,7 +163,7 @@ def test_add_connection_to_directory(self, repository): def test_delete_directory(self, repository): """Test deleting a directory.""" - directory = ConnectionDirectory(name="To Delete", children=[]) + directory = ConnectionDirectory(id=-1, name="To Delete", children=[]) repository.add_directory(directory) assert len(repository.connections.get_value()) == 1 diff --git a/themes/README.md b/themes/README.md new file mode 100644 index 0000000..0b4fad3 --- /dev/null +++ b/themes/README.md @@ -0,0 +1,84 @@ +# PeterSQL Themes + +This directory contains theme files for PeterSQL. Each theme defines colors for the editor and autocomplete components, with support for both dark and light modes. + +## Theme Structure + +Each theme is a YAML file with the following structure: + +```yaml +name: Theme Name +version: 1.0 + +editor: + dark: + # Colors for dark mode + background: auto # 'auto' uses system color + foreground: auto + keyword: '#569cd6' + string: '#ce9178' + # ... more colors + + light: + # Colors for light mode + background: auto + foreground: auto + keyword: '#0000ff' + # ... more colors + +autocomplete: + dark: + keyword: '#569cd6' + function: '#dcdcaa' + table: '#4ec9b0' + column: '#9cdcfe' + + light: + keyword: '#0000ff' + function: '#800080' + table: '#008000' + column: '#000000' +``` + +## Available Colors + +### Editor Colors +- `background` - Editor background +- `foreground` - Default text color +- `line_number_background` - Line number margin background +- `line_number_foreground` - Line number text color +- `keyword` - SQL keywords (SELECT, FROM, etc.) +- `string` - String literals +- `comment` - Comments +- `number` - Numeric literals +- `operator` - Operators (+, -, *, etc.) +- `property` - JSON properties +- `error` - Error highlighting +- `uri` - URI/URL highlighting +- `reference` - Reference highlighting +- `document` - Document markers + +### Autocomplete Colors +- `keyword` - SQL keywords +- `function` - SQL functions +- `table` - Table names +- `column` - Column names + +## Using 'auto' Color + +Set a color to `auto` to use the system color. This is useful for background and foreground colors to ensure the editor adapts to the system theme. + +## Creating a New Theme + +1. Create a new YAML file in this directory (e.g., `mytheme.yml`) +2. Copy the structure from `petersql.yml` +3. Customize the colors +4. Update `settings.yml` to use your theme: + ```yaml + theme: + current: mytheme + ``` + +## Default Theme + +The default theme is `petersql.yml`, which provides VS Code-like colors for both dark and light modes. diff --git a/uv.lock b/uv.lock index 21dbdff..d077a7b 100644 --- a/uv.lock +++ b/uv.lock @@ -13,11 +13,11 @@ wheels = [ [[package]] name = "certifi" -version = "2026.1.4" +version = "2026.2.25" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" }, + { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, ] [[package]] @@ -211,13 +211,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e3/26/57c6fb270950d476074c087527a558ccb6f4436657314bfb6cdf484114c4/docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0", size = 147774, upload-time = "2024-05-23T11:13:55.01Z" }, ] +[[package]] +name = "execnet" +version = "2.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/89/780e11f9588d9e7128a3f87788354c7946a9cbb1401ad38a48c4db9a4f07/execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd", size = 166622, upload-time = "2025-11-12T09:56:37.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec", size = 40708, upload-time = "2025-11-12T09:56:36.333Z" }, +] + [[package]] name = "filelock" -version = "3.20.3" +version = "3.24.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1d/65/ce7f1b70157833bf3cb851b556a37d4547ceafc158aa9b34b36782f23696/filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1", size = 19485, upload-time = "2026-01-09T17:55:05.421Z" } +sdist = { url = "https://files.pythonhosted.org/packages/73/92/a8e2479937ff39185d20dd6a851c1a63e55849e447a55e798cc2e1f49c65/filelock-3.24.3.tar.gz", hash = "sha256:011a5644dc937c22699943ebbfc46e969cdde3e171470a6e40b9533e5a72affa", size = 37935, upload-time = "2026-02-19T00:48:20.543Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/36/7fb70f04bf00bc646cd5bb45aa9eddb15e19437a28b8fb2b4a5249fac770/filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1", size = 16701, upload-time = "2026-01-09T17:55:04.334Z" }, + { url = "https://files.pythonhosted.org/packages/9c/0f/5d0c71a1aefeb08efff26272149e07ab922b64f46c63363756224bd6872e/filelock-3.24.3-py3-none-any.whl", hash = "sha256:426e9a4660391f7f8a810d71b0555bce9008b0a1cc342ab1f6947d37639e002d", size = 24331, upload-time = "2026-02-19T00:48:18.465Z" }, ] [[package]] @@ -249,32 +258,36 @@ wheels = [ [[package]] name = "librt" -version = "0.7.8" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/24/5f3646ff414285e0f7708fa4e946b9bf538345a41d1c375c439467721a5e/librt-0.7.8.tar.gz", hash = "sha256:1a4ede613941d9c3470b0368be851df6bb78ab218635512d0370b27a277a0862", size = 148323, upload-time = "2026-01-14T12:56:16.876Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/73/fa8814c6ce2d49c3827829cadaa1589b0bf4391660bd4510899393a23ebc/librt-0.7.8-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:be927c3c94c74b05128089a955fba86501c3b544d1d300282cc1b4bd370cb418", size = 57049, upload-time = "2026-01-14T12:55:35.056Z" }, - { url = "https://files.pythonhosted.org/packages/53/fe/f6c70956da23ea235fd2e3cc16f4f0b4ebdfd72252b02d1164dd58b4e6c3/librt-0.7.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7b0803e9008c62a7ef79058233db7ff6f37a9933b8f2573c05b07ddafa226611", size = 58689, upload-time = "2026-01-14T12:55:36.078Z" }, - { url = "https://files.pythonhosted.org/packages/1f/4d/7a2481444ac5fba63050d9abe823e6bc16896f575bfc9c1e5068d516cdce/librt-0.7.8-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:79feb4d00b2a4e0e05c9c56df707934f41fcb5fe53fd9efb7549068d0495b758", size = 166808, upload-time = "2026-01-14T12:55:37.595Z" }, - { url = "https://files.pythonhosted.org/packages/ac/3c/10901d9e18639f8953f57c8986796cfbf4c1c514844a41c9197cf87cb707/librt-0.7.8-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b9122094e3f24aa759c38f46bd8863433820654927370250f460ae75488b66ea", size = 175614, upload-time = "2026-01-14T12:55:38.756Z" }, - { url = "https://files.pythonhosted.org/packages/db/01/5cbdde0951a5090a80e5ba44e6357d375048123c572a23eecfb9326993a7/librt-0.7.8-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e03bea66af33c95ce3addf87a9bf1fcad8d33e757bc479957ddbc0e4f7207ac", size = 189955, upload-time = "2026-01-14T12:55:39.939Z" }, - { url = "https://files.pythonhosted.org/packages/6a/b4/e80528d2f4b7eaf1d437fcbd6fc6ba4cbeb3e2a0cb9ed5a79f47c7318706/librt-0.7.8-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f1ade7f31675db00b514b98f9ab9a7698c7282dad4be7492589109471852d398", size = 189370, upload-time = "2026-01-14T12:55:41.057Z" }, - { url = "https://files.pythonhosted.org/packages/c1/ab/938368f8ce31a9787ecd4becb1e795954782e4312095daf8fd22420227c8/librt-0.7.8-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a14229ac62adcf1b90a15992f1ab9c69ae8b99ffb23cb64a90878a6e8a2f5b81", size = 183224, upload-time = "2026-01-14T12:55:42.328Z" }, - { url = "https://files.pythonhosted.org/packages/3c/10/559c310e7a6e4014ac44867d359ef8238465fb499e7eb31b6bfe3e3f86f5/librt-0.7.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5bcaaf624fd24e6a0cb14beac37677f90793a96864c67c064a91458611446e83", size = 203541, upload-time = "2026-01-14T12:55:43.501Z" }, - { url = "https://files.pythonhosted.org/packages/f8/db/a0db7acdb6290c215f343835c6efda5b491bb05c3ddc675af558f50fdba3/librt-0.7.8-cp314-cp314-win32.whl", hash = "sha256:7aa7d5457b6c542ecaed79cec4ad98534373c9757383973e638ccced0f11f46d", size = 40657, upload-time = "2026-01-14T12:55:44.668Z" }, - { url = "https://files.pythonhosted.org/packages/72/e0/4f9bdc2a98a798511e81edcd6b54fe82767a715e05d1921115ac70717f6f/librt-0.7.8-cp314-cp314-win_amd64.whl", hash = "sha256:3d1322800771bee4a91f3b4bd4e49abc7d35e65166821086e5afd1e6c0d9be44", size = 46835, upload-time = "2026-01-14T12:55:45.655Z" }, - { url = "https://files.pythonhosted.org/packages/f9/3d/59c6402e3dec2719655a41ad027a7371f8e2334aa794ed11533ad5f34969/librt-0.7.8-cp314-cp314-win_arm64.whl", hash = "sha256:5363427bc6a8c3b1719f8f3845ea53553d301382928a86e8fab7984426949bce", size = 39885, upload-time = "2026-01-14T12:55:47.138Z" }, - { url = "https://files.pythonhosted.org/packages/4e/9c/2481d80950b83085fb14ba3c595db56330d21bbc7d88a19f20165f3538db/librt-0.7.8-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:ca916919793a77e4a98d4a1701e345d337ce53be4a16620f063191f7322ac80f", size = 59161, upload-time = "2026-01-14T12:55:48.45Z" }, - { url = "https://files.pythonhosted.org/packages/96/79/108df2cfc4e672336765d54e3ff887294c1cc36ea4335c73588875775527/librt-0.7.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:54feb7b4f2f6706bb82325e836a01be805770443e2400f706e824e91f6441dde", size = 61008, upload-time = "2026-01-14T12:55:49.527Z" }, - { url = "https://files.pythonhosted.org/packages/46/f2/30179898f9994a5637459d6e169b6abdc982012c0a4b2d4c26f50c06f911/librt-0.7.8-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:39a4c76fee41007070f872b648cc2f711f9abf9a13d0c7162478043377b52c8e", size = 187199, upload-time = "2026-01-14T12:55:50.587Z" }, - { url = "https://files.pythonhosted.org/packages/b4/da/f7563db55cebdc884f518ba3791ad033becc25ff68eb70902b1747dc0d70/librt-0.7.8-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac9c8a458245c7de80bc1b9765b177055efff5803f08e548dd4bb9ab9a8d789b", size = 198317, upload-time = "2026-01-14T12:55:51.991Z" }, - { url = "https://files.pythonhosted.org/packages/b3/6c/4289acf076ad371471fa86718c30ae353e690d3de6167f7db36f429272f1/librt-0.7.8-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95b67aa7eff150f075fda09d11f6bfb26edffd300f6ab1666759547581e8f666", size = 210334, upload-time = "2026-01-14T12:55:53.682Z" }, - { url = "https://files.pythonhosted.org/packages/4a/7f/377521ac25b78ac0a5ff44127a0360ee6d5ddd3ce7327949876a30533daa/librt-0.7.8-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:535929b6eff670c593c34ff435d5440c3096f20fa72d63444608a5aef64dd581", size = 211031, upload-time = "2026-01-14T12:55:54.827Z" }, - { url = "https://files.pythonhosted.org/packages/c5/b1/e1e96c3e20b23d00cf90f4aad48f0deb4cdfec2f0ed8380d0d85acf98bbf/librt-0.7.8-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:63937bd0f4d1cb56653dc7ae900d6c52c41f0015e25aaf9902481ee79943b33a", size = 204581, upload-time = "2026-01-14T12:55:56.811Z" }, - { url = "https://files.pythonhosted.org/packages/43/71/0f5d010e92ed9747e14bef35e91b6580533510f1e36a8a09eb79ee70b2f0/librt-0.7.8-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cf243da9e42d914036fd362ac3fa77d80a41cadcd11ad789b1b5eec4daaf67ca", size = 224731, upload-time = "2026-01-14T12:55:58.175Z" }, - { url = "https://files.pythonhosted.org/packages/22/f0/07fb6ab5c39a4ca9af3e37554f9d42f25c464829254d72e4ebbd81da351c/librt-0.7.8-cp314-cp314t-win32.whl", hash = "sha256:171ca3a0a06c643bd0a2f62a8944e1902c94aa8e5da4db1ea9a8daf872685365", size = 41173, upload-time = "2026-01-14T12:55:59.315Z" }, - { url = "https://files.pythonhosted.org/packages/24/d4/7e4be20993dc6a782639625bd2f97f3c66125c7aa80c82426956811cfccf/librt-0.7.8-cp314-cp314t-win_amd64.whl", hash = "sha256:445b7304145e24c60288a2f172b5ce2ca35c0f81605f5299f3fa567e189d2e32", size = 47668, upload-time = "2026-01-14T12:56:00.261Z" }, - { url = "https://files.pythonhosted.org/packages/fc/85/69f92b2a7b3c0f88ffe107c86b952b397004b5b8ea5a81da3d9c04c04422/librt-0.7.8-cp314-cp314t-win_arm64.whl", hash = "sha256:8766ece9de08527deabcd7cb1b4f1a967a385d26e33e536d6d8913db6ef74f06", size = 40550, upload-time = "2026-01-14T12:56:01.542Z" }, +version = "0.8.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/56/9c/b4b0c54d84da4a94b37bd44151e46d5e583c9534c7e02250b961b1b6d8a8/librt-0.8.1.tar.gz", hash = "sha256:be46a14693955b3bd96014ccbdb8339ee8c9346fbe11c1b78901b55125f14c73", size = 177471, upload-time = "2026-02-17T16:13:06.101Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/6a/907ef6800f7bca71b525a05f1839b21f708c09043b1c6aa77b6b827b3996/librt-0.8.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6cfa7fe54fd4d1f47130017351a959fe5804bda7a0bc7e07a2cdbc3fdd28d34f", size = 66081, upload-time = "2026-02-17T16:12:12.766Z" }, + { url = "https://files.pythonhosted.org/packages/1b/18/25e991cd5640c9fb0f8d91b18797b29066b792f17bf8493da183bf5caabe/librt-0.8.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:228c2409c079f8c11fb2e5d7b277077f694cb93443eb760e00b3b83cb8b3176c", size = 68309, upload-time = "2026-02-17T16:12:13.756Z" }, + { url = "https://files.pythonhosted.org/packages/a4/36/46820d03f058cfb5a9de5940640ba03165ed8aded69e0733c417bb04df34/librt-0.8.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7aae78ab5e3206181780e56912d1b9bb9f90a7249ce12f0e8bf531d0462dd0fc", size = 196804, upload-time = "2026-02-17T16:12:14.818Z" }, + { url = "https://files.pythonhosted.org/packages/59/18/5dd0d3b87b8ff9c061849fbdb347758d1f724b9a82241aa908e0ec54ccd0/librt-0.8.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:172d57ec04346b047ca6af181e1ea4858086c80bdf455f61994c4aa6fc3f866c", size = 206907, upload-time = "2026-02-17T16:12:16.513Z" }, + { url = "https://files.pythonhosted.org/packages/d1/96/ef04902aad1424fd7299b62d1890e803e6ab4018c3044dca5922319c4b97/librt-0.8.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b1977c4ea97ce5eb7755a78fae68d87e4102e4aaf54985e8b56806849cc06a3", size = 221217, upload-time = "2026-02-17T16:12:17.906Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ff/7e01f2dda84a8f5d280637a2e5827210a8acca9a567a54507ef1c75b342d/librt-0.8.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:10c42e1f6fd06733ef65ae7bebce2872bcafd8d6e6b0a08fe0a05a23b044fb14", size = 214622, upload-time = "2026-02-17T16:12:19.108Z" }, + { url = "https://files.pythonhosted.org/packages/1e/8c/5b093d08a13946034fed57619742f790faf77058558b14ca36a6e331161e/librt-0.8.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4c8dfa264b9193c4ee19113c985c95f876fae5e51f731494fc4e0cf594990ba7", size = 221987, upload-time = "2026-02-17T16:12:20.331Z" }, + { url = "https://files.pythonhosted.org/packages/d3/cc/86b0b3b151d40920ad45a94ce0171dec1aebba8a9d72bb3fa00c73ab25dd/librt-0.8.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:01170b6729a438f0dedc4a26ed342e3dc4f02d1000b4b19f980e1877f0c297e6", size = 215132, upload-time = "2026-02-17T16:12:21.54Z" }, + { url = "https://files.pythonhosted.org/packages/fc/be/8588164a46edf1e69858d952654e216a9a91174688eeefb9efbb38a9c799/librt-0.8.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:7b02679a0d783bdae30d443025b94465d8c3dc512f32f5b5031f93f57ac32071", size = 215195, upload-time = "2026-02-17T16:12:23.073Z" }, + { url = "https://files.pythonhosted.org/packages/f5/f2/0b9279bea735c734d69344ecfe056c1ba211694a72df10f568745c899c76/librt-0.8.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:190b109bb69592a3401fe1ffdea41a2e73370ace2ffdc4a0e8e2b39cdea81b78", size = 237946, upload-time = "2026-02-17T16:12:24.275Z" }, + { url = "https://files.pythonhosted.org/packages/e9/cc/5f2a34fbc8aeb35314a3641f9956fa9051a947424652fad9882be7a97949/librt-0.8.1-cp314-cp314-win32.whl", hash = "sha256:e70a57ecf89a0f64c24e37f38d3fe217a58169d2fe6ed6d70554964042474023", size = 50689, upload-time = "2026-02-17T16:12:25.766Z" }, + { url = "https://files.pythonhosted.org/packages/a0/76/cd4d010ab2147339ca2b93e959c3686e964edc6de66ddacc935c325883d7/librt-0.8.1-cp314-cp314-win_amd64.whl", hash = "sha256:7e2f3edca35664499fbb36e4770650c4bd4a08abc1f4458eab9df4ec56389730", size = 57875, upload-time = "2026-02-17T16:12:27.465Z" }, + { url = "https://files.pythonhosted.org/packages/84/0f/2143cb3c3ca48bd3379dcd11817163ca50781927c4537345d608b5045998/librt-0.8.1-cp314-cp314-win_arm64.whl", hash = "sha256:0d2f82168e55ddefd27c01c654ce52379c0750ddc31ee86b4b266bcf4d65f2a3", size = 48058, upload-time = "2026-02-17T16:12:28.556Z" }, + { url = "https://files.pythonhosted.org/packages/d2/0e/9b23a87e37baf00311c3efe6b48d6b6c168c29902dfc3f04c338372fd7db/librt-0.8.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2c74a2da57a094bd48d03fa5d196da83d2815678385d2978657499063709abe1", size = 68313, upload-time = "2026-02-17T16:12:29.659Z" }, + { url = "https://files.pythonhosted.org/packages/db/9a/859c41e5a4f1c84200a7d2b92f586aa27133c8243b6cac9926f6e54d01b9/librt-0.8.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a355d99c4c0d8e5b770313b8b247411ed40949ca44e33e46a4789b9293a907ee", size = 70994, upload-time = "2026-02-17T16:12:31.516Z" }, + { url = "https://files.pythonhosted.org/packages/4c/28/10605366ee599ed34223ac2bf66404c6fb59399f47108215d16d5ad751a8/librt-0.8.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2eb345e8b33fb748227409c9f1233d4df354d6e54091f0e8fc53acdb2ffedeb7", size = 220770, upload-time = "2026-02-17T16:12:33.294Z" }, + { url = "https://files.pythonhosted.org/packages/af/8d/16ed8fd452dafae9c48d17a6bc1ee3e818fd40ef718d149a8eff2c9f4ea2/librt-0.8.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9be2f15e53ce4e83cc08adc29b26fb5978db62ef2a366fbdf716c8a6c8901040", size = 235409, upload-time = "2026-02-17T16:12:35.443Z" }, + { url = "https://files.pythonhosted.org/packages/89/1b/7bdf3e49349c134b25db816e4a3db6b94a47ac69d7d46b1e682c2c4949be/librt-0.8.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:785ae29c1f5c6e7c2cde2c7c0e148147f4503da3abc5d44d482068da5322fd9e", size = 246473, upload-time = "2026-02-17T16:12:36.656Z" }, + { url = "https://files.pythonhosted.org/packages/4e/8a/91fab8e4fd2a24930a17188c7af5380eb27b203d72101c9cc000dbdfd95a/librt-0.8.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1d3a7da44baf692f0c6aeb5b2a09c5e6fc7a703bca9ffa337ddd2e2da53f7732", size = 238866, upload-time = "2026-02-17T16:12:37.849Z" }, + { url = "https://files.pythonhosted.org/packages/b9/e0/c45a098843fc7c07e18a7f8a24ca8496aecbf7bdcd54980c6ca1aaa79a8e/librt-0.8.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5fc48998000cbc39ec0d5311312dda93ecf92b39aaf184c5e817d5d440b29624", size = 250248, upload-time = "2026-02-17T16:12:39.445Z" }, + { url = "https://files.pythonhosted.org/packages/82/30/07627de23036640c952cce0c1fe78972e77d7d2f8fd54fa5ef4554ff4a56/librt-0.8.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e96baa6820280077a78244b2e06e416480ed859bbd8e5d641cf5742919d8beb4", size = 240629, upload-time = "2026-02-17T16:12:40.889Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c1/55bfe1ee3542eba055616f9098eaf6eddb966efb0ca0f44eaa4aba327307/librt-0.8.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:31362dbfe297b23590530007062c32c6f6176f6099646bb2c95ab1b00a57c382", size = 239615, upload-time = "2026-02-17T16:12:42.446Z" }, + { url = "https://files.pythonhosted.org/packages/2b/39/191d3d28abc26c9099b19852e6c99f7f6d400b82fa5a4e80291bd3803e19/librt-0.8.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cc3656283d11540ab0ea01978378e73e10002145117055e03722417aeab30994", size = 263001, upload-time = "2026-02-17T16:12:43.627Z" }, + { url = "https://files.pythonhosted.org/packages/b9/eb/7697f60fbe7042ab4e88f4ee6af496b7f222fffb0a4e3593ef1f29f81652/librt-0.8.1-cp314-cp314t-win32.whl", hash = "sha256:738f08021b3142c2918c03692608baed43bc51144c29e35807682f8070ee2a3a", size = 51328, upload-time = "2026-02-17T16:12:45.148Z" }, + { url = "https://files.pythonhosted.org/packages/7c/72/34bf2eb7a15414a23e5e70ecb9440c1d3179f393d9349338a91e2781c0fb/librt-0.8.1-cp314-cp314t-win_amd64.whl", hash = "sha256:89815a22daf9c51884fb5dbe4f1ef65ee6a146e0b6a8df05f753e2e4a9359bf4", size = 58722, upload-time = "2026-02-17T16:12:46.85Z" }, + { url = "https://files.pythonhosted.org/packages/b2/c8/d148e041732d631fc76036f8b30fae4e77b027a1e95b7a84bb522481a940/librt-0.8.1-cp314-cp314t-win_arm64.whl", hash = "sha256:bf512a71a23504ed08103a13c941f763db13fb11177beb3d9244c98c29fb4a61", size = 48755, upload-time = "2026-02-17T16:12:47.943Z" }, ] [[package]] @@ -362,7 +375,6 @@ dependencies = [ { name = "psycopg2-binary" }, { name = "pymysql" }, { name = "pyyaml" }, - { name = "requests" }, { name = "sqlglot" }, { name = "wxpython" }, ] @@ -374,6 +386,7 @@ dev = [ { name = "pytest" }, { name = "pytest-cov" }, { name = "pytest-mock" }, + { name = "pytest-xdist" }, { name = "testcontainers" }, { name = "types-pymysql" }, { name = "types-pyyaml" }, @@ -382,34 +395,34 @@ dev = [ [package.metadata] requires-dist = [ - { name = "babel" }, - { name = "mypy", marker = "extra == 'dev'" }, - { name = "oracledb" }, - { name = "pre-commit", marker = "extra == 'dev'" }, - { name = "psutil" }, - { name = "psycopg2-binary" }, - { name = "pymysql" }, - { name = "pytest", marker = "extra == 'dev'" }, - { name = "pytest-cov", marker = "extra == 'dev'" }, - { name = "pytest-mock", marker = "extra == 'dev'" }, - { name = "pyyaml" }, - { name = "requests" }, - { name = "sqlglot" }, - { name = "testcontainers", marker = "extra == 'dev'" }, - { name = "types-pymysql", marker = "extra == 'dev'" }, - { name = "types-pyyaml", marker = "extra == 'dev'" }, - { name = "types-wxpython", marker = "extra == 'dev'" }, - { name = "wxpython" }, + { name = "babel", specifier = ">=2.18.0" }, + { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.19.1" }, + { name = "oracledb", specifier = ">=3.4.2" }, + { name = "pre-commit", marker = "extra == 'dev'", specifier = ">=4.5.1" }, + { name = "psutil", specifier = ">=7.2.2" }, + { name = "psycopg2-binary", specifier = ">=2.9.11" }, + { name = "pymysql", specifier = ">=1.1.2" }, + { name = "pytest", marker = "extra == 'dev'", specifier = ">=9.0.2" }, + { name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=7.0.0" }, + { name = "pytest-mock", marker = "extra == 'dev'", specifier = ">=3.15.1" }, + { name = "pytest-xdist", marker = "extra == 'dev'", specifier = ">=3.8.0" }, + { name = "pyyaml", specifier = ">=6.0.3" }, + { name = "sqlglot", specifier = ">=29.0.1" }, + { name = "testcontainers", marker = "extra == 'dev'", specifier = ">=4.14.1" }, + { name = "types-pymysql", marker = "extra == 'dev'", specifier = ">=1.1.0.20251220" }, + { name = "types-pyyaml", marker = "extra == 'dev'", specifier = ">=6.0.12.20250915" }, + { name = "types-wxpython", marker = "extra == 'dev'", specifier = ">=0.9.7" }, + { name = "wxpython", specifier = ">=4.2.5" }, ] provides-extras = ["dev"] [[package]] name = "platformdirs" -version = "4.5.1" +version = "4.9.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715, upload-time = "2025-12-05T13:52:58.638Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/04/fea538adf7dbbd6d186f551d595961e564a3b6715bdf276b477460858672/platformdirs-4.9.2.tar.gz", hash = "sha256:9a33809944b9db043ad67ca0db94b14bf452cc6aeaac46a88ea55b26e2e9d291", size = 28394, upload-time = "2026-02-16T03:56:10.574Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" }, + { url = "https://files.pythonhosted.org/packages/48/31/05e764397056194206169869b50cf2fee4dbbbc71b344705b9c0d878d4d8/platformdirs-4.9.2-py3-none-any.whl", hash = "sha256:9170634f126f8efdae22fb58ae8a0eaa86f38365bc57897a6c4f781d1f5875bd", size = 21168, upload-time = "2026-02-16T03:56:08.891Z" }, ] [[package]] @@ -547,6 +560,32 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5a/cc/06253936f4a7fa2e0f48dfe6d851d9c56df896a9ab09ac019d70b760619c/pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d", size = 10095, upload-time = "2025-09-16T16:37:25.734Z" }, ] +[[package]] +name = "pytest-xdist" +version = "3.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "execnet" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/b4/439b179d1ff526791eb921115fca8e44e596a13efeda518b9d845a619450/pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1", size = 88069, upload-time = "2025-07-01T13:30:59.346Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88", size = 46396, upload-time = "2025-07-01T13:30:56.632Z" }, +] + +[[package]] +name = "python-discovery" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/bb/93a3e83bdf9322c7e21cafd092e56a4a17c4d8ef4277b6eb01af1a540a6f/python_discovery-1.1.0.tar.gz", hash = "sha256:447941ba1aed8cc2ab7ee3cb91be5fc137c5bdbb05b7e6ea62fbdcb66e50b268", size = 55674, upload-time = "2026-02-26T09:42:49.668Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/54/82a6e2ef37f0f23dccac604b9585bdcbd0698604feb64807dcb72853693e/python_discovery-1.1.0-py3-none-any.whl", hash = "sha256:a162893b8809727f54594a99ad2179d2ede4bf953e12d4c7abc3cc9cdbd1437b", size = 30687, upload-time = "2026-02-26T09:42:48.548Z" }, +] + [[package]] name = "python-dotenv" version = "1.2.1" @@ -609,11 +648,11 @@ wheels = [ [[package]] name = "sqlglot" -version = "28.10.1" +version = "29.0.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1c/66/b2b300f325227044aa6f511ea7c9f3109a1dc74b13a0897931c1754b504e/sqlglot-28.10.1.tar.gz", hash = "sha256:66e0dae43b4bce23314b80e9aef41b8c88fea0e17ada62de095b45262084a8c5", size = 5739510, upload-time = "2026-02-09T23:36:23.671Z" } +sdist = { url = "https://files.pythonhosted.org/packages/61/12/c3f7533fde302fcd59bebcd4c2e46d5bf0eef21f183c67995bbb010fb578/sqlglot-29.0.1.tar.gz", hash = "sha256:0010b4f77fb996c8d25dd4b16f3654e6da163ff1866ceabc70b24e791c203048", size = 5760786, upload-time = "2026-02-23T21:41:20.178Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/55/ff/5a768b34202e1ee485737bfa167bd84592585aa40383f883a8e346d767cc/sqlglot-28.10.1-py3-none-any.whl", hash = "sha256:214aef51fd4ce16407022f81cfc80c173409dab6d0f6ae18c52b43f43b31d4dd", size = 597053, upload-time = "2026-02-09T23:36:21.385Z" }, + { url = "https://files.pythonhosted.org/packages/ad/c9/f58c3a17beb650700f9d2eccd410726b6d96df8953663700764ca48636c7/sqlglot-29.0.1-py3-none-any.whl", hash = "sha256:06a473ea6c2b3632ac67bd38e687a6860265bf4156e66b54adeda15d07f00c65", size = 611448, upload-time = "2026-02-23T21:41:18.008Z" }, ] [[package]] @@ -679,16 +718,17 @@ wheels = [ [[package]] name = "virtualenv" -version = "20.36.1" +version = "21.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, { name = "filelock" }, { name = "platformdirs" }, + { name = "python-discovery" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/aa/a3/4d310fa5f00863544e1d0f4de93bddec248499ccf97d4791bc3122c9d4f3/virtualenv-20.36.1.tar.gz", hash = "sha256:8befb5c81842c641f8ee658481e42641c68b5eab3521d8e092d18320902466ba", size = 6032239, upload-time = "2026-01-09T18:21:01.296Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2f/c9/18d4b36606d6091844daa3bd93cf7dc78e6f5da21d9f21d06c221104b684/virtualenv-21.1.0.tar.gz", hash = "sha256:1990a0188c8f16b6b9cf65c9183049007375b26aad415514d377ccacf1e4fb44", size = 5840471, upload-time = "2026-02-27T08:49:29.702Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/2a/dc2228b2888f51192c7dc766106cd475f1b768c10caaf9727659726f7391/virtualenv-20.36.1-py3-none-any.whl", hash = "sha256:575a8d6b124ef88f6f51d56d656132389f961062a9177016a50e4f507bbcc19f", size = 6008258, upload-time = "2026-01-09T18:20:59.425Z" }, + { url = "https://files.pythonhosted.org/packages/78/55/896b06bf93a49bec0f4ae2a6f1ed12bd05c8860744ac3a70eda041064e4d/virtualenv-21.1.0-py3-none-any.whl", hash = "sha256:164f5e14c5587d170cf98e60378eb91ea35bf037be313811905d3a24ea33cc07", size = 5825072, upload-time = "2026-02-27T08:49:27.516Z" }, ] [[package]] diff --git a/windows/__init__.py b/windows/__init__.py old mode 100755 new mode 100644 index fed60d3..b3cb67a --- a/windows/__init__.py +++ b/windows/__init__.py @@ -1,2529 +1,7 @@ -# -*- coding: utf-8 -*- - -########################################################################### -## Python code generated with wxFormBuilder (version 4.2.1-111-g5faebfea) -## http://www.wxformbuilder.org/ -## -## PLEASE DO *NOT* EDIT THIS FILE! -########################################################################### - -from .components.dataview import TableIndexesDataViewCtrl -from .components.dataview import TableForeignKeysDataViewCtrl -from .components.dataview import TableCheckDataViewCtrl -from .components.dataview import TableColumnsDataViewCtrl -from .components.dataview import TableRecordsDataViewCtrl -import wx -import wx.xrc -import wx.dataview -import wx.stc -import wx.lib.agw.hypertreelist - -import gettext -_ = gettext.gettext - -########################################################################### -## Class ConnectionsDialog -########################################################################### - -class ConnectionsDialog ( wx.Dialog ): - - def __init__( self, parent ): - wx.Dialog.__init__ ( self, parent, id = wx.ID_ANY, title = _(u"Connection"), pos = wx.DefaultPosition, size = wx.Size( 800,600 ), style = wx.DEFAULT_DIALOG_STYLE|wx.DIALOG_NO_PARENT|wx.RESIZE_BORDER ) - - self.SetSizeHints( wx.Size( -1,-1 ), wx.DefaultSize ) - - bSizer34 = wx.BoxSizer( wx.VERTICAL ) - - self.m_splitter3 = wx.SplitterWindow( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.SP_LIVE_UPDATE ) - self.m_splitter3.Bind( wx.EVT_IDLE, self.m_splitter3OnIdle ) - self.m_splitter3.SetMinimumPaneSize( 250 ) - - self.m_panel16 = wx.Panel( self.m_splitter3, wx.ID_ANY, wx.DefaultPosition, wx.Size( -1,-1 ), wx.TAB_TRAVERSAL ) - bSizer35 = wx.BoxSizer( wx.VERTICAL ) - - self.connections_tree_ctrl = wx.dataview.DataViewCtrl( self.m_panel16, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.dataview.DV_ROW_LINES ) - self.connection_name = self.connections_tree_ctrl.AppendIconTextColumn( _(u"Name"), 0, wx.dataview.DATAVIEW_CELL_EDITABLE, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) - self.connection_last_connection = self.connections_tree_ctrl.AppendTextColumn( _(u"Last connection"), 1, wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) - bSizer35.Add( self.connections_tree_ctrl, 1, wx.ALL|wx.EXPAND|wx.TOP, 5 ) - - self.search_connection = wx.SearchCtrl( self.m_panel16, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) - self.search_connection.ShowSearchButton( True ) - self.search_connection.ShowCancelButton( True ) - bSizer35.Add( self.search_connection, 0, wx.BOTTOM|wx.EXPAND|wx.LEFT|wx.RIGHT, 5 ) - - - self.m_panel16.SetSizer( bSizer35 ) - self.m_panel16.Layout() - bSizer35.Fit( self.m_panel16 ) - self.m_menu5 = wx.Menu() - self.m_menuItem4 = wx.MenuItem( self.m_menu5, wx.ID_ANY, _(u"New directory"), wx.EmptyString, wx.ITEM_NORMAL ) - self.m_menu5.Append( self.m_menuItem4 ) - - self.m_menuItem5 = wx.MenuItem( self.m_menu5, wx.ID_ANY, _(u"New Session"), wx.EmptyString, wx.ITEM_NORMAL ) - self.m_menu5.Append( self.m_menuItem5 ) - - self.m_menu5.AppendSeparator() - - self.m_menuItem10 = wx.MenuItem( self.m_menu5, wx.ID_ANY, _(u"Import"), wx.EmptyString, wx.ITEM_NORMAL ) - self.m_menu5.Append( self.m_menuItem10 ) - - self.m_panel16.Bind( wx.EVT_RIGHT_DOWN, self.m_panel16OnContextMenu ) - - self.m_panel17 = wx.Panel( self.m_splitter3, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) - bSizer36 = wx.BoxSizer( wx.VERTICAL ) - - self.m_notebook4 = wx.Notebook( self.m_panel17, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.NB_FIXEDWIDTH ) - self.panel_connection = wx.Panel( self.m_notebook4, wx.ID_ANY, wx.DefaultPosition, wx.Size( 600,-1 ), wx.BORDER_NONE|wx.TAB_TRAVERSAL ) - self.panel_connection.SetMinSize( wx.Size( 600,-1 ) ) - - bSizer12 = wx.BoxSizer( wx.VERTICAL ) - - bSizer1211 = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticText211 = wx.StaticText( self.panel_connection, wx.ID_ANY, _(u"Name"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) - self.m_staticText211.Wrap( -1 ) - - bSizer1211.Add( self.m_staticText211, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) - - self.name = wx.TextCtrl( self.panel_connection, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) - bSizer1211.Add( self.name, 1, wx.ALIGN_CENTER|wx.ALL, 5 ) - - - bSizer12.Add( bSizer1211, 0, wx.EXPAND, 5 ) - - bSizer13 = wx.BoxSizer( wx.HORIZONTAL ) - - bSizer13.SetMinSize( wx.Size( -1,0 ) ) - self.m_staticText2 = wx.StaticText( self.panel_connection, wx.ID_ANY, _(u"Engine"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) - self.m_staticText2.Wrap( -1 ) - - bSizer13.Add( self.m_staticText2, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) - - engineChoices = [] - self.engine = wx.Choice( self.panel_connection, wx.ID_ANY, wx.DefaultPosition, wx.Size( 400,-1 ), engineChoices, 0 ) - self.engine.SetSelection( 0 ) - bSizer13.Add( self.engine, 1, wx.ALIGN_CENTER|wx.ALL, 5 ) - - - bSizer12.Add( bSizer13, 0, wx.EXPAND, 5 ) - - self.m_staticline41 = wx.StaticLine( self.panel_connection, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL ) - bSizer12.Add( self.m_staticline41, 0, wx.EXPAND | wx.ALL, 5 ) - - self.panel_credentials = wx.Panel( self.panel_connection, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) - bSizer103 = wx.BoxSizer( wx.VERTICAL ) - - bSizer121 = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticText21 = wx.StaticText( self.panel_credentials, wx.ID_ANY, _(u"Host + port"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) - self.m_staticText21.Wrap( -1 ) - - bSizer121.Add( self.m_staticText21, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) - - self.hostname = wx.TextCtrl( self.panel_credentials, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) - bSizer121.Add( self.hostname, 1, wx.ALIGN_CENTER|wx.ALL, 5 ) - - self.port = wx.SpinCtrl( self.panel_credentials, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.SP_ARROW_KEYS, 0, 65536, 3306 ) - bSizer121.Add( self.port, 0, wx.ALL, 5 ) - - - bSizer103.Add( bSizer121, 0, wx.EXPAND, 5 ) - - bSizer122 = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticText22 = wx.StaticText( self.panel_credentials, wx.ID_ANY, _(u"Username"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) - self.m_staticText22.Wrap( -1 ) - - bSizer122.Add( self.m_staticText22, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) - - self.username = wx.TextCtrl( self.panel_credentials, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) - bSizer122.Add( self.username, 1, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) - - - bSizer103.Add( bSizer122, 0, wx.EXPAND, 5 ) - - bSizer1221 = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticText221 = wx.StaticText( self.panel_credentials, wx.ID_ANY, _(u"Password"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) - self.m_staticText221.Wrap( -1 ) - - bSizer1221.Add( self.m_staticText221, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) - - self.password = wx.TextCtrl( self.panel_credentials, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_PASSWORD ) - bSizer1221.Add( self.password, 1, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) - - - bSizer103.Add( bSizer1221, 0, wx.EXPAND, 5 ) - - bSizer116 = wx.BoxSizer( wx.HORIZONTAL ) - - - bSizer116.Add( ( 156, 0), 0, wx.EXPAND, 5 ) - - self.ssh_tunnel_enabled = wx.CheckBox( self.panel_credentials, wx.ID_ANY, _(u"Use SSH tunnel"), wx.DefaultPosition, wx.DefaultSize, 0 ) - bSizer116.Add( self.ssh_tunnel_enabled, 0, wx.ALL, 5 ) - - - bSizer103.Add( bSizer116, 0, wx.EXPAND, 5 ) - - self.m_staticline5 = wx.StaticLine( self.panel_credentials, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL ) - bSizer103.Add( self.m_staticline5, 0, wx.EXPAND | wx.ALL, 5 ) - - - self.panel_credentials.SetSizer( bSizer103 ) - self.panel_credentials.Layout() - bSizer103.Fit( self.panel_credentials ) - bSizer12.Add( self.panel_credentials, 0, wx.EXPAND | wx.ALL, 0 ) - - self.panel_source = wx.Panel( self.panel_connection, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) - self.panel_source.Hide() - - bSizer105 = wx.BoxSizer( wx.VERTICAL ) - - bSizer106 = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticText50 = wx.StaticText( self.panel_source, wx.ID_ANY, _(u"Filename"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) - self.m_staticText50.Wrap( -1 ) - - bSizer106.Add( self.m_staticText50, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) - - self.filename = wx.FilePickerCtrl( self.panel_source, wx.ID_ANY, wx.EmptyString, _(u"Select a file"), _(u"*.*"), wx.DefaultPosition, wx.DefaultSize, wx.FLP_CHANGE_DIR|wx.FLP_DEFAULT_STYLE|wx.FLP_FILE_MUST_EXIST ) - bSizer106.Add( self.filename, 1, wx.ALIGN_CENTER|wx.ALL, 5 ) - - - bSizer105.Add( bSizer106, 1, wx.EXPAND, 5 ) - - - self.panel_source.SetSizer( bSizer105 ) - self.panel_source.Layout() - bSizer105.Fit( self.panel_source ) - bSizer12.Add( self.panel_source, 0, wx.EXPAND | wx.ALL, 0 ) - - bSizer122111 = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticText22111 = wx.StaticText( self.panel_connection, wx.ID_ANY, _(u"Comments"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) - self.m_staticText22111.Wrap( -1 ) - - bSizer122111.Add( self.m_staticText22111, 0, wx.ALL, 5 ) - - self.comments = wx.TextCtrl( self.panel_connection, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.Size( -1,200 ), wx.TE_MULTILINE ) - bSizer122111.Add( self.comments, 1, wx.ALL|wx.EXPAND, 5 ) - - - bSizer12.Add( bSizer122111, 0, wx.EXPAND, 5 ) - - - self.panel_connection.SetSizer( bSizer12 ) - self.panel_connection.Layout() - self.m_notebook4.AddPage( self.panel_connection, _(u"Settings"), True ) - self.panel_ssh_tunnel = wx.Panel( self.m_notebook4, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) - self.panel_ssh_tunnel.Enable( False ) - self.panel_ssh_tunnel.Hide() - - bSizer102 = wx.BoxSizer( wx.VERTICAL ) - - bSizer1213 = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticText213 = wx.StaticText( self.panel_ssh_tunnel, wx.ID_ANY, _(u"SSH executable"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) - self.m_staticText213.Wrap( -1 ) - - bSizer1213.Add( self.m_staticText213, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) - - self.ssh_tunnel_executable = wx.TextCtrl( self.panel_ssh_tunnel, wx.ID_ANY, _(u"ssh"), wx.DefaultPosition, wx.DefaultSize, 0 ) - bSizer1213.Add( self.ssh_tunnel_executable, 1, wx.ALIGN_CENTER|wx.ALL, 5 ) - - - bSizer102.Add( bSizer1213, 0, wx.EXPAND, 5 ) - - bSizer12131 = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticText2131 = wx.StaticText( self.panel_ssh_tunnel, wx.ID_ANY, _(u"SSH host + port"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) - self.m_staticText2131.Wrap( -1 ) - - bSizer12131.Add( self.m_staticText2131, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) - - self.ssh_tunnel_hostname = wx.TextCtrl( self.panel_ssh_tunnel, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) - bSizer12131.Add( self.ssh_tunnel_hostname, 1, wx.ALIGN_CENTER|wx.ALL, 5 ) - - self.ssh_tunnel_port = wx.SpinCtrl( self.panel_ssh_tunnel, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.SP_ARROW_KEYS, 0, 65536, 22 ) - bSizer12131.Add( self.ssh_tunnel_port, 0, wx.ALL, 5 ) - - - bSizer102.Add( bSizer12131, 0, wx.EXPAND, 5 ) - - bSizer12132 = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticText2132 = wx.StaticText( self.panel_ssh_tunnel, wx.ID_ANY, _(u"SSH username"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) - self.m_staticText2132.Wrap( -1 ) - - bSizer12132.Add( self.m_staticText2132, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) - - self.ssh_tunnel_username = wx.TextCtrl( self.panel_ssh_tunnel, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) - bSizer12132.Add( self.ssh_tunnel_username, 1, wx.ALIGN_CENTER|wx.ALL, 5 ) - - - bSizer102.Add( bSizer12132, 0, wx.EXPAND, 5 ) - - bSizer121321 = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticText21321 = wx.StaticText( self.panel_ssh_tunnel, wx.ID_ANY, _(u"SSH password"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) - self.m_staticText21321.Wrap( -1 ) - - bSizer121321.Add( self.m_staticText21321, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) - - self.ssh_tunnel_password = wx.TextCtrl( self.panel_ssh_tunnel, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_PASSWORD ) - bSizer121321.Add( self.ssh_tunnel_password, 1, wx.ALIGN_CENTER|wx.ALL, 5 ) - - - bSizer102.Add( bSizer121321, 0, wx.EXPAND, 5 ) - - bSizer1213211 = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticText213211 = wx.StaticText( self.panel_ssh_tunnel, wx.ID_ANY, _(u"Local port"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) - self.m_staticText213211.Wrap( -1 ) - - bSizer1213211.Add( self.m_staticText213211, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) - - self.ssh_tunnel_local_port = wx.SpinCtrl( self.panel_ssh_tunnel, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.SP_ARROW_KEYS, 0, 65536, 0 ) - self.ssh_tunnel_local_port.SetToolTip( _(u"if the value is set to 0, the first available port will be used") ) - - bSizer1213211.Add( self.ssh_tunnel_local_port, 1, wx.ALL, 5 ) - - - bSizer102.Add( bSizer1213211, 0, wx.EXPAND, 5 ) - - - self.panel_ssh_tunnel.SetSizer( bSizer102 ) - self.panel_ssh_tunnel.Layout() - bSizer102.Fit( self.panel_ssh_tunnel ) - self.m_notebook4.AddPage( self.panel_ssh_tunnel, _(u"SSH Tunnel"), False ) - self.panel_statistics = wx.Panel( self.m_notebook4, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) - bSizer361 = wx.BoxSizer( wx.VERTICAL ) - - bSizer37 = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticText15 = wx.StaticText( self.panel_statistics, wx.ID_ANY, _(u"Created at"), wx.DefaultPosition, wx.DefaultSize, 0 ) - self.m_staticText15.Wrap( -1 ) - - self.m_staticText15.SetMinSize( wx.Size( 200,-1 ) ) - - bSizer37.Add( self.m_staticText15, 0, wx.ALL, 5 ) - - self.created_at = wx.StaticText( self.panel_statistics, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) - self.created_at.Wrap( -1 ) - - bSizer37.Add( self.created_at, 0, wx.ALL, 5 ) - - - bSizer361.Add( bSizer37, 0, wx.EXPAND, 5 ) - - bSizer371 = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticText151 = wx.StaticText( self.panel_statistics, wx.ID_ANY, _(u"Last connection"), wx.DefaultPosition, wx.DefaultSize, 0 ) - self.m_staticText151.Wrap( -1 ) - - self.m_staticText151.SetMinSize( wx.Size( 200,-1 ) ) - - bSizer371.Add( self.m_staticText151, 0, wx.ALL, 5 ) - - self.last_connection_at = wx.StaticText( self.panel_statistics, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) - self.last_connection_at.Wrap( -1 ) - - bSizer371.Add( self.last_connection_at, 0, wx.ALL, 5 ) - - - bSizer361.Add( bSizer371, 0, wx.EXPAND, 5 ) - - bSizer3711 = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticText1511 = wx.StaticText( self.panel_statistics, wx.ID_ANY, _(u"Successful connections"), wx.DefaultPosition, wx.DefaultSize, 0 ) - self.m_staticText1511.Wrap( -1 ) - - self.m_staticText1511.SetMinSize( wx.Size( 200,-1 ) ) - - bSizer3711.Add( self.m_staticText1511, 0, wx.ALL, 5 ) - - self.successful_connections = wx.StaticText( self.panel_statistics, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) - self.successful_connections.Wrap( -1 ) - - bSizer3711.Add( self.successful_connections, 0, wx.ALL, 5 ) - - - bSizer361.Add( bSizer3711, 0, wx.EXPAND, 5 ) - - bSizer37111 = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticText15111 = wx.StaticText( self.panel_statistics, wx.ID_ANY, _(u"Unsuccessful connections"), wx.DefaultPosition, wx.DefaultSize, 0 ) - self.m_staticText15111.Wrap( -1 ) - - self.m_staticText15111.SetMinSize( wx.Size( 200,-1 ) ) - - bSizer37111.Add( self.m_staticText15111, 0, wx.ALL, 5 ) - - self.unsuccessful_connections = wx.StaticText( self.panel_statistics, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) - self.unsuccessful_connections.Wrap( -1 ) - - bSizer37111.Add( self.unsuccessful_connections, 0, wx.ALL, 5 ) - - - bSizer361.Add( bSizer37111, 0, wx.EXPAND, 5 ) - - - self.panel_statistics.SetSizer( bSizer361 ) - self.panel_statistics.Layout() - bSizer361.Fit( self.panel_statistics ) - self.m_notebook4.AddPage( self.panel_statistics, _(u"Statistics"), False ) - - bSizer36.Add( self.m_notebook4, 1, wx.ALL|wx.EXPAND, 5 ) - - - self.m_panel17.SetSizer( bSizer36 ) - self.m_panel17.Layout() - bSizer36.Fit( self.m_panel17 ) - self.m_splitter3.SplitVertically( self.m_panel16, self.m_panel17, 250 ) - bSizer34.Add( self.m_splitter3, 1, wx.EXPAND, 5 ) - - self.m_staticline4 = wx.StaticLine( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL ) - bSizer34.Add( self.m_staticline4, 0, wx.EXPAND | wx.ALL, 5 ) - - bSizer28 = wx.BoxSizer( wx.HORIZONTAL ) - - bSizer301 = wx.BoxSizer( wx.HORIZONTAL ) - - self.btn_create = wx.Button( self, wx.ID_ANY, _(u"Create"), wx.DefaultPosition, wx.DefaultSize, 0 ) - - self.btn_create.SetBitmap( wx.Bitmap( u"icons/16x16/add.png", wx.BITMAP_TYPE_ANY ) ) - bSizer301.Add( self.btn_create, 0, wx.ALL|wx.BOTTOM, 5 ) - - self.btn_create_directory = wx.Button( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.BU_EXACTFIT|wx.BU_NOTEXT ) - - self.btn_create_directory.SetBitmap( wx.Bitmap( u"icons/16x16/folder.png", wx.BITMAP_TYPE_ANY ) ) - bSizer301.Add( self.btn_create_directory, 0, wx.ALL, 5 ) - - self.btn_delete = wx.Button( self, wx.ID_ANY, _(u"Delete"), wx.DefaultPosition, wx.DefaultSize, 0 ) - - self.btn_delete.SetBitmap( wx.Bitmap( u"icons/16x16/delete.png", wx.BITMAP_TYPE_ANY ) ) - self.btn_delete.Enable( False ) - - bSizer301.Add( self.btn_delete, 0, wx.ALL, 5 ) - - - bSizer28.Add( bSizer301, 1, wx.EXPAND, 5 ) - - bSizer110 = wx.BoxSizer( wx.HORIZONTAL ) - - - bSizer28.Add( bSizer110, 1, wx.EXPAND, 5 ) - - bSizer29 = wx.BoxSizer( wx.HORIZONTAL ) - - self.btn_cancel = wx.Button( self, wx.ID_ANY, _(u"Cancel"), wx.DefaultPosition, wx.DefaultSize, 0 ) - self.btn_cancel.Hide() - - bSizer29.Add( self.btn_cancel, 0, wx.ALL, 5 ) - - self.btn_save = wx.Button( self, wx.ID_ANY, _(u"Save"), wx.DefaultPosition, wx.DefaultSize, 0 ) - - self.btn_save.SetBitmap( wx.Bitmap( u"icons/16x16/disk.png", wx.BITMAP_TYPE_ANY ) ) - self.btn_save.Enable( False ) - - bSizer29.Add( self.btn_save, 0, wx.ALL, 5 ) - - self.btn_test = wx.Button( self, wx.ID_ANY, _(u"Test"), wx.DefaultPosition, wx.DefaultSize, 0 ) - - self.btn_test.SetBitmap( wx.Bitmap( u"icons/16x16/world_go.png", wx.BITMAP_TYPE_ANY ) ) - self.btn_test.Enable( False ) - - bSizer29.Add( self.btn_test, 0, wx.ALL, 5 ) - - self.btn_open = wx.Button( self, wx.ID_ANY, _(u"Connect"), wx.DefaultPosition, wx.DefaultSize, 0 ) - - self.btn_open.SetBitmap( wx.Bitmap( u"icons/16x16/server_go.png", wx.BITMAP_TYPE_ANY ) ) - self.btn_open.Enable( False ) - - bSizer29.Add( self.btn_open, 0, wx.ALL, 5 ) - - - bSizer28.Add( bSizer29, 0, wx.EXPAND, 5 ) - - - bSizer34.Add( bSizer28, 0, wx.EXPAND, 0 ) - - - self.SetSizer( bSizer34 ) - self.Layout() - - self.Centre( wx.BOTH ) - - # Connect Events - self.Bind( wx.EVT_CLOSE, self.on_close ) - self.Bind( wx.EVT_MENU, self.on_new_directory, id = self.m_menuItem4.GetId() ) - self.Bind( wx.EVT_MENU, self.on_new_session, id = self.m_menuItem5.GetId() ) - self.Bind( wx.EVT_MENU, self.on_import, id = self.m_menuItem10.GetId() ) - self.engine.Bind( wx.EVT_CHOICE, self.on_choice_engine ) - self.btn_create.Bind( wx.EVT_BUTTON, self.on_create_session ) - self.btn_create_directory.Bind( wx.EVT_BUTTON, self.on_create_directory ) - self.btn_delete.Bind( wx.EVT_BUTTON, self.on_delete ) - self.btn_save.Bind( wx.EVT_BUTTON, self.on_save ) - self.btn_open.Bind( wx.EVT_BUTTON, self.on_connect ) - - def __del__( self ): - pass - - - # Virtual event handlers, override them in your derived class - def on_close( self, event ): - event.Skip() - - def on_new_directory( self, event ): - event.Skip() - - def on_new_session( self, event ): - event.Skip() - - def on_import( self, event ): - event.Skip() - - def on_choice_engine( self, event ): - event.Skip() - - def on_create_session( self, event ): - event.Skip() - - def on_create_directory( self, event ): - event.Skip() - - def on_delete( self, event ): - event.Skip() - - def on_save( self, event ): - event.Skip() - - def on_connect( self, event ): - event.Skip() - - def m_splitter3OnIdle( self, event ): - self.m_splitter3.SetSashPosition( 250 ) - self.m_splitter3.Unbind( wx.EVT_IDLE ) - - def m_panel16OnContextMenu( self, event ): - self.m_panel16.PopupMenu( self.m_menu5, event.GetPosition() ) - - -########################################################################### -## Class Settings -########################################################################### - -class Settings ( wx.Dialog ): - - def __init__( self, parent ): - wx.Dialog.__init__ ( self, parent, id = wx.ID_ANY, title = _(u"Settings"), pos = wx.DefaultPosition, size = wx.Size( 800,600 ), style = wx.DEFAULT_DIALOG_STYLE ) - - self.SetSizeHints( wx.Size( 800,600 ), wx.DefaultSize ) - - bSizer63 = wx.BoxSizer( wx.VERTICAL ) - - self.m_notebook4 = wx.Notebook( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0 ) - self.locales = wx.Panel( self.m_notebook4, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) - bSizer65 = wx.BoxSizer( wx.VERTICAL ) - - bSizer64 = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticText27 = wx.StaticText( self.locales, wx.ID_ANY, _(u"Language"), wx.DefaultPosition, wx.DefaultSize, 0 ) - self.m_staticText27.Wrap( -1 ) - - bSizer64.Add( self.m_staticText27, 0, wx.ALL, 5 ) - - m_choice5Choices = [ _(u"English"), _(u"Italian"), _(u"French") ] - self.m_choice5 = wx.Choice( self.locales, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, m_choice5Choices, 0|wx.BORDER_NONE ) - self.m_choice5.SetSelection( 0 ) - bSizer64.Add( self.m_choice5, 1, wx.ALL, 5 ) - - - bSizer65.Add( bSizer64, 1, wx.EXPAND, 5 ) - - - self.locales.SetSizer( bSizer65 ) - self.locales.Layout() - bSizer65.Fit( self.locales ) - self.m_notebook4.AddPage( self.locales, _(u"Locale"), False ) - - bSizer63.Add( self.m_notebook4, 1, wx.EXPAND | wx.ALL, 5 ) - - - self.SetSizer( bSizer63 ) - self.Layout() - - self.Centre( wx.BOTH ) - - def __del__( self ): - pass - - -########################################################################### -## Class AdvancedCellEditorDialog -########################################################################### - -class AdvancedCellEditorDialog ( wx.Dialog ): - - def __init__( self, parent ): - wx.Dialog.__init__ ( self, parent, id = wx.ID_ANY, title = _(u"Edit Value"), pos = wx.DefaultPosition, size = wx.Size( 900,550 ), style = wx.DEFAULT_DIALOG_STYLE ) - - self.SetSizeHints( wx.Size( 640,480 ), wx.DefaultSize ) - - bSizer111 = wx.BoxSizer( wx.VERTICAL ) - - bSizer112 = wx.BoxSizer( wx.VERTICAL ) - - bSizer113 = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticText51 = wx.StaticText( self, wx.ID_ANY, _(u"Syntax"), wx.DefaultPosition, wx.Size( -1,-1 ), 0 ) - self.m_staticText51.Wrap( -1 ) - - bSizer113.Add( self.m_staticText51, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) - - syntax_choiceChoices = [] - self.syntax_choice = wx.Choice( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, syntax_choiceChoices, 0 ) - self.syntax_choice.SetSelection( 0 ) - bSizer113.Add( self.syntax_choice, 0, wx.ALL, 5 ) - - - bSizer112.Add( bSizer113, 1, wx.EXPAND, 5 ) - - - bSizer111.Add( bSizer112, 0, wx.EXPAND, 5 ) - - self.advanced_stc_editor = wx.stc.StyledTextCtrl( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0) - self.advanced_stc_editor.SetUseTabs ( False ) - self.advanced_stc_editor.SetTabWidth ( 4 ) - self.advanced_stc_editor.SetIndent ( 4 ) - self.advanced_stc_editor.SetTabIndents( True ) - self.advanced_stc_editor.SetBackSpaceUnIndents( True ) - self.advanced_stc_editor.SetViewEOL( False ) - self.advanced_stc_editor.SetViewWhiteSpace( False ) - self.advanced_stc_editor.SetMarginWidth( 2, 0 ) - self.advanced_stc_editor.SetIndentationGuides( True ) - self.advanced_stc_editor.SetReadOnly( False ) - self.advanced_stc_editor.SetMarginWidth( 1, 0 ) - self.advanced_stc_editor.SetMarginType( 0, wx.stc.STC_MARGIN_NUMBER ) - self.advanced_stc_editor.SetMarginWidth( 0, self.advanced_stc_editor.TextWidth( wx.stc.STC_STYLE_LINENUMBER, "_99999" ) ) - self.advanced_stc_editor.MarkerDefine( wx.stc.STC_MARKNUM_FOLDER, wx.stc.STC_MARK_BOXPLUS ) - self.advanced_stc_editor.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDER, wx.BLACK) - self.advanced_stc_editor.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDER, wx.WHITE) - self.advanced_stc_editor.MarkerDefine( wx.stc.STC_MARKNUM_FOLDEROPEN, wx.stc.STC_MARK_BOXMINUS ) - self.advanced_stc_editor.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDEROPEN, wx.BLACK ) - self.advanced_stc_editor.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDEROPEN, wx.WHITE ) - self.advanced_stc_editor.MarkerDefine( wx.stc.STC_MARKNUM_FOLDERSUB, wx.stc.STC_MARK_EMPTY ) - self.advanced_stc_editor.MarkerDefine( wx.stc.STC_MARKNUM_FOLDEREND, wx.stc.STC_MARK_BOXPLUS ) - self.advanced_stc_editor.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDEREND, wx.BLACK ) - self.advanced_stc_editor.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDEREND, wx.WHITE ) - self.advanced_stc_editor.MarkerDefine( wx.stc.STC_MARKNUM_FOLDEROPENMID, wx.stc.STC_MARK_BOXMINUS ) - self.advanced_stc_editor.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDEROPENMID, wx.BLACK) - self.advanced_stc_editor.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDEROPENMID, wx.WHITE) - self.advanced_stc_editor.MarkerDefine( wx.stc.STC_MARKNUM_FOLDERMIDTAIL, wx.stc.STC_MARK_EMPTY ) - self.advanced_stc_editor.MarkerDefine( wx.stc.STC_MARKNUM_FOLDERTAIL, wx.stc.STC_MARK_EMPTY ) - self.advanced_stc_editor.SetSelBackground( True, wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT ) ) - self.advanced_stc_editor.SetSelForeground( True, wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHTTEXT ) ) - bSizer111.Add( self.advanced_stc_editor, 1, wx.EXPAND | wx.ALL, 5 ) - - bSizer114 = wx.BoxSizer( wx.HORIZONTAL ) - - - bSizer114.Add( ( 0, 0), 1, wx.EXPAND, 5 ) - - self.m_button49 = wx.Button( self, wx.ID_ANY, _(u"Cancel"), wx.DefaultPosition, wx.DefaultSize, 0 ) - bSizer114.Add( self.m_button49, 0, wx.ALL, 5 ) - - self.m_button48 = wx.Button( self, wx.ID_ANY, _(u"Ok"), wx.DefaultPosition, wx.DefaultSize, 0 ) - bSizer114.Add( self.m_button48, 0, wx.ALL, 5 ) - - - bSizer111.Add( bSizer114, 0, wx.EXPAND, 5 ) - - - self.SetSizer( bSizer111 ) - self.Layout() - - self.Centre( wx.BOTH ) - - # Connect Events - self.syntax_choice.Bind( wx.EVT_CHOICE, self.on_syntax_changed ) - - def __del__( self ): - pass - - - # Virtual event handlers, override them in your derived class - def on_syntax_changed( self, event ): - event.Skip() - - -########################################################################### -## Class MainFrameView -########################################################################### - -class MainFrameView ( wx.Frame ): - - def __init__( self, parent ): - wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = _(u"PeterSQL"), pos = wx.DefaultPosition, size = wx.Size( 1024,762 ), style = wx.DEFAULT_FRAME_STYLE|wx.MAXIMIZE_BOX|wx.TAB_TRAVERSAL ) - - self.SetSizeHints( wx.Size( 800,600 ), wx.DefaultSize ) - - self.m_menubar2 = wx.MenuBar( 0 ) - self.m_menu2 = wx.Menu() - self.m_menubar2.Append( self.m_menu2, _(u"File") ) - - self.m_menu4 = wx.Menu() - self.m_menuItem15 = wx.MenuItem( self.m_menu4, wx.ID_ANY, _(u"About"), wx.EmptyString, wx.ITEM_NORMAL ) - self.m_menu4.Append( self.m_menuItem15 ) - - self.m_menubar2.Append( self.m_menu4, _(u"Help") ) - - self.SetMenuBar( self.m_menubar2 ) - - self.m_toolBar1 = self.CreateToolBar( wx.TB_HORIZONTAL, wx.ID_ANY ) - self.m_tool5 = self.m_toolBar1.AddTool( wx.ID_ANY, _(u"Open connection manager"), wx.Bitmap( u"icons/16x16/server_connect.png", wx.BITMAP_TYPE_ANY ), wx.NullBitmap, wx.ITEM_NORMAL, wx.EmptyString, wx.EmptyString, None ) - - self.m_tool4 = self.m_toolBar1.AddTool( wx.ID_ANY, _(u"Disconnect from server"), wx.Bitmap( u"icons/16x16/disconnect.png", wx.BITMAP_TYPE_ANY ), wx.NullBitmap, wx.ITEM_NORMAL, wx.EmptyString, wx.EmptyString, None ) - - self.m_toolBar1.AddSeparator() - - self.database_refresh = self.m_toolBar1.AddTool( wx.ID_ANY, _(u"tool"), wx.Bitmap( u"icons/16x16/database_refresh.png", wx.BITMAP_TYPE_ANY ), wx.NullBitmap, wx.ITEM_NORMAL, _(u"Refresh"), _(u"Refresh"), None ) - - self.m_toolBar1.AddSeparator() - - self.database_add = self.m_toolBar1.AddTool( wx.ID_ANY, _(u"Add"), wx.Bitmap( u"icons/16x16/database_add.png", wx.BITMAP_TYPE_ANY ), wx.NullBitmap, wx.ITEM_NORMAL, wx.EmptyString, wx.EmptyString, None ) - - self.database_delete = self.m_toolBar1.AddTool( wx.ID_ANY, _(u"Add"), wx.Bitmap( u"icons/16x16/database_delete.png", wx.BITMAP_TYPE_ANY ), wx.NullBitmap, wx.ITEM_NORMAL, wx.EmptyString, wx.EmptyString, None ) - - self.m_toolBar1.Realize() - - bSizer19 = wx.BoxSizer( wx.VERTICAL ) - - self.m_panel13 = wx.Panel( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) - bSizer21 = wx.BoxSizer( wx.VERTICAL ) - - self.m_splitter51 = wx.SplitterWindow( self.m_panel13, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.SP_3D|wx.SP_LIVE_UPDATE ) - self.m_splitter51.SetSashGravity( 1 ) - self.m_splitter51.Bind( wx.EVT_IDLE, self.m_splitter51OnIdle ) - - self.m_panel22 = wx.Panel( self.m_splitter51, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) - bSizer72 = wx.BoxSizer( wx.VERTICAL ) - - self.m_splitter4 = wx.SplitterWindow( self.m_panel22, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.SP_LIVE_UPDATE ) - self.m_splitter4.Bind( wx.EVT_IDLE, self.m_splitter4OnIdle ) - self.m_splitter4.SetMinimumPaneSize( 100 ) - - self.m_panel14 = wx.Panel( self.m_splitter4, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) - bSizer24 = wx.BoxSizer( wx.HORIZONTAL ) - - self.tree_ctrl_explorer = wx.lib.agw.hypertreelist.HyperTreeList( - self.m_panel14, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, - agwStyle=wx.TR_DEFAULT_STYLE|wx.TR_SINGLE|wx.TR_FULL_ROW_HIGHLIGHT|wx.TR_HIDE_ROOT|wx.TR_LINES_AT_ROOT - ) - bSizer24.Add( self.tree_ctrl_explorer, 1, wx.ALL|wx.EXPAND, 5 ) - - - self.m_panel14.SetSizer( bSizer24 ) - self.m_panel14.Layout() - bSizer24.Fit( self.m_panel14 ) - self.m_menu5 = wx.Menu() - self.m_menuItem4 = wx.MenuItem( self.m_menu5, wx.ID_ANY, _(u"MyMenuItem"), wx.EmptyString, wx.ITEM_NORMAL ) - self.m_menu5.Append( self.m_menuItem4 ) - - self.m_menu1 = wx.Menu() - self.m_menuItem5 = wx.MenuItem( self.m_menu1, wx.ID_ANY, _(u"MyMenuItem"), wx.EmptyString, wx.ITEM_NORMAL ) - self.m_menu1.Append( self.m_menuItem5 ) - - self.m_menu5.AppendSubMenu( self.m_menu1, _(u"MyMenu") ) - - self.m_panel14.Bind( wx.EVT_RIGHT_DOWN, self.m_panel14OnContextMenu ) - - self.m_panel15 = wx.Panel( self.m_splitter4, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) - bSizer25 = wx.BoxSizer( wx.VERTICAL ) - - self.MainFrameNotebook = wx.Notebook( self.m_panel15, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.NB_FIXEDWIDTH ) - MainFrameNotebookImageSize = wx.Size( 16,16 ) - MainFrameNotebookIndex = 0 - MainFrameNotebookImages = wx.ImageList( MainFrameNotebookImageSize.GetWidth(), MainFrameNotebookImageSize.GetHeight() ) - self.MainFrameNotebook.AssignImageList( MainFrameNotebookImages ) - self.panel_system = wx.Panel( self.MainFrameNotebook, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) - bSizer272 = wx.BoxSizer( wx.VERTICAL ) - - self.m_staticText291 = wx.StaticText( self.panel_system, wx.ID_ANY, _(u"MyLabel"), wx.DefaultPosition, wx.DefaultSize, 0 ) - self.m_staticText291.Wrap( -1 ) - - bSizer272.Add( self.m_staticText291, 0, wx.ALL, 5 ) - - self.system_databases = wx.dataview.DataViewListCtrl( self.panel_system, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0 ) - self.m_dataViewListColumn1 = self.system_databases.AppendTextColumn( _(u"Databases"), wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) - self.m_dataViewListColumn2 = self.system_databases.AppendTextColumn( _(u"Size"), wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) - self.m_dataViewListColumn3 = self.system_databases.AppendTextColumn( _(u"Elements"), wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) - self.m_dataViewListColumn4 = self.system_databases.AppendTextColumn( _(u"Modified at"), wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) - self.m_dataViewListColumn5 = self.system_databases.AppendTextColumn( _(u"Tables"), wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) - bSizer272.Add( self.system_databases, 1, wx.ALL|wx.EXPAND, 5 ) - - - self.panel_system.SetSizer( bSizer272 ) - self.panel_system.Layout() - bSizer272.Fit( self.panel_system ) - self.MainFrameNotebook.AddPage( self.panel_system, _(u"System"), False ) - MainFrameNotebookBitmap = wx.Bitmap( u"icons/16x16/server.png", wx.BITMAP_TYPE_ANY ) - if ( MainFrameNotebookBitmap.IsOk() ): - MainFrameNotebookImages.Add( MainFrameNotebookBitmap ) - self.MainFrameNotebook.SetPageImage( MainFrameNotebookIndex, MainFrameNotebookIndex ) - MainFrameNotebookIndex += 1 - - self.panel_database = wx.Panel( self.MainFrameNotebook, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) - bSizer27 = wx.BoxSizer( wx.VERTICAL ) - - self.m_notebook6 = wx.Notebook( self.panel_database, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0 ) - self.m_panel30 = wx.Panel( self.m_notebook6, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) - bSizer80 = wx.BoxSizer( wx.VERTICAL ) - - bSizer531 = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticText391 = wx.StaticText( self.m_panel30, wx.ID_ANY, _(u"Table:"), wx.DefaultPosition, wx.DefaultSize, 0 ) - self.m_staticText391.Wrap( -1 ) - - bSizer531.Add( self.m_staticText391, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) - - - bSizer531.Add( ( 100, 0), 0, wx.EXPAND, 5 ) - - self.btn_insert_table = wx.Button( self.m_panel30, wx.ID_ANY, _(u"Insert"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) - - self.btn_insert_table.SetBitmap( wx.Bitmap( u"icons/16x16/add.png", wx.BITMAP_TYPE_ANY ) ) - bSizer531.Add( self.btn_insert_table, 0, wx.ALL|wx.EXPAND, 2 ) - - self.btn_clone_table = wx.Button( self.m_panel30, wx.ID_ANY, _(u"Clone"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) - - self.btn_clone_table.SetBitmap( wx.Bitmap( u"icons/16x16/table_multiple.png", wx.BITMAP_TYPE_ANY ) ) - self.btn_clone_table.Enable( False ) - - bSizer531.Add( self.btn_clone_table, 0, wx.ALL|wx.EXPAND, 5 ) - - self.btn_delete_table = wx.Button( self.m_panel30, wx.ID_ANY, _(u"Delete"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) - - self.btn_delete_table.SetBitmap( wx.Bitmap( u"icons/16x16/delete.png", wx.BITMAP_TYPE_ANY ) ) - self.btn_delete_table.Enable( False ) - - bSizer531.Add( self.btn_delete_table, 0, wx.ALL|wx.EXPAND, 2 ) - - - bSizer531.Add( ( 0, 0), 1, wx.EXPAND, 5 ) - - - bSizer80.Add( bSizer531, 0, wx.EXPAND, 5 ) - - self.list_ctrl_database_tables = wx.dataview.DataViewCtrl( self.m_panel30, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0 ) - self.m_dataViewColumn12 = self.list_ctrl_database_tables.AppendTextColumn( _(u"Name"), 0, wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE|wx.dataview.DATAVIEW_COL_SORTABLE ) - self.m_dataViewColumn13 = self.list_ctrl_database_tables.AppendTextColumn( _(u"Rows"), 1, wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_RIGHT, wx.dataview.DATAVIEW_COL_RESIZABLE|wx.dataview.DATAVIEW_COL_SORTABLE ) - self.m_dataViewColumn14 = self.list_ctrl_database_tables.AppendTextColumn( _(u"Size"), 2, wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_RIGHT, wx.dataview.DATAVIEW_COL_RESIZABLE|wx.dataview.DATAVIEW_COL_SORTABLE ) - self.m_dataViewColumn15 = self.list_ctrl_database_tables.AppendDateColumn( _(u"Created at"), 3, wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE|wx.dataview.DATAVIEW_COL_SORTABLE ) - self.m_dataViewColumn16 = self.list_ctrl_database_tables.AppendDateColumn( _(u"Updated at"), 4, wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE|wx.dataview.DATAVIEW_COL_SORTABLE ) - self.m_dataViewColumn17 = self.list_ctrl_database_tables.AppendTextColumn( _(u"Engine"), 5, wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE|wx.dataview.DATAVIEW_COL_SORTABLE ) - self.m_dataViewColumn19 = self.list_ctrl_database_tables.AppendTextColumn( _(u"Collation"), 6, wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE|wx.dataview.DATAVIEW_COL_SORTABLE ) - self.m_dataViewColumn18 = self.list_ctrl_database_tables.AppendTextColumn( _(u"Comments"), 7, wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE|wx.dataview.DATAVIEW_COL_SORTABLE ) - bSizer80.Add( self.list_ctrl_database_tables, 1, wx.ALL|wx.EXPAND, 5 ) - - - self.m_panel30.SetSizer( bSizer80 ) - self.m_panel30.Layout() - bSizer80.Fit( self.m_panel30 ) - self.m_notebook6.AddPage( self.m_panel30, _(u"Tables"), False ) - self.m_panel31 = wx.Panel( self.m_notebook6, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) - bSizer82 = wx.BoxSizer( wx.VERTICAL ) - - - self.m_panel31.SetSizer( bSizer82 ) - self.m_panel31.Layout() - bSizer82.Fit( self.m_panel31 ) - self.m_notebook6.AddPage( self.m_panel31, _(u"Diagram"), False ) - - bSizer27.Add( self.m_notebook6, 1, wx.EXPAND | wx.ALL, 5 ) - - - self.panel_database.SetSizer( bSizer27 ) - self.panel_database.Layout() - bSizer27.Fit( self.panel_database ) - self.m_menu15 = wx.Menu() - self.panel_database.Bind( wx.EVT_RIGHT_DOWN, self.panel_databaseOnContextMenu ) - - self.MainFrameNotebook.AddPage( self.panel_database, _(u"Database"), False ) - MainFrameNotebookBitmap = wx.Bitmap( u"icons/16x16/database.png", wx.BITMAP_TYPE_ANY ) - if ( MainFrameNotebookBitmap.IsOk() ): - MainFrameNotebookImages.Add( MainFrameNotebookBitmap ) - self.MainFrameNotebook.SetPageImage( MainFrameNotebookIndex, MainFrameNotebookIndex ) - MainFrameNotebookIndex += 1 - - self.panel_table = wx.Panel( self.MainFrameNotebook, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) - bSizer251 = wx.BoxSizer( wx.VERTICAL ) - - self.m_splitter41 = wx.SplitterWindow( self.panel_table, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.SP_LIVE_UPDATE ) - self.m_splitter41.Bind( wx.EVT_IDLE, self.m_splitter41OnIdle ) - self.m_splitter41.SetMinimumPaneSize( 200 ) - - self.m_panel19 = wx.Panel( self.m_splitter41, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) - bSizer55 = wx.BoxSizer( wx.VERTICAL ) - - self.m_notebook3 = wx.Notebook( self.m_panel19, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.NB_FIXEDWIDTH ) - m_notebook3ImageSize = wx.Size( 16,16 ) - m_notebook3Index = 0 - m_notebook3Images = wx.ImageList( m_notebook3ImageSize.GetWidth(), m_notebook3ImageSize.GetHeight() ) - self.m_notebook3.AssignImageList( m_notebook3Images ) - self.PanelTableBase = wx.Panel( self.m_notebook3, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) - bSizer262 = wx.BoxSizer( wx.VERTICAL ) - - bSizer271 = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticText8 = wx.StaticText( self.PanelTableBase, wx.ID_ANY, _(u"Name"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) - self.m_staticText8.Wrap( -1 ) - - bSizer271.Add( self.m_staticText8, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) - - self.table_name = wx.TextCtrl( self.PanelTableBase, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) - bSizer271.Add( self.table_name, 1, wx.ALL|wx.EXPAND, 5 ) - - - bSizer262.Add( bSizer271, 0, wx.EXPAND, 5 ) - - bSizer273 = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticText83 = wx.StaticText( self.PanelTableBase, wx.ID_ANY, _(u"Comments"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) - self.m_staticText83.Wrap( -1 ) - - bSizer273.Add( self.m_staticText83, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) - - self.table_comment = wx.TextCtrl( self.PanelTableBase, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_MULTILINE ) - bSizer273.Add( self.table_comment, 1, wx.ALL|wx.EXPAND, 5 ) - - - bSizer262.Add( bSizer273, 1, wx.EXPAND, 5 ) - - - self.PanelTableBase.SetSizer( bSizer262 ) - self.PanelTableBase.Layout() - bSizer262.Fit( self.PanelTableBase ) - self.m_notebook3.AddPage( self.PanelTableBase, _(u"Base"), True ) - m_notebook3Bitmap = wx.Bitmap( u"icons/16x16/table.png", wx.BITMAP_TYPE_ANY ) - if ( m_notebook3Bitmap.IsOk() ): - m_notebook3Images.Add( m_notebook3Bitmap ) - self.m_notebook3.SetPageImage( m_notebook3Index, m_notebook3Index ) - m_notebook3Index += 1 - - self.PanelTableOptions = wx.Panel( self.m_notebook3, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) - bSizer261 = wx.BoxSizer( wx.VERTICAL ) - - gSizer11 = wx.GridSizer( 0, 2, 0, 0 ) - - bSizer27111 = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticText8111 = wx.StaticText( self.PanelTableOptions, wx.ID_ANY, _(u"Auto Increment"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) - self.m_staticText8111.Wrap( -1 ) - - bSizer27111.Add( self.m_staticText8111, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) - - self.table_auto_increment = wx.TextCtrl( self.PanelTableOptions, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) - bSizer27111.Add( self.table_auto_increment, 1, wx.ALL|wx.EXPAND, 5 ) - - - gSizer11.Add( bSizer27111, 1, wx.EXPAND, 5 ) - - bSizer2712 = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticText812 = wx.StaticText( self.PanelTableOptions, wx.ID_ANY, _(u"Engine"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) - self.m_staticText812.Wrap( -1 ) - - bSizer2712.Add( self.m_staticText812, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) - - table_engineChoices = [ wx.EmptyString ] - self.table_engine = wx.Choice( self.PanelTableOptions, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, table_engineChoices, 0 ) - self.table_engine.SetSelection( 1 ) - bSizer2712.Add( self.table_engine, 1, wx.ALL|wx.EXPAND, 5 ) - - - gSizer11.Add( bSizer2712, 0, wx.EXPAND, 5 ) - - bSizer2721 = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticText821 = wx.StaticText( self.PanelTableOptions, wx.ID_ANY, _(u"Default Collation"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) - self.m_staticText821.Wrap( -1 ) - - bSizer2721.Add( self.m_staticText821, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) - - table_collationChoices = [] - self.table_collation = wx.Choice( self.PanelTableOptions, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, table_collationChoices, 0 ) - self.table_collation.SetSelection( 0 ) - bSizer2721.Add( self.table_collation, 1, wx.ALL, 5 ) - - - gSizer11.Add( bSizer2721, 0, wx.EXPAND, 5 ) - - - bSizer261.Add( gSizer11, 0, wx.EXPAND, 5 ) - - - self.PanelTableOptions.SetSizer( bSizer261 ) - self.PanelTableOptions.Layout() - bSizer261.Fit( self.PanelTableOptions ) - self.m_notebook3.AddPage( self.PanelTableOptions, _(u"Options"), False ) - m_notebook3Bitmap = wx.Bitmap( u"icons/16x16/wrench.png", wx.BITMAP_TYPE_ANY ) - if ( m_notebook3Bitmap.IsOk() ): - m_notebook3Images.Add( m_notebook3Bitmap ) - self.m_notebook3.SetPageImage( m_notebook3Index, m_notebook3Index ) - m_notebook3Index += 1 - - self.PanelTableIndex = wx.Panel( self.m_notebook3, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) - bSizer28 = wx.BoxSizer( wx.HORIZONTAL ) - - bSizer791 = wx.BoxSizer( wx.VERTICAL ) - - self.btn_delete_index = wx.Button( self.PanelTableIndex, wx.ID_ANY, _(u"Remove"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) - - self.btn_delete_index.SetBitmap( wx.Bitmap( u"icons/16x16/delete.png", wx.BITMAP_TYPE_ANY ) ) - self.btn_delete_index.Enable( False ) - - bSizer791.Add( self.btn_delete_index, 0, wx.ALL|wx.EXPAND, 5 ) - - self.btn_clear_index = wx.Button( self.PanelTableIndex, wx.ID_ANY, _(u"Clear"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) - - self.btn_clear_index.SetBitmap( wx.Bitmap( u"icons/16x16/cross.png", wx.BITMAP_TYPE_ANY ) ) - bSizer791.Add( self.btn_clear_index, 0, wx.ALL|wx.EXPAND, 5 ) - - - bSizer28.Add( bSizer791, 0, wx.ALIGN_CENTER, 5 ) - - self.dv_table_indexes = TableIndexesDataViewCtrl( self.PanelTableIndex, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0 ) - bSizer28.Add( self.dv_table_indexes, 1, wx.ALL|wx.EXPAND, 0 ) - - - self.PanelTableIndex.SetSizer( bSizer28 ) - self.PanelTableIndex.Layout() - bSizer28.Fit( self.PanelTableIndex ) - self.m_notebook3.AddPage( self.PanelTableIndex, _(u"Indexes"), False ) - m_notebook3Bitmap = wx.Bitmap( u"icons/16x16/lightning.png", wx.BITMAP_TYPE_ANY ) - if ( m_notebook3Bitmap.IsOk() ): - m_notebook3Images.Add( m_notebook3Bitmap ) - self.m_notebook3.SetPageImage( m_notebook3Index, m_notebook3Index ) - m_notebook3Index += 1 - - self.PanelTableFK = wx.Panel( self.m_notebook3, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) - bSizer77 = wx.BoxSizer( wx.VERTICAL ) - - bSizer78 = wx.BoxSizer( wx.HORIZONTAL ) - - bSizer79 = wx.BoxSizer( wx.VERTICAL ) - - self.btn_insert_foreign_key = wx.Button( self.PanelTableFK, wx.ID_ANY, _(u"Insert"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) - - self.btn_insert_foreign_key.SetBitmap( wx.Bitmap( u"icons/16x16/add.png", wx.BITMAP_TYPE_ANY ) ) - bSizer79.Add( self.btn_insert_foreign_key, 0, wx.ALL|wx.EXPAND, 5 ) - - self.btn_delete_foreign_key = wx.Button( self.PanelTableFK, wx.ID_ANY, _(u"Remove"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) - - self.btn_delete_foreign_key.SetBitmap( wx.Bitmap( u"icons/16x16/delete.png", wx.BITMAP_TYPE_ANY ) ) - self.btn_delete_foreign_key.Enable( False ) - - bSizer79.Add( self.btn_delete_foreign_key, 0, wx.ALL|wx.EXPAND, 5 ) - - self.btn_clear_foreign_key = wx.Button( self.PanelTableFK, wx.ID_ANY, _(u"Clear"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) - - self.btn_clear_foreign_key.SetBitmap( wx.Bitmap( u"icons/16x16/cross.png", wx.BITMAP_TYPE_ANY ) ) - bSizer79.Add( self.btn_clear_foreign_key, 0, wx.ALL|wx.EXPAND, 5 ) - - - bSizer78.Add( bSizer79, 0, wx.ALIGN_CENTER, 5 ) - - self.dv_table_foreign_keys = TableForeignKeysDataViewCtrl( self.PanelTableFK, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0 ) - bSizer78.Add( self.dv_table_foreign_keys, 1, wx.ALL|wx.EXPAND, 0 ) - - - bSizer77.Add( bSizer78, 1, wx.EXPAND, 5 ) - - - self.PanelTableFK.SetSizer( bSizer77 ) - self.PanelTableFK.Layout() - bSizer77.Fit( self.PanelTableFK ) - self.m_notebook3.AddPage( self.PanelTableFK, _(u"Foreign Keys"), False ) - m_notebook3Bitmap = wx.Bitmap( u"icons/16x16/table_relationship.png", wx.BITMAP_TYPE_ANY ) - if ( m_notebook3Bitmap.IsOk() ): - m_notebook3Images.Add( m_notebook3Bitmap ) - self.m_notebook3.SetPageImage( m_notebook3Index, m_notebook3Index ) - m_notebook3Index += 1 - - self.PanelTableCheck = wx.Panel( self.m_notebook3, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) - bSizer771 = wx.BoxSizer( wx.VERTICAL ) - - bSizer781 = wx.BoxSizer( wx.HORIZONTAL ) - - bSizer792 = wx.BoxSizer( wx.VERTICAL ) - - self.btn_insert_check = wx.Button( self.PanelTableCheck, wx.ID_ANY, _(u"Insert"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) - - self.btn_insert_check.SetBitmap( wx.Bitmap( u"icons/16x16/add.png", wx.BITMAP_TYPE_ANY ) ) - bSizer792.Add( self.btn_insert_check, 0, wx.ALL|wx.EXPAND, 5 ) - - self.btn_delete_check = wx.Button( self.PanelTableCheck, wx.ID_ANY, _(u"Remove"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) - - self.btn_delete_check.SetBitmap( wx.Bitmap( u"icons/16x16/delete.png", wx.BITMAP_TYPE_ANY ) ) - self.btn_delete_check.Enable( False ) - - bSizer792.Add( self.btn_delete_check, 0, wx.ALL|wx.EXPAND, 5 ) - - self.btn_clear_check = wx.Button( self.PanelTableCheck, wx.ID_ANY, _(u"Clear"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) - - self.btn_clear_check.SetBitmap( wx.Bitmap( u"icons/16x16/cross.png", wx.BITMAP_TYPE_ANY ) ) - bSizer792.Add( self.btn_clear_check, 0, wx.ALL|wx.EXPAND, 5 ) - - - bSizer781.Add( bSizer792, 0, wx.ALIGN_CENTER, 5 ) - - self.dv_table_checks = TableCheckDataViewCtrl( self.PanelTableCheck, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0 ) - bSizer781.Add( self.dv_table_checks, 1, wx.ALL|wx.EXPAND, 0 ) - - - bSizer771.Add( bSizer781, 1, wx.EXPAND, 5 ) - - - self.PanelTableCheck.SetSizer( bSizer771 ) - self.PanelTableCheck.Layout() - bSizer771.Fit( self.PanelTableCheck ) - self.m_notebook3.AddPage( self.PanelTableCheck, _(u"Checks"), False ) - m_notebook3Bitmap = wx.Bitmap( u"icons/16x16/tick.png", wx.BITMAP_TYPE_ANY ) - if ( m_notebook3Bitmap.IsOk() ): - m_notebook3Images.Add( m_notebook3Bitmap ) - self.m_notebook3.SetPageImage( m_notebook3Index, m_notebook3Index ) - m_notebook3Index += 1 - - self.PanelTableCreate = wx.Panel( self.m_notebook3, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) - bSizer109 = wx.BoxSizer( wx.VERTICAL ) - - self.sql_create_table = wx.stc.StyledTextCtrl( self.PanelTableCreate, wx.ID_ANY, wx.DefaultPosition, wx.Size( -1,200 ), 0) - self.sql_create_table.SetUseTabs ( True ) - self.sql_create_table.SetTabWidth ( 4 ) - self.sql_create_table.SetIndent ( 4 ) - self.sql_create_table.SetTabIndents( True ) - self.sql_create_table.SetBackSpaceUnIndents( True ) - self.sql_create_table.SetViewEOL( False ) - self.sql_create_table.SetViewWhiteSpace( False ) - self.sql_create_table.SetMarginWidth( 2, 0 ) - self.sql_create_table.SetIndentationGuides( True ) - self.sql_create_table.SetReadOnly( False ) - self.sql_create_table.SetMarginWidth( 1, 0 ) - self.sql_create_table.SetMarginType( 0, wx.stc.STC_MARGIN_NUMBER ) - self.sql_create_table.SetMarginWidth( 0, self.sql_create_table.TextWidth( wx.stc.STC_STYLE_LINENUMBER, "_99999" ) ) - self.sql_create_table.MarkerDefine( wx.stc.STC_MARKNUM_FOLDER, wx.stc.STC_MARK_BOXPLUS ) - self.sql_create_table.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDER, wx.BLACK) - self.sql_create_table.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDER, wx.WHITE) - self.sql_create_table.MarkerDefine( wx.stc.STC_MARKNUM_FOLDEROPEN, wx.stc.STC_MARK_BOXMINUS ) - self.sql_create_table.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDEROPEN, wx.BLACK ) - self.sql_create_table.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDEROPEN, wx.WHITE ) - self.sql_create_table.MarkerDefine( wx.stc.STC_MARKNUM_FOLDERSUB, wx.stc.STC_MARK_EMPTY ) - self.sql_create_table.MarkerDefine( wx.stc.STC_MARKNUM_FOLDEREND, wx.stc.STC_MARK_BOXPLUS ) - self.sql_create_table.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDEREND, wx.BLACK ) - self.sql_create_table.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDEREND, wx.WHITE ) - self.sql_create_table.MarkerDefine( wx.stc.STC_MARKNUM_FOLDEROPENMID, wx.stc.STC_MARK_BOXMINUS ) - self.sql_create_table.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDEROPENMID, wx.BLACK) - self.sql_create_table.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDEROPENMID, wx.WHITE) - self.sql_create_table.MarkerDefine( wx.stc.STC_MARKNUM_FOLDERMIDTAIL, wx.stc.STC_MARK_EMPTY ) - self.sql_create_table.MarkerDefine( wx.stc.STC_MARKNUM_FOLDERTAIL, wx.stc.STC_MARK_EMPTY ) - self.sql_create_table.SetSelBackground( True, wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT ) ) - self.sql_create_table.SetSelForeground( True, wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHTTEXT ) ) - bSizer109.Add( self.sql_create_table, 1, wx.EXPAND | wx.ALL, 5 ) - - - self.PanelTableCreate.SetSizer( bSizer109 ) - self.PanelTableCreate.Layout() - bSizer109.Fit( self.PanelTableCreate ) - self.m_notebook3.AddPage( self.PanelTableCreate, _(u"Create"), False ) - m_notebook3Bitmap = wx.Bitmap( u"icons/16x16/code-folding.png", wx.BITMAP_TYPE_ANY ) - if ( m_notebook3Bitmap.IsOk() ): - m_notebook3Images.Add( m_notebook3Bitmap ) - self.m_notebook3.SetPageImage( m_notebook3Index, m_notebook3Index ) - m_notebook3Index += 1 - - - bSizer55.Add( self.m_notebook3, 1, wx.EXPAND | wx.ALL, 5 ) - - - self.m_panel19.SetSizer( bSizer55 ) - self.m_panel19.Layout() - bSizer55.Fit( self.m_panel19 ) - self.panel_table_columns = wx.Panel( self.m_splitter41, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) - self.panel_table_columns.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_WINDOW ) ) - - bSizer54 = wx.BoxSizer( wx.VERTICAL ) - - bSizer53 = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticText39 = wx.StaticText( self.panel_table_columns, wx.ID_ANY, _(u"Columns:"), wx.DefaultPosition, wx.DefaultSize, 0 ) - self.m_staticText39.Wrap( -1 ) - - bSizer53.Add( self.m_staticText39, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) - - - bSizer53.Add( ( 100, 0), 0, wx.EXPAND, 5 ) - - self.btn_insert_column = wx.Button( self.panel_table_columns, wx.ID_ANY, _(u"Insert"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) - - self.btn_insert_column.SetBitmap( wx.Bitmap( u"icons/16x16/add.png", wx.BITMAP_TYPE_ANY ) ) - bSizer53.Add( self.btn_insert_column, 0, wx.LEFT|wx.RIGHT, 2 ) - - self.btn_delete_column = wx.Button( self.panel_table_columns, wx.ID_ANY, _(u"Delete"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) - - self.btn_delete_column.SetBitmap( wx.Bitmap( u"icons/16x16/delete.png", wx.BITMAP_TYPE_ANY ) ) - self.btn_delete_column.Enable( False ) - - bSizer53.Add( self.btn_delete_column, 0, wx.LEFT|wx.RIGHT, 2 ) - - self.btn_move_up_column = wx.Button( self.panel_table_columns, wx.ID_ANY, _(u"Up"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) - - self.btn_move_up_column.SetBitmap( wx.Bitmap( u"icons/16x16/arrow_up.png", wx.BITMAP_TYPE_ANY ) ) - self.btn_move_up_column.Enable( False ) - - bSizer53.Add( self.btn_move_up_column, 0, wx.LEFT|wx.RIGHT, 2 ) - - self.btn_move_down_column = wx.Button( self.panel_table_columns, wx.ID_ANY, _(u"Down"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) - - self.btn_move_down_column.SetBitmap( wx.Bitmap( u"icons/16x16/arrow_down.png", wx.BITMAP_TYPE_ANY ) ) - self.btn_move_down_column.Enable( False ) - - bSizer53.Add( self.btn_move_down_column, 0, wx.LEFT|wx.RIGHT, 2 ) - - - bSizer53.Add( ( 0, 0), 1, wx.EXPAND, 5 ) - - - bSizer54.Add( bSizer53, 0, wx.ALL|wx.EXPAND, 5 ) - - self.list_ctrl_table_columns = TableColumnsDataViewCtrl( self.panel_table_columns, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0 ) - bSizer54.Add( self.list_ctrl_table_columns, 1, wx.ALL|wx.EXPAND, 5 ) - - bSizer52 = wx.BoxSizer( wx.HORIZONTAL ) - - self.btn_delete_table = wx.Button( self.panel_table_columns, wx.ID_ANY, _(u"Delete"), wx.DefaultPosition, wx.DefaultSize, 0 ) - bSizer52.Add( self.btn_delete_table, 0, wx.ALL, 5 ) - - self.btn_cancel_table = wx.Button( self.panel_table_columns, wx.ID_ANY, _(u"Cancel"), wx.DefaultPosition, wx.DefaultSize, 0 ) - self.btn_cancel_table.Enable( False ) - - bSizer52.Add( self.btn_cancel_table, 0, wx.ALL, 5 ) - - self.btn_apply_table = wx.Button( self.panel_table_columns, wx.ID_ANY, _(u"Apply"), wx.DefaultPosition, wx.DefaultSize, 0 ) - self.btn_apply_table.Enable( False ) - - bSizer52.Add( self.btn_apply_table, 0, wx.ALL, 5 ) - - - bSizer54.Add( bSizer52, 0, wx.EXPAND, 5 ) - - - self.panel_table_columns.SetSizer( bSizer54 ) - self.panel_table_columns.Layout() - bSizer54.Fit( self.panel_table_columns ) - self.menu_table_columns = wx.Menu() - self.add_index = wx.MenuItem( self.menu_table_columns, wx.ID_ANY, _(u"Add Index"), wx.EmptyString, wx.ITEM_NORMAL ) - self.menu_table_columns.Append( self.add_index ) - - self.m_menu21 = wx.Menu() - self.m_menuItem8 = wx.MenuItem( self.m_menu21, wx.ID_ANY, _(u"Add PrimaryKey"), wx.EmptyString, wx.ITEM_NORMAL ) - self.m_menu21.Append( self.m_menuItem8 ) - - self.m_menuItem9 = wx.MenuItem( self.m_menu21, wx.ID_ANY, _(u"Add Index"), wx.EmptyString, wx.ITEM_NORMAL ) - self.m_menu21.Append( self.m_menuItem9 ) - - self.menu_table_columns.AppendSubMenu( self.m_menu21, _(u"MyMenu") ) - - self.panel_table_columns.Bind( wx.EVT_RIGHT_DOWN, self.panel_table_columnsOnContextMenu ) - - self.m_splitter41.SplitHorizontally( self.m_panel19, self.panel_table_columns, 200 ) - bSizer251.Add( self.m_splitter41, 1, wx.EXPAND, 0 ) - - - self.panel_table.SetSizer( bSizer251 ) - self.panel_table.Layout() - bSizer251.Fit( self.panel_table ) - self.MainFrameNotebook.AddPage( self.panel_table, _(u"Table"), False ) - MainFrameNotebookBitmap = wx.Bitmap( u"icons/16x16/table.png", wx.BITMAP_TYPE_ANY ) - if ( MainFrameNotebookBitmap.IsOk() ): - MainFrameNotebookImages.Add( MainFrameNotebookBitmap ) - self.MainFrameNotebook.SetPageImage( MainFrameNotebookIndex, MainFrameNotebookIndex ) - MainFrameNotebookIndex += 1 - - self.panel_views = wx.Panel( self.MainFrameNotebook, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) - bSizer84 = wx.BoxSizer( wx.VERTICAL ) - - self.m_notebook7 = wx.Notebook( self.panel_views, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0 ) - self.m_panel34 = wx.Panel( self.m_notebook7, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) - bSizer85 = wx.BoxSizer( wx.VERTICAL ) - - bSizer86 = wx.BoxSizer( wx.VERTICAL ) - - bSizer89 = wx.BoxSizer( wx.HORIZONTAL ) - - bSizer87 = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticText40 = wx.StaticText( self.m_panel34, wx.ID_ANY, _(u"Name"), wx.DefaultPosition, wx.DefaultSize, 0 ) - self.m_staticText40.Wrap( -1 ) - - bSizer87.Add( self.m_staticText40, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) - - self.m_textCtrl22 = wx.TextCtrl( self.m_panel34, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) - bSizer87.Add( self.m_textCtrl22, 1, wx.ALL|wx.EXPAND, 5 ) - - - bSizer89.Add( bSizer87, 1, wx.EXPAND, 5 ) - - bSizer871 = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticText401 = wx.StaticText( self.m_panel34, wx.ID_ANY, _(u"Temporary"), wx.DefaultPosition, wx.DefaultSize, 0 ) - self.m_staticText401.Wrap( -1 ) - - bSizer871.Add( self.m_staticText401, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) - - self.m_checkBox5 = wx.CheckBox( self.m_panel34, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) - bSizer871.Add( self.m_checkBox5, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) - - - bSizer89.Add( bSizer871, 1, wx.EXPAND, 5 ) - - - bSizer86.Add( bSizer89, 0, wx.EXPAND, 5 ) - - - bSizer85.Add( bSizer86, 1, wx.EXPAND, 5 ) - - - self.m_panel34.SetSizer( bSizer85 ) - self.m_panel34.Layout() - bSizer85.Fit( self.m_panel34 ) - self.m_notebook7.AddPage( self.m_panel34, _(u"Options"), False ) - - bSizer84.Add( self.m_notebook7, 1, wx.EXPAND | wx.ALL, 5 ) - - self.sql_view = wx.stc.StyledTextCtrl( self.panel_views, wx.ID_ANY, wx.DefaultPosition, wx.Size( -1,200 ), 0) - self.sql_view.SetUseTabs ( True ) - self.sql_view.SetTabWidth ( 4 ) - self.sql_view.SetIndent ( 4 ) - self.sql_view.SetTabIndents( True ) - self.sql_view.SetBackSpaceUnIndents( True ) - self.sql_view.SetViewEOL( False ) - self.sql_view.SetViewWhiteSpace( False ) - self.sql_view.SetMarginWidth( 2, 0 ) - self.sql_view.SetIndentationGuides( True ) - self.sql_view.SetReadOnly( False ) - self.sql_view.SetMarginWidth( 1, 0 ) - self.sql_view.SetMarginType( 0, wx.stc.STC_MARGIN_NUMBER ) - self.sql_view.SetMarginWidth( 0, self.sql_view.TextWidth( wx.stc.STC_STYLE_LINENUMBER, "_99999" ) ) - self.sql_view.MarkerDefine( wx.stc.STC_MARKNUM_FOLDER, wx.stc.STC_MARK_BOXPLUS ) - self.sql_view.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDER, wx.BLACK) - self.sql_view.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDER, wx.WHITE) - self.sql_view.MarkerDefine( wx.stc.STC_MARKNUM_FOLDEROPEN, wx.stc.STC_MARK_BOXMINUS ) - self.sql_view.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDEROPEN, wx.BLACK ) - self.sql_view.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDEROPEN, wx.WHITE ) - self.sql_view.MarkerDefine( wx.stc.STC_MARKNUM_FOLDERSUB, wx.stc.STC_MARK_EMPTY ) - self.sql_view.MarkerDefine( wx.stc.STC_MARKNUM_FOLDEREND, wx.stc.STC_MARK_BOXPLUS ) - self.sql_view.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDEREND, wx.BLACK ) - self.sql_view.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDEREND, wx.WHITE ) - self.sql_view.MarkerDefine( wx.stc.STC_MARKNUM_FOLDEROPENMID, wx.stc.STC_MARK_BOXMINUS ) - self.sql_view.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDEROPENMID, wx.BLACK) - self.sql_view.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDEROPENMID, wx.WHITE) - self.sql_view.MarkerDefine( wx.stc.STC_MARKNUM_FOLDERMIDTAIL, wx.stc.STC_MARK_EMPTY ) - self.sql_view.MarkerDefine( wx.stc.STC_MARKNUM_FOLDERTAIL, wx.stc.STC_MARK_EMPTY ) - self.sql_view.SetSelBackground( True, wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT ) ) - self.sql_view.SetSelForeground( True, wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHTTEXT ) ) - bSizer84.Add( self.sql_view, 1, wx.EXPAND | wx.ALL, 5 ) - - bSizer91 = wx.BoxSizer( wx.HORIZONTAL ) - - self.btn_delete_view = wx.Button( self.panel_views, wx.ID_ANY, _(u"Delete"), wx.DefaultPosition, wx.DefaultSize, 0 ) - bSizer91.Add( self.btn_delete_view, 0, wx.ALL, 5 ) - - self.btn_cancel_view = wx.Button( self.panel_views, wx.ID_ANY, _(u"Cancel"), wx.DefaultPosition, wx.DefaultSize, 0 ) - self.btn_cancel_view.Enable( False ) - - bSizer91.Add( self.btn_cancel_view, 0, wx.ALL, 5 ) - - self.btn_save_view = wx.Button( self.panel_views, wx.ID_ANY, _(u"Save"), wx.DefaultPosition, wx.DefaultSize, 0 ) - self.btn_save_view.Enable( False ) - - bSizer91.Add( self.btn_save_view, 0, wx.ALL, 5 ) - - - bSizer84.Add( bSizer91, 0, wx.EXPAND, 5 ) - - - self.panel_views.SetSizer( bSizer84 ) - self.panel_views.Layout() - bSizer84.Fit( self.panel_views ) - self.MainFrameNotebook.AddPage( self.panel_views, _(u"Views"), False ) - MainFrameNotebookBitmap = wx.Bitmap( u"icons/16x16/view.png", wx.BITMAP_TYPE_ANY ) - if ( MainFrameNotebookBitmap.IsOk() ): - MainFrameNotebookImages.Add( MainFrameNotebookBitmap ) - self.MainFrameNotebook.SetPageImage( MainFrameNotebookIndex, MainFrameNotebookIndex ) - MainFrameNotebookIndex += 1 - - self.panel_triggers = wx.Panel( self.MainFrameNotebook, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) - self.MainFrameNotebook.AddPage( self.panel_triggers, _(u"Triggers"), False ) - MainFrameNotebookBitmap = wx.Bitmap( u"icons/16x16/cog.png", wx.BITMAP_TYPE_ANY ) - if ( MainFrameNotebookBitmap.IsOk() ): - MainFrameNotebookImages.Add( MainFrameNotebookBitmap ) - self.MainFrameNotebook.SetPageImage( MainFrameNotebookIndex, MainFrameNotebookIndex ) - MainFrameNotebookIndex += 1 - - self.panel_records = wx.Panel( self.MainFrameNotebook, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) - bSizer61 = wx.BoxSizer( wx.VERTICAL ) - - bSizer94 = wx.BoxSizer( wx.HORIZONTAL ) - - self.name_database_table = wx.StaticText( self.panel_records, wx.ID_ANY, _(u"Table `%(database_name)s`.`%(table_name)s`: %(total_rows) rows total"), wx.DefaultPosition, wx.DefaultSize, 0 ) - self.name_database_table.Wrap( -1 ) - - bSizer94.Add( self.name_database_table, 0, wx.ALL, 5 ) - - - bSizer61.Add( bSizer94, 0, wx.EXPAND, 5 ) - - bSizer83 = wx.BoxSizer( wx.HORIZONTAL ) - - self.btn_insert_record = wx.Button( self.panel_records, wx.ID_ANY, _(u"Insert record"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) - - self.btn_insert_record.SetBitmap( wx.Bitmap( u"icons/16x16/add.png", wx.BITMAP_TYPE_ANY ) ) - bSizer83.Add( self.btn_insert_record, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) - - self.btn_duplicate_record = wx.Button( self.panel_records, wx.ID_ANY, _(u"Duplicate record"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) - - self.btn_duplicate_record.SetBitmap( wx.Bitmap( u"icons/16x16/add.png", wx.BITMAP_TYPE_ANY ) ) - self.btn_duplicate_record.Enable( False ) - - bSizer83.Add( self.btn_duplicate_record, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) - - self.btn_delete_record = wx.Button( self.panel_records, wx.ID_ANY, _(u"Delete record"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) - - self.btn_delete_record.SetBitmap( wx.Bitmap( u"icons/16x16/delete.png", wx.BITMAP_TYPE_ANY ) ) - self.btn_delete_record.Enable( False ) - - bSizer83.Add( self.btn_delete_record, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) - - self.m_staticline3 = wx.StaticLine( self.panel_records, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_VERTICAL ) - bSizer83.Add( self.m_staticline3, 0, wx.EXPAND | wx.ALL, 5 ) - - self.chb_auto_apply = wx.CheckBox( self.panel_records, wx.ID_ANY, _(u"Apply changes automatically"), wx.DefaultPosition, wx.DefaultSize, 0 ) - self.chb_auto_apply.SetValue(True) - self.chb_auto_apply.SetToolTip( _(u"If enabled, table edits are applied immediately without pressing Apply or Cancel") ) - self.chb_auto_apply.SetHelpText( _(u"If enabled, table edits are applied immediately without pressing Apply or Cancel") ) - - bSizer83.Add( self.chb_auto_apply, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) - - self.btn_cancel_record = wx.Button( self.panel_records, wx.ID_ANY, _(u"Cancel"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) - - self.btn_cancel_record.SetBitmap( wx.Bitmap( u"icons/16x16/cancel.png", wx.BITMAP_TYPE_ANY ) ) - self.btn_cancel_record.Enable( False ) - - bSizer83.Add( self.btn_cancel_record, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) - - self.btn_apply_record = wx.Button( self.panel_records, wx.ID_ANY, _(u"Apply"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) - - self.btn_apply_record.SetBitmap( wx.Bitmap( u"icons/16x16/disk.png", wx.BITMAP_TYPE_ANY ) ) - self.btn_apply_record.Enable( False ) - - bSizer83.Add( self.btn_apply_record, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) - - - bSizer83.Add( ( 0, 0), 1, wx.EXPAND, 5 ) - - self.m_button40 = wx.Button( self.panel_records, wx.ID_ANY, _(u"Next"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) - - self.m_button40.SetBitmap( wx.Bitmap( u"icons/16x16/resultset_next.png", wx.BITMAP_TYPE_ANY ) ) - bSizer83.Add( self.m_button40, 0, wx.ALL, 5 ) - - - bSizer61.Add( bSizer83, 0, wx.EXPAND, 5 ) - - self.m_collapsiblePane1 = wx.CollapsiblePane( self.panel_records, wx.ID_ANY, _(u"Filters"), wx.DefaultPosition, wx.DefaultSize, wx.CP_DEFAULT_STYLE|wx.CP_NO_TLW_RESIZE|wx.FULL_REPAINT_ON_RESIZE ) - self.m_collapsiblePane1.Collapse( False ) - - bSizer831 = wx.BoxSizer( wx.VERTICAL ) - - self.sql_query_filters = wx.stc.StyledTextCtrl( self.m_collapsiblePane1.GetPane(), wx.ID_ANY, wx.DefaultPosition, wx.Size( -1,100 ), 0) - self.sql_query_filters.SetUseTabs ( True ) - self.sql_query_filters.SetTabWidth ( 4 ) - self.sql_query_filters.SetIndent ( 4 ) - self.sql_query_filters.SetTabIndents( True ) - self.sql_query_filters.SetBackSpaceUnIndents( True ) - self.sql_query_filters.SetViewEOL( False ) - self.sql_query_filters.SetViewWhiteSpace( False ) - self.sql_query_filters.SetMarginWidth( 2, 0 ) - self.sql_query_filters.SetIndentationGuides( True ) - self.sql_query_filters.SetReadOnly( False ) - self.sql_query_filters.SetMarginWidth( 1, 0 ) - self.sql_query_filters.SetMarginWidth ( 0, 0 ) - self.sql_query_filters.MarkerDefine( wx.stc.STC_MARKNUM_FOLDER, wx.stc.STC_MARK_BOXPLUS ) - self.sql_query_filters.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDER, wx.BLACK) - self.sql_query_filters.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDER, wx.WHITE) - self.sql_query_filters.MarkerDefine( wx.stc.STC_MARKNUM_FOLDEROPEN, wx.stc.STC_MARK_BOXMINUS ) - self.sql_query_filters.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDEROPEN, wx.BLACK ) - self.sql_query_filters.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDEROPEN, wx.WHITE ) - self.sql_query_filters.MarkerDefine( wx.stc.STC_MARKNUM_FOLDERSUB, wx.stc.STC_MARK_EMPTY ) - self.sql_query_filters.MarkerDefine( wx.stc.STC_MARKNUM_FOLDEREND, wx.stc.STC_MARK_BOXPLUS ) - self.sql_query_filters.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDEREND, wx.BLACK ) - self.sql_query_filters.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDEREND, wx.WHITE ) - self.sql_query_filters.MarkerDefine( wx.stc.STC_MARKNUM_FOLDEROPENMID, wx.stc.STC_MARK_BOXMINUS ) - self.sql_query_filters.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDEROPENMID, wx.BLACK) - self.sql_query_filters.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDEROPENMID, wx.WHITE) - self.sql_query_filters.MarkerDefine( wx.stc.STC_MARKNUM_FOLDERMIDTAIL, wx.stc.STC_MARK_EMPTY ) - self.sql_query_filters.MarkerDefine( wx.stc.STC_MARKNUM_FOLDERTAIL, wx.stc.STC_MARK_EMPTY ) - self.sql_query_filters.SetSelBackground( True, wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT ) ) - self.sql_query_filters.SetSelForeground( True, wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHTTEXT ) ) - bSizer831.Add( self.sql_query_filters, 1, wx.EXPAND | wx.ALL, 5 ) - - self.m_button41 = wx.Button( self.m_collapsiblePane1.GetPane(), wx.ID_ANY, _(u"Apply"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) - - self.m_button41.SetBitmap( wx.Bitmap( u"icons/16x16/tick.png", wx.BITMAP_TYPE_ANY ) ) - self.m_button41.SetHelpText( _(u"CTRL+ENTER") ) - - bSizer831.Add( self.m_button41, 0, wx.ALL, 5 ) - - - self.m_collapsiblePane1.GetPane().SetSizer( bSizer831 ) - self.m_collapsiblePane1.GetPane().Layout() - bSizer831.Fit( self.m_collapsiblePane1.GetPane() ) - bSizer61.Add( self.m_collapsiblePane1, 0, wx.ALL|wx.EXPAND, 5 ) - - self.list_ctrl_table_records = TableRecordsDataViewCtrl( self.panel_records, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.dataview.DV_MULTIPLE ) - self.list_ctrl_table_records.SetFont( wx.Font( 10, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, wx.EmptyString ) ) - - bSizer61.Add( self.list_ctrl_table_records, 1, wx.ALL|wx.EXPAND, 5 ) - - - self.panel_records.SetSizer( bSizer61 ) - self.panel_records.Layout() - bSizer61.Fit( self.panel_records ) - self.m_menu10 = wx.Menu() - self.m_menuItem13 = wx.MenuItem( self.m_menu10, wx.ID_ANY, _(u"Insert row")+ u"\t" + u"Ins", wx.EmptyString, wx.ITEM_NORMAL ) - self.m_menu10.Append( self.m_menuItem13 ) - - self.m_menuItem14 = wx.MenuItem( self.m_menu10, wx.ID_ANY, _(u"MyMenuItem"), wx.EmptyString, wx.ITEM_NORMAL ) - self.m_menu10.Append( self.m_menuItem14 ) - - self.panel_records.Bind( wx.EVT_RIGHT_DOWN, self.panel_recordsOnContextMenu ) - - self.MainFrameNotebook.AddPage( self.panel_records, _(u"Data"), True ) - MainFrameNotebookBitmap = wx.Bitmap( u"icons/16x16/text_columns.png", wx.BITMAP_TYPE_ANY ) - if ( MainFrameNotebookBitmap.IsOk() ): - MainFrameNotebookImages.Add( MainFrameNotebookBitmap ) - self.MainFrameNotebook.SetPageImage( MainFrameNotebookIndex, MainFrameNotebookIndex ) - MainFrameNotebookIndex += 1 - - self.panel_query = wx.Panel( self.MainFrameNotebook, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) - self.panel_query.Enable( False ) - - bSizer26 = wx.BoxSizer( wx.VERTICAL ) - - self.sql_query = wx.stc.StyledTextCtrl( self.panel_query, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0) - self.sql_query.SetUseTabs ( True ) - self.sql_query.SetTabWidth ( 4 ) - self.sql_query.SetIndent ( 4 ) - self.sql_query.SetTabIndents( True ) - self.sql_query.SetBackSpaceUnIndents( True ) - self.sql_query.SetViewEOL( False ) - self.sql_query.SetViewWhiteSpace( False ) - self.sql_query.SetMarginWidth( 2, 0 ) - self.sql_query.SetIndentationGuides( True ) - self.sql_query.SetReadOnly( False ) - self.sql_query.SetMarginWidth( 1, 0 ) - self.sql_query.SetMarginType( 0, wx.stc.STC_MARGIN_NUMBER ) - self.sql_query.SetMarginWidth( 0, self.sql_query.TextWidth( wx.stc.STC_STYLE_LINENUMBER, "_99999" ) ) - self.sql_query.MarkerDefine( wx.stc.STC_MARKNUM_FOLDER, wx.stc.STC_MARK_BOXPLUS ) - self.sql_query.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDER, wx.BLACK) - self.sql_query.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDER, wx.WHITE) - self.sql_query.MarkerDefine( wx.stc.STC_MARKNUM_FOLDEROPEN, wx.stc.STC_MARK_BOXMINUS ) - self.sql_query.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDEROPEN, wx.BLACK ) - self.sql_query.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDEROPEN, wx.WHITE ) - self.sql_query.MarkerDefine( wx.stc.STC_MARKNUM_FOLDERSUB, wx.stc.STC_MARK_EMPTY ) - self.sql_query.MarkerDefine( wx.stc.STC_MARKNUM_FOLDEREND, wx.stc.STC_MARK_BOXPLUS ) - self.sql_query.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDEREND, wx.BLACK ) - self.sql_query.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDEREND, wx.WHITE ) - self.sql_query.MarkerDefine( wx.stc.STC_MARKNUM_FOLDEROPENMID, wx.stc.STC_MARK_BOXMINUS ) - self.sql_query.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDEROPENMID, wx.BLACK) - self.sql_query.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDEROPENMID, wx.WHITE) - self.sql_query.MarkerDefine( wx.stc.STC_MARKNUM_FOLDERMIDTAIL, wx.stc.STC_MARK_EMPTY ) - self.sql_query.MarkerDefine( wx.stc.STC_MARKNUM_FOLDERTAIL, wx.stc.STC_MARK_EMPTY ) - self.sql_query.SetSelBackground( True, wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT ) ) - self.sql_query.SetSelForeground( True, wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHTTEXT ) ) - bSizer26.Add( self.sql_query, 1, wx.EXPAND | wx.ALL, 5 ) - - - self.panel_query.SetSizer( bSizer26 ) - self.panel_query.Layout() - bSizer26.Fit( self.panel_query ) - self.MainFrameNotebook.AddPage( self.panel_query, _(u"Query"), False ) - MainFrameNotebookBitmap = wx.Bitmap( u"icons/16x16/arrow_right.png", wx.BITMAP_TYPE_ANY ) - if ( MainFrameNotebookBitmap.IsOk() ): - MainFrameNotebookImages.Add( MainFrameNotebookBitmap ) - self.MainFrameNotebook.SetPageImage( MainFrameNotebookIndex, MainFrameNotebookIndex ) - MainFrameNotebookIndex += 1 - - self.QueryPanelTpl = wx.Panel( self.MainFrameNotebook, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) - self.QueryPanelTpl.Hide() - - bSizer263 = wx.BoxSizer( wx.VERTICAL ) - - self.m_textCtrl101 = wx.TextCtrl( self.QueryPanelTpl, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_MULTILINE|wx.TE_RICH|wx.TE_RICH2 ) - bSizer263.Add( self.m_textCtrl101, 1, wx.ALL|wx.EXPAND, 5 ) - - bSizer49 = wx.BoxSizer( wx.HORIZONTAL ) - - - bSizer49.Add( ( 0, 0), 1, wx.EXPAND, 5 ) - - self.m_button17 = wx.Button( self.QueryPanelTpl, wx.ID_ANY, _(u"Close"), wx.DefaultPosition, wx.DefaultSize, 0 ) - bSizer49.Add( self.m_button17, 0, wx.ALL, 5 ) - - self.m_button121 = wx.Button( self.QueryPanelTpl, wx.ID_ANY, _(u"New"), wx.DefaultPosition, wx.DefaultSize, 0 ) - bSizer49.Add( self.m_button121, 0, wx.ALL, 5 ) - - - bSizer263.Add( bSizer49, 0, wx.EXPAND, 5 ) - - - self.QueryPanelTpl.SetSizer( bSizer263 ) - self.QueryPanelTpl.Layout() - bSizer263.Fit( self.QueryPanelTpl ) - self.MainFrameNotebook.AddPage( self.QueryPanelTpl, _(u"Query #2"), False ) - - bSizer25.Add( self.MainFrameNotebook, 1, wx.ALL|wx.EXPAND, 5 ) - - - self.m_panel15.SetSizer( bSizer25 ) - self.m_panel15.Layout() - bSizer25.Fit( self.m_panel15 ) - self.m_menu3 = wx.Menu() - self.m_menuItem3 = wx.MenuItem( self.m_menu3, wx.ID_ANY, _(u"MyMenuItem"), wx.EmptyString, wx.ITEM_NORMAL ) - self.m_menu3.Append( self.m_menuItem3 ) - - self.m_panel15.Bind( wx.EVT_RIGHT_DOWN, self.m_panel15OnContextMenu ) - - self.m_splitter4.SplitVertically( self.m_panel14, self.m_panel15, 320 ) - bSizer72.Add( self.m_splitter4, 1, wx.EXPAND, 5 ) - - - self.m_panel22.SetSizer( bSizer72 ) - self.m_panel22.Layout() - bSizer72.Fit( self.m_panel22 ) - self.LogSQLPanel = wx.Panel( self.m_splitter51, wx.ID_ANY, wx.DefaultPosition, wx.Size( -1,-1 ), wx.TAB_TRAVERSAL ) - sizer_log_sql = wx.BoxSizer( wx.VERTICAL ) - - self.sql_query_logs = wx.stc.StyledTextCtrl( self.LogSQLPanel, wx.ID_ANY, wx.DefaultPosition, wx.Size( -1,200 ), 0) - self.sql_query_logs.SetUseTabs ( True ) - self.sql_query_logs.SetTabWidth ( 4 ) - self.sql_query_logs.SetIndent ( 4 ) - self.sql_query_logs.SetTabIndents( True ) - self.sql_query_logs.SetBackSpaceUnIndents( True ) - self.sql_query_logs.SetViewEOL( False ) - self.sql_query_logs.SetViewWhiteSpace( False ) - self.sql_query_logs.SetMarginWidth( 2, 0 ) - self.sql_query_logs.SetIndentationGuides( True ) - self.sql_query_logs.SetReadOnly( False ) - self.sql_query_logs.SetMarginWidth( 1, 0 ) - self.sql_query_logs.SetMarginType( 0, wx.stc.STC_MARGIN_NUMBER ) - self.sql_query_logs.SetMarginWidth( 0, self.sql_query_logs.TextWidth( wx.stc.STC_STYLE_LINENUMBER, "_99999" ) ) - self.sql_query_logs.MarkerDefine( wx.stc.STC_MARKNUM_FOLDER, wx.stc.STC_MARK_BOXPLUS ) - self.sql_query_logs.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDER, wx.BLACK) - self.sql_query_logs.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDER, wx.WHITE) - self.sql_query_logs.MarkerDefine( wx.stc.STC_MARKNUM_FOLDEROPEN, wx.stc.STC_MARK_BOXMINUS ) - self.sql_query_logs.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDEROPEN, wx.BLACK ) - self.sql_query_logs.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDEROPEN, wx.WHITE ) - self.sql_query_logs.MarkerDefine( wx.stc.STC_MARKNUM_FOLDERSUB, wx.stc.STC_MARK_EMPTY ) - self.sql_query_logs.MarkerDefine( wx.stc.STC_MARKNUM_FOLDEREND, wx.stc.STC_MARK_BOXPLUS ) - self.sql_query_logs.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDEREND, wx.BLACK ) - self.sql_query_logs.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDEREND, wx.WHITE ) - self.sql_query_logs.MarkerDefine( wx.stc.STC_MARKNUM_FOLDEROPENMID, wx.stc.STC_MARK_BOXMINUS ) - self.sql_query_logs.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDEROPENMID, wx.BLACK) - self.sql_query_logs.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDEROPENMID, wx.WHITE) - self.sql_query_logs.MarkerDefine( wx.stc.STC_MARKNUM_FOLDERMIDTAIL, wx.stc.STC_MARK_EMPTY ) - self.sql_query_logs.MarkerDefine( wx.stc.STC_MARKNUM_FOLDERTAIL, wx.stc.STC_MARK_EMPTY ) - self.sql_query_logs.SetSelBackground( True, wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT ) ) - self.sql_query_logs.SetSelForeground( True, wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHTTEXT ) ) - sizer_log_sql.Add( self.sql_query_logs, 1, wx.EXPAND | wx.ALL, 5 ) - - - self.LogSQLPanel.SetSizer( sizer_log_sql ) - self.LogSQLPanel.Layout() - sizer_log_sql.Fit( self.LogSQLPanel ) - self.m_splitter51.SplitHorizontally( self.m_panel22, self.LogSQLPanel, -150 ) - bSizer21.Add( self.m_splitter51, 1, wx.EXPAND, 5 ) - - - self.m_panel13.SetSizer( bSizer21 ) - self.m_panel13.Layout() - bSizer21.Fit( self.m_panel13 ) - bSizer19.Add( self.m_panel13, 1, wx.EXPAND | wx.ALL, 0 ) - - - self.SetSizer( bSizer19 ) - self.Layout() - self.status_bar = self.CreateStatusBar( 4, wx.STB_SIZEGRIP, wx.ID_ANY ) - - self.Centre( wx.BOTH ) - - # Connect Events - self.Bind( wx.EVT_CLOSE, self.do_close ) - self.Bind( wx.EVT_MENU, self.on_menu_about, id = self.m_menuItem15.GetId() ) - self.Bind( wx.EVT_TOOL, self.do_open_connection_manager, id = self.m_tool5.GetId() ) - self.Bind( wx.EVT_TOOL, self.do_disconnect, id = self.m_tool4.GetId() ) - self.MainFrameNotebook.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGED, self.on_page_chaged ) - self.btn_insert_table.Bind( wx.EVT_BUTTON, self.on_insert_table ) - self.btn_clone_table.Bind( wx.EVT_BUTTON, self.on_clone_table ) - self.btn_delete_table.Bind( wx.EVT_BUTTON, self.on_delete_table ) - self.btn_delete_index.Bind( wx.EVT_BUTTON, self.on_delete_index ) - self.btn_clear_index.Bind( wx.EVT_BUTTON, self.on_clear_index ) - self.btn_insert_foreign_key.Bind( wx.EVT_BUTTON, self.on_insert_foreign_key ) - self.btn_delete_foreign_key.Bind( wx.EVT_BUTTON, self.on_delete_foreign_key ) - self.btn_clear_foreign_key.Bind( wx.EVT_BUTTON, self.on_clear_foreign_key ) - self.btn_insert_check.Bind( wx.EVT_BUTTON, self.on_insert_foreign_key ) - self.btn_delete_check.Bind( wx.EVT_BUTTON, self.on_delete_foreign_key ) - self.btn_clear_check.Bind( wx.EVT_BUTTON, self.on_clear_foreign_key ) - self.btn_insert_column.Bind( wx.EVT_BUTTON, self.on_insert_column ) - self.btn_delete_column.Bind( wx.EVT_BUTTON, self.on_delete_column ) - self.btn_move_up_column.Bind( wx.EVT_BUTTON, self.on_move_up_column ) - self.btn_move_down_column.Bind( wx.EVT_BUTTON, self.on_move_down_column ) - self.btn_delete_table.Bind( wx.EVT_BUTTON, self.on_delete_table ) - self.btn_cancel_table.Bind( wx.EVT_BUTTON, self.on_cancel_table ) - self.btn_apply_table.Bind( wx.EVT_BUTTON, self.do_apply_table ) - self.btn_insert_record.Bind( wx.EVT_BUTTON, self.on_insert_record ) - self.btn_duplicate_record.Bind( wx.EVT_BUTTON, self.on_duplicate_record ) - self.btn_delete_record.Bind( wx.EVT_BUTTON, self.on_delete_record ) - self.chb_auto_apply.Bind( wx.EVT_CHECKBOX, self.on_auto_apply ) - self.m_button40.Bind( wx.EVT_BUTTON, self.on_next_records ) - self.m_collapsiblePane1.Bind( wx.EVT_COLLAPSIBLEPANE_CHANGED, self.on_collapsible_pane_changed ) - self.m_button41.Bind( wx.EVT_BUTTON, self.on_apply_filters ) - - def __del__( self ): - pass - - - # Virtual event handlers, override them in your derived class - def do_close( self, event ): - event.Skip() - - def on_menu_about( self, event ): - event.Skip() - - def do_open_connection_manager( self, event ): - event.Skip() - - def do_disconnect( self, event ): - event.Skip() - - def on_page_chaged( self, event ): - event.Skip() - - def on_insert_table( self, event ): - event.Skip() - - def on_clone_table( self, event ): - event.Skip() - - def on_delete_table( self, event ): - event.Skip() - - def on_delete_index( self, event ): - event.Skip() - - def on_clear_index( self, event ): - event.Skip() - - def on_insert_foreign_key( self, event ): - event.Skip() - - def on_delete_foreign_key( self, event ): - event.Skip() - - def on_clear_foreign_key( self, event ): - event.Skip() - - - - - def on_insert_column( self, event ): - event.Skip() - - def on_delete_column( self, event ): - event.Skip() - - def on_move_up_column( self, event ): - event.Skip() - - def on_move_down_column( self, event ): - event.Skip() - - - def on_cancel_table( self, event ): - event.Skip() - - def do_apply_table( self, event ): - event.Skip() - - def on_insert_record( self, event ): - event.Skip() - - def on_duplicate_record( self, event ): - event.Skip() - - def on_delete_record( self, event ): - event.Skip() - - def on_auto_apply( self, event ): - event.Skip() - - def on_next_records( self, event ): - event.Skip() - - def on_collapsible_pane_changed( self, event ): - event.Skip() - - def on_apply_filters( self, event ): - event.Skip() - - def m_splitter51OnIdle( self, event ): - self.m_splitter51.SetSashPosition( -150 ) - self.m_splitter51.Unbind( wx.EVT_IDLE ) - - def m_splitter4OnIdle( self, event ): - self.m_splitter4.SetSashPosition( 320 ) - self.m_splitter4.Unbind( wx.EVT_IDLE ) - - def m_panel14OnContextMenu( self, event ): - self.m_panel14.PopupMenu( self.m_menu5, event.GetPosition() ) - - def panel_databaseOnContextMenu( self, event ): - self.panel_database.PopupMenu( self.m_menu15, event.GetPosition() ) - - def m_splitter41OnIdle( self, event ): - self.m_splitter41.SetSashPosition( 200 ) - self.m_splitter41.Unbind( wx.EVT_IDLE ) - - def panel_table_columnsOnContextMenu( self, event ): - self.panel_table_columns.PopupMenu( self.menu_table_columns, event.GetPosition() ) - - def panel_recordsOnContextMenu( self, event ): - self.panel_records.PopupMenu( self.m_menu10, event.GetPosition() ) - - def m_panel15OnContextMenu( self, event ): - self.m_panel15.PopupMenu( self.m_menu3, event.GetPosition() ) - - -########################################################################### -## Class Trash -########################################################################### - -class Trash ( wx.Panel ): - - def __init__( self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition, size = wx.Size( 500,300 ), style = wx.TAB_TRAVERSAL, name = wx.EmptyString ): - wx.Panel.__init__ ( self, parent, id = id, pos = pos, size = size, style = style, name = name ) - - bSizer90 = wx.BoxSizer( wx.VERTICAL ) - - self.m_textCtrl221 = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) - bSizer90.Add( self.m_textCtrl221, 1, wx.ALL|wx.EXPAND, 5 ) - - bSizer93 = wx.BoxSizer( wx.VERTICAL ) - - self.tree_ctrl_explorer____ = wx.dataview.TreeListCtrl( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.dataview.TL_DEFAULT_STYLE ) - self.tree_ctrl_explorer____.AppendColumn( _(u"Column5"), wx.COL_WIDTH_DEFAULT, wx.ALIGN_LEFT, wx.COL_RESIZABLE ) - - bSizer93.Add( self.tree_ctrl_explorer____, 1, wx.EXPAND | wx.ALL, 5 ) - - - bSizer90.Add( bSizer93, 1, wx.EXPAND, 5 ) - - self.m_collapsiblePane2 = wx.CollapsiblePane( self, wx.ID_ANY, _(u"collapsible"), wx.DefaultPosition, wx.DefaultSize, wx.CP_DEFAULT_STYLE ) - self.m_collapsiblePane2.Collapse( False ) - - bSizer92 = wx.BoxSizer( wx.VERTICAL ) - - - self.m_collapsiblePane2.GetPane().SetSizer( bSizer92 ) - self.m_collapsiblePane2.GetPane().Layout() - bSizer92.Fit( self.m_collapsiblePane2.GetPane() ) - bSizer90.Add( self.m_collapsiblePane2, 1, wx.EXPAND | wx.ALL, 5 ) - - self.tree_ctrl_sessions = wx.TreeCtrl( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TR_DEFAULT_STYLE|wx.TR_FULL_ROW_HIGHLIGHT|wx.TR_HAS_BUTTONS|wx.TR_HIDE_ROOT|wx.TR_TWIST_BUTTONS ) - self.m_menu12 = wx.Menu() - self.tree_ctrl_sessions.Bind( wx.EVT_RIGHT_DOWN, self.tree_ctrl_sessionsOnContextMenu ) - - bSizer90.Add( self.tree_ctrl_sessions, 1, wx.ALL|wx.EXPAND, 5 ) - - self.m_treeListCtrl3 = wx.dataview.TreeListCtrl( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.dataview.TL_DEFAULT_STYLE ) - - bSizer90.Add( self.m_treeListCtrl3, 1, wx.EXPAND | wx.ALL, 5 ) - - self.tree_ctrl_sessions1 = wx.dataview.TreeListCtrl( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.dataview.TL_DEFAULT_STYLE ) - self.tree_ctrl_sessions1.AppendColumn( _(u"Column3"), wx.COL_WIDTH_DEFAULT, wx.ALIGN_LEFT, wx.COL_RESIZABLE ) - self.tree_ctrl_sessions1.AppendColumn( _(u"Column4"), wx.COL_WIDTH_DEFAULT, wx.ALIGN_LEFT, wx.COL_RESIZABLE ) - - bSizer90.Add( self.tree_ctrl_sessions1, 1, wx.EXPAND | wx.ALL, 5 ) - - self.table_collationdd = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) - bSizer90.Add( self.table_collationdd, 1, wx.ALL|wx.EXPAND, 5 ) - - self.m_textCtrl21 = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_MULTILINE ) - bSizer90.Add( self.m_textCtrl21, 1, wx.ALL|wx.EXPAND, 5 ) - - bSizer51 = wx.BoxSizer( wx.VERTICAL ) - - self.panel_credentials = wx.Panel( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) - bSizer48 = wx.BoxSizer( wx.VERTICAL ) - - self.m_notebook8 = wx.Notebook( self.panel_credentials, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0 ) - - bSizer48.Add( self.m_notebook8, 1, wx.EXPAND | wx.ALL, 5 ) - - - self.panel_credentials.SetSizer( bSizer48 ) - self.panel_credentials.Layout() - bSizer48.Fit( self.panel_credentials ) - bSizer51.Add( self.panel_credentials, 0, wx.EXPAND | wx.ALL, 0 ) - - self.panel_source = wx.Panel( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) - self.panel_source.Hide() - - bSizer52 = wx.BoxSizer( wx.VERTICAL ) - - bSizer1212 = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticText212 = wx.StaticText( self.panel_source, wx.ID_ANY, _(u"Filename"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) - self.m_staticText212.Wrap( -1 ) - - bSizer1212.Add( self.m_staticText212, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) - - self.filename = wx.FilePickerCtrl( self.panel_source, wx.ID_ANY, wx.EmptyString, _(u"Select a file"), _(u"Database (*.db;*.db3;*.sdb;*.s3db;*.sqlite;*.sqlite3)|*.db;*.db3;*.sdb;*.s3db;*.sqlite;*.sqlite3"), wx.DefaultPosition, wx.DefaultSize, wx.FLP_CHANGE_DIR|wx.FLP_USE_TEXTCTRL ) - bSizer1212.Add( self.filename, 1, wx.ALL, 5 ) - - - bSizer52.Add( bSizer1212, 0, wx.EXPAND, 0 ) - - - self.panel_source.SetSizer( bSizer52 ) - self.panel_source.Layout() - bSizer52.Fit( self.panel_source ) - bSizer51.Add( self.panel_source, 0, wx.EXPAND | wx.ALL, 0 ) - - - bSizer90.Add( bSizer51, 0, wx.EXPAND, 0 ) - - self.m_panel35 = wx.Panel( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) - bSizer96 = wx.BoxSizer( wx.VERTICAL ) - - - self.m_panel35.SetSizer( bSizer96 ) - self.m_panel35.Layout() - bSizer96.Fit( self.m_panel35 ) - bSizer90.Add( self.m_panel35, 1, wx.EXPAND | wx.ALL, 5 ) - - self.ssh_tunnel_port = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) - bSizer90.Add( self.ssh_tunnel_port, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) - - self.ssh_tunnel_local_port = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) - bSizer90.Add( self.ssh_tunnel_local_port, 1, wx.ALIGN_CENTER|wx.ALL, 5 ) - - bSizer12211 = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticText2211 = wx.StaticText( self, wx.ID_ANY, _(u"Port"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) - self.m_staticText2211.Wrap( -1 ) - - bSizer12211.Add( self.m_staticText2211, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) - - - bSizer90.Add( bSizer12211, 0, wx.EXPAND, 5 ) - - self.tree_ctrl_sessions2 = wx.TreeCtrl( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TR_DEFAULT_STYLE ) - self.tree_ctrl_sessions2.Hide() - - bSizer90.Add( self.tree_ctrl_sessions2, 1, wx.ALL|wx.EXPAND, 5 ) - - self.tree_ctrl_sessions_bkp3 = wx.dataview.TreeListCtrl( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.dataview.TL_DEFAULT_STYLE|wx.dataview.TL_SINGLE ) - self.tree_ctrl_sessions_bkp3.Hide() - - self.tree_ctrl_sessions_bkp3.AppendColumn( _(u"Name"), wx.COL_WIDTH_DEFAULT, wx.ALIGN_LEFT, wx.COL_RESIZABLE ) - self.tree_ctrl_sessions_bkp3.AppendColumn( _(u"Usage"), wx.COL_WIDTH_DEFAULT, wx.ALIGN_LEFT, wx.COL_RESIZABLE ) - - bSizer90.Add( self.tree_ctrl_sessions_bkp3, 1, wx.EXPAND | wx.ALL, 5 ) - - self.tree_ctrl_sessions_bkp = wx.dataview.DataViewCtrl( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.dataview.DV_SINGLE ) - self.tree_ctrl_sessions_bkp.Hide() - - self.m_dataViewColumn1 = self.tree_ctrl_sessions_bkp.AppendIconTextColumn( _(u"Database"), 0, wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) - self.m_dataViewColumn3 = self.tree_ctrl_sessions_bkp.AppendProgressColumn( _(u"Size"), 1, wx.dataview.DATAVIEW_CELL_INERT, 50, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) - bSizer90.Add( self.tree_ctrl_sessions_bkp, 1, wx.ALL|wx.EXPAND, 5 ) - - self.rows_database_table = wx.StaticText( self, wx.ID_ANY, _(u"%(total_rows)s"), wx.DefaultPosition, wx.DefaultSize, 0 ) - self.rows_database_table.Wrap( -1 ) - - bSizer90.Add( self.rows_database_table, 0, wx.ALL, 5 ) - - self.m_staticText44 = wx.StaticText( self, wx.ID_ANY, _(u"rows total"), wx.DefaultPosition, wx.DefaultSize, 0 ) - self.m_staticText44.Wrap( -1 ) - - bSizer90.Add( self.m_staticText44, 0, wx.ALL, 5 ) - - self.____list_ctrl_database_tables = wx.dataview.DataViewCtrl( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0 ) - self.m_dataViewColumn5 = self.____list_ctrl_database_tables.AppendTextColumn( _(u"Name"), 0, wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) - self.m_dataViewColumn6 = self.____list_ctrl_database_tables.AppendTextColumn( _(u"Name"), 0, wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) - self.m_dataViewColumn7 = self.____list_ctrl_database_tables.AppendTextColumn( _(u"Name"), 0, wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) - self.m_dataViewColumn8 = self.____list_ctrl_database_tables.AppendTextColumn( _(u"Name"), 0, wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) - self.m_dataViewColumn9 = self.____list_ctrl_database_tables.AppendTextColumn( _(u"Name"), 0, wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) - self.m_dataViewColumn10 = self.____list_ctrl_database_tables.AppendTextColumn( _(u"Name"), 0, wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) - self.m_dataViewColumn11 = self.____list_ctrl_database_tables.AppendTextColumn( _(u"Name"), 0, wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) - self.m_dataViewColumn20 = self.____list_ctrl_database_tables.AppendTextColumn( _(u"Name"), 0, wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) - self.m_dataViewColumn21 = self.____list_ctrl_database_tables.AppendTextColumn( _(u"Name"), 0, wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) - bSizer90.Add( self.____list_ctrl_database_tables, 0, wx.ALL, 5 ) - - self.___list_ctrl_database_tables = wx.dataview.DataViewListCtrl( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0 ) - self.m_dataViewListColumn6 = self.___list_ctrl_database_tables.AppendTextColumn( _(u"Name"), wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) - self.m_dataViewListColumn7 = self.___list_ctrl_database_tables.AppendTextColumn( _(u"Lines"), wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) - self.m_dataViewListColumn8 = self.___list_ctrl_database_tables.AppendTextColumn( _(u"Size"), wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) - self.m_dataViewListColumn9 = self.___list_ctrl_database_tables.AppendTextColumn( _(u"Created at"), wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) - self.m_dataViewListColumn10 = self.___list_ctrl_database_tables.AppendTextColumn( _(u"Updated at"), wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) - self.m_dataViewListColumn11 = self.___list_ctrl_database_tables.AppendTextColumn( _(u"Engine"), wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) - self.m_dataViewListColumn12 = self.___list_ctrl_database_tables.AppendTextColumn( _(u"Comments"), wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) - bSizer90.Add( self.___list_ctrl_database_tables, 1, wx.ALL|wx.EXPAND, 5 ) - - self.m_gauge1 = wx.Gauge( self, wx.ID_ANY, 100, wx.DefaultPosition, wx.DefaultSize, 0 ) - self.m_gauge1.SetValue( 0 ) - bSizer90.Add( self.m_gauge1, 0, wx.ALL|wx.EXPAND, 5 ) - - - bSizer90.Add( ( 150, 0), 0, wx.EXPAND, 5 ) - - - bSizer90.Add( ( 0, 0), 1, wx.EXPAND, 5 ) - - self.tree_ctrl_explorer__ = wx.dataview.DataViewCtrl( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0 ) - self.tree_ctrl_explorer__.Hide() - - bSizer90.Add( self.tree_ctrl_explorer__, 1, wx.ALL|wx.EXPAND, 5 ) - - self.m_vlistBox1 = wx.VListBox( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0 ) - bSizer90.Add( self.m_vlistBox1, 0, wx.ALL, 5 ) - - m_listBox1Choices = [] - self.m_listBox1 = wx.ListBox( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, m_listBox1Choices, 0 ) - bSizer90.Add( self.m_listBox1, 0, wx.ALL, 5 ) - - self.m_textCtrl10 = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_MULTILINE|wx.TE_RICH|wx.TE_RICH2 ) - bSizer90.Add( self.m_textCtrl10, 1, wx.ALL|wx.EXPAND, 5 ) - - self.m_button12 = wx.Button( self, wx.ID_ANY, _(u"New"), wx.DefaultPosition, wx.DefaultSize, 0 ) - bSizer90.Add( self.m_button12, 0, wx.ALIGN_RIGHT|wx.ALL, 5 ) - - - self.SetSizer( bSizer90 ) - self.Layout() - self.m_menu11 = wx.Menu() - self.Bind( wx.EVT_RIGHT_DOWN, self.TrashOnContextMenu ) - - - # Connect Events - self.tree_ctrl_sessions.Bind( wx.EVT_TREE_ITEM_RIGHT_CLICK, self.show_tree_ctrl_menu ) - - def __del__( self ): - pass - - - # Virtual event handlers, override them in your derived class - def show_tree_ctrl_menu( self, event ): - event.Skip() - - def tree_ctrl_sessionsOnContextMenu( self, event ): - self.tree_ctrl_sessions.PopupMenu( self.m_menu12, event.GetPosition() ) - - def TrashOnContextMenu( self, event ): - self.PopupMenu( self.m_menu11, event.GetPosition() ) - - -########################################################################### -## Class MyPanel1 -########################################################################### - -class MyPanel1 ( wx.Panel ): - - def __init__( self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition, size = wx.Size( 500,300 ), style = wx.TAB_TRAVERSAL, name = wx.EmptyString ): - wx.Panel.__init__ ( self, parent, id = id, pos = pos, size = size, style = style, name = name ) - - - def __del__( self ): - pass - - -########################################################################### -## Class EditColumnView -########################################################################### - -class EditColumnView ( wx.Dialog ): - - def __init__( self, parent ): - wx.Dialog.__init__ ( self, parent, id = wx.ID_ANY, title = _(u"Edit Column"), pos = wx.DefaultPosition, size = wx.Size( 600,600 ), style = wx.DEFAULT_DIALOG_STYLE|wx.STAY_ON_TOP ) - - self.SetSizeHints( wx.DefaultSize, wx.DefaultSize ) - - bSizer98 = wx.BoxSizer( wx.VERTICAL ) - - bSizer52 = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticText26 = wx.StaticText( self, wx.ID_ANY, _(u"Name"), wx.DefaultPosition, wx.Size( 100,-1 ), wx.ST_NO_AUTORESIZE ) - self.m_staticText26.Wrap( -1 ) - - bSizer52.Add( self.m_staticText26, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) - - self.column_name = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) - bSizer52.Add( self.column_name, 1, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) - - self.m_staticText261 = wx.StaticText( self, wx.ID_ANY, _(u"Datatype"), wx.DefaultPosition, wx.Size( 100,-1 ), 0 ) - self.m_staticText261.Wrap( -1 ) - - bSizer52.Add( self.m_staticText261, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) - - column_datatypeChoices = [] - self.column_datatype = wx.Choice( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, column_datatypeChoices, 0 ) - self.column_datatype.SetSelection( 0 ) - bSizer52.Add( self.column_datatype, 1, wx.ALL, 5 ) - - - bSizer98.Add( bSizer52, 0, wx.EXPAND, 5 ) - - bSizer5211 = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticText2611 = wx.StaticText( self, wx.ID_ANY, _(u"Length/Set"), wx.DefaultPosition, wx.Size( 100,-1 ), 0 ) - self.m_staticText2611.Wrap( -1 ) - - bSizer5211.Add( self.m_staticText2611, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) - - bSizer60 = wx.BoxSizer( wx.HORIZONTAL ) - - self.column_set = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) - bSizer60.Add( self.column_set, 1, wx.ALIGN_CENTER|wx.ALL, 5 ) - - self.column_length = wx.SpinCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.SP_ARROW_KEYS, 0, 65536, 0 ) - bSizer60.Add( self.column_length, 1, wx.ALL, 5 ) - - self.column_scale = wx.SpinCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.SP_WRAP, 0, 65536, 0 ) - self.column_scale.Enable( False ) - - bSizer60.Add( self.column_scale, 1, wx.ALL, 5 ) - - - bSizer5211.Add( bSizer60, 1, wx.EXPAND, 5 ) - - self.m_staticText261111112 = wx.StaticText( self, wx.ID_ANY, _(u"Collation"), wx.DefaultPosition, wx.Size( 100,-1 ), 0 ) - self.m_staticText261111112.Wrap( -1 ) - - bSizer5211.Add( self.m_staticText261111112, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) - - column_collationChoices = [] - self.column_collation = wx.Choice( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, column_collationChoices, 0 ) - self.column_collation.SetSelection( 0 ) - bSizer5211.Add( self.column_collation, 1, wx.ALL, 5 ) - - - bSizer98.Add( bSizer5211, 0, wx.EXPAND, 5 ) - - bSizer52111 = wx.BoxSizer( wx.HORIZONTAL ) - - - bSizer52111.Add( ( 0, 0), 1, wx.EXPAND, 5 ) - - self.column_unsigned = wx.CheckBox( self, wx.ID_ANY, _(u"Unsigned"), wx.DefaultPosition, wx.DefaultSize, 0 ) - bSizer52111.Add( self.column_unsigned, 1, wx.ALL, 5 ) - - - bSizer52111.Add( ( 0, 0), 1, wx.EXPAND, 5 ) - - self.column_allow_null = wx.CheckBox( self, wx.ID_ANY, _(u"Allow NULL"), wx.DefaultPosition, wx.DefaultSize, 0 ) - bSizer52111.Add( self.column_allow_null, 1, wx.ALL, 5 ) - - - bSizer52111.Add( ( 0, 0), 1, wx.EXPAND, 5 ) - - self.column_zero_fill = wx.CheckBox( self, wx.ID_ANY, _(u"Zero Fill"), wx.DefaultPosition, wx.DefaultSize, 0 ) - bSizer52111.Add( self.column_zero_fill, 1, wx.ALL, 5 ) - - - bSizer52111.Add( ( 0, 0), 1, wx.EXPAND, 5 ) - - - bSizer98.Add( bSizer52111, 0, wx.EXPAND, 5 ) - - bSizer53 = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticText26111111 = wx.StaticText( self, wx.ID_ANY, _(u"Default"), wx.DefaultPosition, wx.Size( 100,-1 ), 0 ) - self.m_staticText26111111.Wrap( -1 ) - - bSizer53.Add( self.m_staticText26111111, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) - - self.column_default = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) - bSizer53.Add( self.column_default, 1, wx.ALL, 5 ) - - - bSizer98.Add( bSizer53, 0, wx.EXPAND, 5 ) - - bSizer531 = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticText261111111 = wx.StaticText( self, wx.ID_ANY, _(u"Comments"), wx.DefaultPosition, wx.Size( 100,-1 ), 0 ) - self.m_staticText261111111.Wrap( -1 ) - - bSizer531.Add( self.m_staticText261111111, 0, wx.ALL, 5 ) - - self.column_comments = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.Size( -1,100 ), wx.TE_MULTILINE ) - bSizer531.Add( self.column_comments, 1, wx.ALL, 5 ) - - - bSizer98.Add( bSizer531, 0, wx.EXPAND, 5 ) - - bSizer532 = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticText261111113 = wx.StaticText( self, wx.ID_ANY, _(u"Virtuality"), wx.DefaultPosition, wx.Size( 100,-1 ), 0 ) - self.m_staticText261111113.Wrap( -1 ) - - bSizer532.Add( self.m_staticText261111113, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) - - column_virtualityChoices = [] - self.column_virtuality = wx.Choice( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, column_virtualityChoices, 0 ) - self.column_virtuality.SetSelection( 0 ) - bSizer532.Add( self.column_virtuality, 1, wx.ALL, 5 ) - - - bSizer98.Add( bSizer532, 0, wx.EXPAND, 5 ) - - bSizer5311 = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticText2611111111 = wx.StaticText( self, wx.ID_ANY, _(u"Expression"), wx.DefaultPosition, wx.Size( 100,-1 ), 0 ) - self.m_staticText2611111111.Wrap( -1 ) - - bSizer5311.Add( self.m_staticText2611111111, 0, wx.ALL, 5 ) - - self.column_expression = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.Size( -1,100 ), wx.TE_MULTILINE ) - bSizer5311.Add( self.column_expression, 1, wx.ALL, 5 ) - - - bSizer98.Add( bSizer5311, 0, wx.EXPAND, 5 ) - - - bSizer98.Add( ( 0, 0), 1, wx.EXPAND, 5 ) - - self.m_staticline2 = wx.StaticLine( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL ) - bSizer98.Add( self.m_staticline2, 0, wx.EXPAND | wx.ALL, 5 ) - - bSizer64 = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_button16 = wx.Button( self, wx.ID_ANY, _(u"Cancel"), wx.DefaultPosition, wx.DefaultSize, 0 ) - - self.m_button16.SetDefault() - - self.m_button16.SetBitmap( wx.Bitmap( u"icons/16x16/cancel.png", wx.BITMAP_TYPE_ANY ) ) - bSizer64.Add( self.m_button16, 0, wx.ALL, 5 ) - - - bSizer64.Add( ( 0, 0), 1, wx.EXPAND, 5 ) - - self.m_button15 = wx.Button( self, wx.ID_ANY, _(u"Save"), wx.DefaultPosition, wx.DefaultSize, 0 ) - - self.m_button15.SetBitmap( wx.Bitmap( u"icons/16x16/disk.png", wx.BITMAP_TYPE_ANY ) ) - bSizer64.Add( self.m_button15, 0, wx.ALL, 5 ) - - - bSizer98.Add( bSizer64, 0, wx.EXPAND, 5 ) - - - self.SetSizer( bSizer98 ) - self.Layout() - - self.Centre( wx.BOTH ) - - def __del__( self ): - pass - - -########################################################################### -## Class TablePanel -########################################################################### - -class TablePanel ( wx.Panel ): - - def __init__( self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition, size = wx.Size( 640,480 ), style = wx.TAB_TRAVERSAL, name = wx.EmptyString ): - wx.Panel.__init__ ( self, parent, id = id, pos = pos, size = size, style = style, name = name ) - - bSizer251 = wx.BoxSizer( wx.VERTICAL ) - - self.m_splitter41 = wx.SplitterWindow( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.SP_LIVE_UPDATE ) - self.m_splitter41.Bind( wx.EVT_IDLE, self.m_splitter41OnIdle ) - self.m_splitter41.SetMinimumPaneSize( 200 ) - - self.m_panel19 = wx.Panel( self.m_splitter41, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) - bSizer55 = wx.BoxSizer( wx.VERTICAL ) - - self.m_notebook3 = wx.Notebook( self.m_panel19, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.NB_FIXEDWIDTH ) - m_notebook3ImageSize = wx.Size( 16,16 ) - m_notebook3Index = 0 - m_notebook3Images = wx.ImageList( m_notebook3ImageSize.GetWidth(), m_notebook3ImageSize.GetHeight() ) - self.m_notebook3.AssignImageList( m_notebook3Images ) - self.PanelTableBase = wx.Panel( self.m_notebook3, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) - bSizer262 = wx.BoxSizer( wx.VERTICAL ) - - bSizer271 = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticText8 = wx.StaticText( self.PanelTableBase, wx.ID_ANY, _(u"Name"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) - self.m_staticText8.Wrap( -1 ) - - bSizer271.Add( self.m_staticText8, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) - - self.table_name = wx.TextCtrl( self.PanelTableBase, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) - bSizer271.Add( self.table_name, 1, wx.ALL|wx.EXPAND, 5 ) - - - bSizer262.Add( bSizer271, 0, wx.EXPAND, 5 ) - - bSizer273 = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticText83 = wx.StaticText( self.PanelTableBase, wx.ID_ANY, _(u"Comments"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) - self.m_staticText83.Wrap( -1 ) - - bSizer273.Add( self.m_staticText83, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) - - self.table_comment = wx.TextCtrl( self.PanelTableBase, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_MULTILINE ) - bSizer273.Add( self.table_comment, 1, wx.ALL|wx.EXPAND, 5 ) - - - bSizer262.Add( bSizer273, 1, wx.EXPAND, 5 ) - - - self.PanelTableBase.SetSizer( bSizer262 ) - self.PanelTableBase.Layout() - bSizer262.Fit( self.PanelTableBase ) - self.m_notebook3.AddPage( self.PanelTableBase, _(u"Base"), True ) - m_notebook3Bitmap = wx.Bitmap( u"icons/16x16/table.png", wx.BITMAP_TYPE_ANY ) - if ( m_notebook3Bitmap.IsOk() ): - m_notebook3Images.Add( m_notebook3Bitmap ) - self.m_notebook3.SetPageImage( m_notebook3Index, m_notebook3Index ) - m_notebook3Index += 1 - - self.PanelTableOptions = wx.Panel( self.m_notebook3, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) - bSizer261 = wx.BoxSizer( wx.VERTICAL ) - - gSizer11 = wx.GridSizer( 0, 2, 0, 0 ) - - bSizer27111 = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticText8111 = wx.StaticText( self.PanelTableOptions, wx.ID_ANY, _(u"Auto Increment"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) - self.m_staticText8111.Wrap( -1 ) - - bSizer27111.Add( self.m_staticText8111, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) - - self.table_auto_increment = wx.TextCtrl( self.PanelTableOptions, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) - bSizer27111.Add( self.table_auto_increment, 1, wx.ALL|wx.EXPAND, 5 ) - - - gSizer11.Add( bSizer27111, 1, wx.EXPAND, 5 ) - - bSizer2712 = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticText812 = wx.StaticText( self.PanelTableOptions, wx.ID_ANY, _(u"Engine"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) - self.m_staticText812.Wrap( -1 ) - - bSizer2712.Add( self.m_staticText812, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) - - table_engineChoices = [ wx.EmptyString ] - self.table_engine = wx.Choice( self.PanelTableOptions, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, table_engineChoices, 0 ) - self.table_engine.SetSelection( 1 ) - bSizer2712.Add( self.table_engine, 1, wx.ALL|wx.EXPAND, 5 ) - - - gSizer11.Add( bSizer2712, 0, wx.EXPAND, 5 ) - - bSizer2721 = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticText821 = wx.StaticText( self.PanelTableOptions, wx.ID_ANY, _(u"Default Collation"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) - self.m_staticText821.Wrap( -1 ) - - bSizer2721.Add( self.m_staticText821, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) - - self.table_collation = wx.TextCtrl( self.PanelTableOptions, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) - bSizer2721.Add( self.table_collation, 1, wx.ALL|wx.EXPAND, 5 ) - - - gSizer11.Add( bSizer2721, 0, wx.EXPAND, 5 ) - - - bSizer261.Add( gSizer11, 0, wx.EXPAND, 5 ) - - - self.PanelTableOptions.SetSizer( bSizer261 ) - self.PanelTableOptions.Layout() - bSizer261.Fit( self.PanelTableOptions ) - self.m_notebook3.AddPage( self.PanelTableOptions, _(u"Options"), False ) - m_notebook3Bitmap = wx.Bitmap( u"icons/16x16/wrench.png", wx.BITMAP_TYPE_ANY ) - if ( m_notebook3Bitmap.IsOk() ): - m_notebook3Images.Add( m_notebook3Bitmap ) - self.m_notebook3.SetPageImage( m_notebook3Index, m_notebook3Index ) - m_notebook3Index += 1 - - self.PanelTableIndex = wx.Panel( self.m_notebook3, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) - bSizer28 = wx.BoxSizer( wx.HORIZONTAL ) - - - self.PanelTableIndex.SetSizer( bSizer28 ) - self.PanelTableIndex.Layout() - bSizer28.Fit( self.PanelTableIndex ) - self.m_notebook3.AddPage( self.PanelTableIndex, _(u"Indexes"), False ) - m_notebook3Bitmap = wx.Bitmap( u"icons/16x16/lightning.png", wx.BITMAP_TYPE_ANY ) - if ( m_notebook3Bitmap.IsOk() ): - m_notebook3Images.Add( m_notebook3Bitmap ) - self.m_notebook3.SetPageImage( m_notebook3Index, m_notebook3Index ) - m_notebook3Index += 1 - - - bSizer55.Add( self.m_notebook3, 1, wx.EXPAND | wx.ALL, 5 ) - - - self.m_panel19.SetSizer( bSizer55 ) - self.m_panel19.Layout() - bSizer55.Fit( self.m_panel19 ) - self.panel_table_columns = wx.Panel( self.m_splitter41, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) - self.panel_table_columns.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_WINDOW ) ) - - bSizer54 = wx.BoxSizer( wx.VERTICAL ) - - bSizer53 = wx.BoxSizer( wx.HORIZONTAL ) - - self.m_staticText39 = wx.StaticText( self.panel_table_columns, wx.ID_ANY, _(u"Columns:"), wx.DefaultPosition, wx.DefaultSize, 0 ) - self.m_staticText39.Wrap( -1 ) - - bSizer53.Add( self.m_staticText39, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) - - - bSizer53.Add( ( 100, 0), 0, wx.EXPAND, 5 ) - - self.btn_insert_column = wx.Button( self.panel_table_columns, wx.ID_ANY, _(u"Insert"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) - - self.btn_insert_column.SetBitmap( wx.Bitmap( u"icons/16x16/add.png", wx.BITMAP_TYPE_ANY ) ) - bSizer53.Add( self.btn_insert_column, 0, wx.LEFT|wx.RIGHT, 2 ) - - self.btn_column_delete = wx.Button( self.panel_table_columns, wx.ID_ANY, _(u"Delete"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) - - self.btn_column_delete.SetBitmap( wx.Bitmap( u"icons/16x16/delete.png", wx.BITMAP_TYPE_ANY ) ) - self.btn_column_delete.Enable( False ) - - bSizer53.Add( self.btn_column_delete, 0, wx.LEFT|wx.RIGHT, 2 ) - - self.btn_column_move_up = wx.Button( self.panel_table_columns, wx.ID_ANY, _(u"Up"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) - - self.btn_column_move_up.SetBitmap( wx.Bitmap( u"icons/16x16/arrow_up.png", wx.BITMAP_TYPE_ANY ) ) - self.btn_column_move_up.Enable( False ) - - bSizer53.Add( self.btn_column_move_up, 0, wx.LEFT|wx.RIGHT, 2 ) - - self.btn_column_move_down = wx.Button( self.panel_table_columns, wx.ID_ANY, _(u"Down"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) - - self.btn_column_move_down.SetBitmap( wx.Bitmap( u"icons/16x16/arrow_down.png", wx.BITMAP_TYPE_ANY ) ) - self.btn_column_move_down.Enable( False ) - - bSizer53.Add( self.btn_column_move_down, 0, wx.LEFT|wx.RIGHT, 2 ) - - - bSizer53.Add( ( 0, 0), 1, wx.EXPAND, 5 ) - - - bSizer54.Add( bSizer53, 0, wx.ALL|wx.EXPAND, 5 ) - - self.list_ctrl_table_columns = TableColumnsDataViewCtrl( self.panel_table_columns, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0 ) - bSizer54.Add( self.list_ctrl_table_columns, 1, wx.ALL|wx.EXPAND, 5 ) - - bSizer52 = wx.BoxSizer( wx.HORIZONTAL ) - - self.btn_table_delete = wx.Button( self.panel_table_columns, wx.ID_ANY, _(u"Delete"), wx.DefaultPosition, wx.DefaultSize, 0 ) - bSizer52.Add( self.btn_table_delete, 0, wx.ALL, 5 ) - - self.btn_table_cancel = wx.Button( self.panel_table_columns, wx.ID_ANY, _(u"Cancel"), wx.DefaultPosition, wx.DefaultSize, 0 ) - self.btn_table_cancel.Enable( False ) - - bSizer52.Add( self.btn_table_cancel, 0, wx.ALL, 5 ) - - self.btn_table_save = wx.Button( self.panel_table_columns, wx.ID_ANY, _(u"Save"), wx.DefaultPosition, wx.DefaultSize, 0 ) - self.btn_table_save.Enable( False ) - - bSizer52.Add( self.btn_table_save, 0, wx.ALL, 5 ) - - - bSizer54.Add( bSizer52, 0, wx.EXPAND, 5 ) - - - self.panel_table_columns.SetSizer( bSizer54 ) - self.panel_table_columns.Layout() - bSizer54.Fit( self.panel_table_columns ) - self.menu_table_columns = wx.Menu() - self.add_index = wx.MenuItem( self.menu_table_columns, wx.ID_ANY, _(u"Add Index"), wx.EmptyString, wx.ITEM_NORMAL ) - self.menu_table_columns.Append( self.add_index ) - - self.m_menu21 = wx.Menu() - self.m_menuItem8 = wx.MenuItem( self.m_menu21, wx.ID_ANY, _(u"Add PrimaryKey"), wx.EmptyString, wx.ITEM_NORMAL ) - self.m_menu21.Append( self.m_menuItem8 ) - - self.m_menuItem9 = wx.MenuItem( self.m_menu21, wx.ID_ANY, _(u"Add Index"), wx.EmptyString, wx.ITEM_NORMAL ) - self.m_menu21.Append( self.m_menuItem9 ) - - self.menu_table_columns.AppendSubMenu( self.m_menu21, _(u"MyMenu") ) - - self.panel_table_columns.Bind( wx.EVT_RIGHT_DOWN, self.panel_table_columnsOnContextMenu ) - - self.m_splitter41.SplitHorizontally( self.m_panel19, self.panel_table_columns, 200 ) - bSizer251.Add( self.m_splitter41, 1, wx.EXPAND, 0 ) - - - self.SetSizer( bSizer251 ) - self.Layout() - - # Connect Events - self.btn_insert_column.Bind( wx.EVT_BUTTON, self.on_column_insert ) - self.btn_column_delete.Bind( wx.EVT_BUTTON, self.on_column_delete ) - self.btn_column_move_up.Bind( wx.EVT_BUTTON, self.on_column_move_up ) - self.btn_column_move_down.Bind( wx.EVT_BUTTON, self.on_column_move_down ) - self.btn_table_delete.Bind( wx.EVT_BUTTON, self.on_delete_table ) - self.btn_table_cancel.Bind( wx.EVT_BUTTON, self.do_cancel_table ) - self.btn_table_save.Bind( wx.EVT_BUTTON, self.do_save_table ) - - def __del__( self ): - pass - - - # Virtual event handlers, override them in your derived class - def on_column_insert( self, event ): - event.Skip() - - def on_column_delete( self, event ): - event.Skip() - - def on_column_move_up( self, event ): - event.Skip() - - def on_column_move_down( self, event ): - event.Skip() - - def on_delete_table( self, event ): - event.Skip() - - def do_cancel_table( self, event ): - event.Skip() - - def do_save_table( self, event ): - event.Skip() - - def m_splitter41OnIdle( self, event ): - self.m_splitter41.SetSashPosition( 200 ) - self.m_splitter41.Unbind( wx.EVT_IDLE ) - - def panel_table_columnsOnContextMenu( self, event ): - self.panel_table_columns.PopupMenu( self.menu_table_columns, event.GetPosition() ) - +from windows.views import ConnectionsDialog, MainFrameView, SettingsDialog +__all__ = [ + "ConnectionsDialog", + "MainFrameView", + "SettingsDialog", +] diff --git a/windows/components/dataview.py b/windows/components/dataview.py index 9dc4dd7..618b748 100644 --- a/windows/components/dataview.py +++ b/windows/components/dataview.py @@ -12,13 +12,12 @@ from structures.engines.database import SQLColumn, SQLTable, SQLIndex from structures.engines.datatype import DataTypeCategory from structures.engines.indextype import SQLIndexType, StandardIndexType -from windows.components import BaseDataViewCtrl +from windows.components import BaseDataViewCtrl from windows.components.popup import PopupColumnDatatype, PopupColumnDefault, PopupCheckList, PopupChoice, PopupCalendar, PopupCalendarTime from windows.components.renders import PopupRenderer, LengthSetRender, TimeRenderer, FloatRenderer, IntegerRenderer, TextRenderer, AdvancedTextRenderer -from windows.main import CURRENT_SESSION, CURRENT_DATABASE, CURRENT_TABLE -from windows.main.table import NEW_TABLE +from windows.state import CURRENT_SESSION, CURRENT_DATABASE, CURRENT_TABLE, NEW_TABLE class _SQLiteTableColumnsDataViewCtrl: @@ -330,18 +329,21 @@ def _load_table_columns(self, popup, column2_render: PopupRenderer) -> list[str] class TableRecordsDataViewCtrl(BaseDataViewCtrl): on_record_insert: Callable[[...], Optional[bool]] on_record_delete: Callable[[...], Optional[bool]] - make_advanced_dialog: Callable[[wx.Window, str], 'AdvancedCellEditorDialog'] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) CURRENT_TABLE.subscribe(self._load_table) + def make_advanced_dialog(self, parent, value: str): + from windows.dialogs.advanced_cell_editor import AdvancedCellEditorController + return AdvancedCellEditorController(parent, value) + def _get_column_renderer(self, column: SQLColumn) -> wx.dataview.DataViewRenderer: for foreign_key in column.table.foreign_keys: if column.name in foreign_key.columns: - session = CURRENT_SESSION() - database = CURRENT_DATABASE() + session = CURRENT_SESSION.get_value() + database = CURRENT_DATABASE.get_value() records = [] references = [] @@ -446,6 +448,10 @@ def autosize_columns_from_content(self, sample_rows: int = 30): col.SetWidth(width) +class QueryEditorResultsDataViewCtrl(TableRecordsDataViewCtrl): + pass + + class DatabaseTablesDataViewCtrl(BaseDataViewCtrl): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/windows/components/popup.py b/windows/components/popup.py index 3111df7..3078c11 100644 --- a/windows/components/popup.py +++ b/windows/components/popup.py @@ -13,7 +13,8 @@ from windows.components import BasePopup from structures.engines.datatype import SQLDataType, DataTypeCategory, StandardDataType -from windows.main import CURRENT_SESSION + +from windows.state import CURRENT_SESSION class PopupColumnDefault(BasePopup): diff --git a/windows/components/stc/__init__.py b/windows/components/stc/__init__.py index e9a00ce..6c5504c 100644 --- a/windows/components/stc/__init__.py +++ b/windows/components/stc/__init__.py @@ -1,5 +1,5 @@ -from windows.components.stc.auto_complete import CompletionResult -from windows.components.stc.auto_complete import SQLAutoCompleteController, SQLCompletionProvider +from windows.components.stc.autocomplete.completion_types import CompletionResult +from windows.components.stc.autocomplete.auto_complete import SQLAutoCompleteController, SQLCompletionProvider from windows.components.stc.detectors import detect_syntax_id from windows.components.stc.profiles import BASE64, CSV, HTML, JSON, MARKDOWN, REGEX, SQL, TEXT, XML, YAML from windows.components.stc.profiles import Detector, Formatter, SyntaxProfile diff --git a/windows/components/stc/auto_complete.py b/windows/components/stc/auto_complete.py deleted file mode 100644 index 10b52a8..0000000 --- a/windows/components/stc/auto_complete.py +++ /dev/null @@ -1,275 +0,0 @@ -import re - -from dataclasses import dataclass -from typing import Callable, Optional - -import wx -import wx.stc - -from structures.engines.database import SQLDatabase, SQLTable - - -@dataclass(frozen=True, slots=True) -class CompletionResult: - prefix: str - prefix_length: int - items: tuple[str, ...] - - -class SQLCompletionProvider: - _word_at_caret_pattern = re.compile(r"[A-Za-z_][A-Za-z0-9_]*$") - _token_pattern = re.compile(r"[A-Za-z_][A-Za-z0-9_]*|[.,()*=]") - - _from_like_keywords: set[str] = {"FROM", "JOIN", "UPDATE", "INTO"} - - def __init__( - self, - get_database: Callable[[], Optional[SQLDatabase]], - get_current_table: Optional[Callable[[], Optional[SQLTable]]] = None, - *, - is_filter_editor: bool = False, - ) -> None: - self._get_database = get_database - self._get_current_table = get_current_table or (lambda: None) - self._is_filter_editor = is_filter_editor - - def get(self, text: str, pos: int) -> Optional[CompletionResult]: - if (database := self._get_database()) is None: - return None - - safe_pos = self._clamp_position(pos=pos, text=text) - prefix = self._extract_prefix(pos=safe_pos, text=text) - previous_token = self._previous_token(pos=safe_pos, text=text) - previous_keyword = previous_token.upper() if previous_token and previous_token[0].isalpha() else None - - if self._is_dot_trigger(pos=safe_pos, text=text): - owner = self._word_before_dot(dot_pos=safe_pos - 1, text=text) - items = self._columns_for_owner(database=database, owner=owner) - return CompletionResult(items=tuple(items), prefix="", prefix_length=0) - - if previous_keyword in self._from_like_keywords: - items = self._tables(database=database) - return CompletionResult(items=tuple(items), prefix=prefix, prefix_length=len(prefix)) - - if self._is_filter_editor: - items = self._filter_items(database=database) - return CompletionResult(items=tuple(items), prefix=prefix, prefix_length=len(prefix)) - - if self._should_suggest_select_items(previous_token=previous_token, pos=safe_pos, text=text): - items = self._columns_prioritized(database=database) + self._functions(database=database) - return CompletionResult(items=tuple(items), prefix=prefix, prefix_length=len(prefix)) - - items = self._keywords(database=database) - return CompletionResult(items=tuple(items), prefix=prefix, prefix_length=len(prefix)) - - @staticmethod - def _clamp_position(*, pos: int, text: str) -> int: - if pos < 0: - return 0 - if pos > len(text): - return len(text) - return pos - - def _extract_prefix(self, *, pos: int, text: str) -> str: - left_text = text[:pos] - if (match := self._word_at_caret_pattern.search(left_text)) is None: - return "" - return match.group(0) - - def _previous_token(self, *, pos: int, text: str) -> Optional[str]: - tokens = self._token_pattern.findall(text[:pos]) - if not tokens: - return None - return tokens[-1] - - @staticmethod - def _is_dot_trigger(*, pos: int, text: str) -> bool: - return pos > 0 and text[pos - 1] == "." - - def _word_before_dot(self, *, dot_pos: int, text: str) -> str: - if dot_pos <= 0: - return "" - left_text = text[:dot_pos] - if (match := self._word_at_caret_pattern.search(left_text)) is None: - return "" - return match.group(0) - - def _should_suggest_select_items(self, *, previous_token: Optional[str], pos: int, text: str) -> bool: - if not self._is_in_select_list(pos=pos, text=text): - return False - if not previous_token: - return False - if previous_token == ",": - return True - return previous_token.upper() == "SELECT" - - @staticmethod - def _is_in_select_list(*, pos: int, text: str) -> bool: - left_upper = text[:pos].upper() - select_index = left_upper.rfind("SELECT") - if select_index == -1: - return False - from_index = left_upper.rfind("FROM") - return from_index == -1 or from_index < select_index - - def _columns_for_owner(self, *, database: SQLDatabase, owner: str) -> list[str]: - if not owner: - return [] - for table in database.tables: - if table.name.lower() == owner.lower(): - return [column.name for column in table.columns if column.name] - return [] - - def _columns_prioritized(self, *, database: SQLDatabase) -> list[str]: - items: list[str] = [] - current_table = self._get_current_table() - - if current_table is not None: - items.extend([column.name for column in current_table.columns if column.name]) - - for table in database.tables: - if table is current_table: - continue - for column in table.columns: - if column.name: - items.append(f"{table.name}.{column.name}") - - return items - - def _filter_items(self, *, database: SQLDatabase) -> list[str]: - return self._columns_prioritized(database=database) + self._functions(database=database) - - def _functions(self, *, database: SQLDatabase) -> list[str]: - functions = database.context.FUNCTIONS - return [str(function_name).upper() for function_name in functions] - - def _keywords(self, *, database: SQLDatabase) -> list[str]: - keywords = database.context.KEYWORDS - return [str(keyword).upper() for keyword in keywords] - - def _tables(self, *, database: SQLDatabase) -> list[str]: - return [table.name for table in database.tables] - - -class SQLAutoCompleteController: - def __init__( - self, - editor: wx.stc.StyledTextCtrl, - provider: SQLCompletionProvider, - *, - debounce_ms: int = 80, - is_enabled: bool = True, - min_prefix_length: int = 1, - ) -> None: - self._editor = editor - self._provider = provider - self._debounce_ms = debounce_ms - self._is_enabled = is_enabled - self._min_prefix_length = min_prefix_length - - self._is_showing = False - self._pending_call: Optional[wx.CallLater] = None - - self._configure_autocomp() - - self._editor.Bind(wx.stc.EVT_STC_CHARADDED, self._on_char_added) - self._editor.Bind(wx.EVT_KEY_DOWN, self._on_key_down) - - def set_enabled(self, is_enabled: bool) -> None: - self._is_enabled = is_enabled - if not is_enabled: - self._cancel_pending() - self._cancel_if_active() - - def show(self, *, force: bool) -> None: - if not self._is_enabled: - return - if self._is_showing: - return - - self._is_showing = True - try: - pos = self._editor.GetCurrentPos() - text = self._editor.GetText() - - result = self._provider.get(pos=pos, text=text) - if result is None: - self._cancel_if_active() - return - - if not force and result.prefix_length < self._min_prefix_length: - self._cancel_if_active() - return - - if not result.items: - self._cancel_if_active() - return - - items = self._unique_sorted_items(items=result.items) - - self._editor.AutoCompShow(result.prefix_length, "\n".join(items)) - if result.prefix: - self._editor.AutoCompSelect(result.prefix) - finally: - self._is_showing = False - - def _configure_autocomp(self) -> None: - # Use newline separator to support a wide range of identifiers. - self._editor.AutoCompSetSeparator(ord("\n")) - self._editor.AutoCompSetIgnoreCase(True) - self._editor.AutoCompSetAutoHide(True) - self._editor.AutoCompSetDropRestOfWord(True) - - def _on_key_down(self, event: wx.KeyEvent) -> None: - if not self._is_enabled: - event.Skip() - return - - key_code = event.GetKeyCode() - - if event.ControlDown() and key_code == wx.WXK_SPACE: - self._cancel_pending() - self.show(force=True) - return - - if key_code == wx.WXK_TAB and self._editor.AutoCompActive(): - self._cancel_pending() - self._editor.AutoCompComplete() - return - - if key_code == wx.WXK_ESCAPE and self._editor.AutoCompActive(): - self._cancel_pending() - self._editor.AutoCompCancel() - return - - event.Skip() - - def _on_char_added(self, event: wx.stc.StyledTextEvent) -> None: - if not self._is_enabled: - return - - key_code = event.GetKey() - character = chr(key_code) - - if character.isalnum() or character in {"_", ".", ","}: - self._schedule_show(force=False) - - def _schedule_show(self, *, force: bool) -> None: - self._cancel_pending() - self._pending_call = wx.CallLater(self._debounce_ms, self.show, force=force) - - def _cancel_pending(self) -> None: - if self._pending_call is None: - return - if self._pending_call.IsRunning(): - self._pending_call.Stop() - self._pending_call = None - - def _cancel_if_active(self) -> None: - if self._editor.AutoCompActive(): - self._editor.AutoCompCancel() - - @staticmethod - def _unique_sorted_items(*, items: tuple[str, ...]) -> list[str]: - unique_items: set[str] = set(items) - return sorted(unique_items, key=str.upper) diff --git a/windows/components/stc/autocomplete/auto_complete.py b/windows/components/stc/autocomplete/auto_complete.py new file mode 100644 index 0000000..65195f4 --- /dev/null +++ b/windows/components/stc/autocomplete/auto_complete.py @@ -0,0 +1,339 @@ +from typing import Callable, Optional + +import wx +import wx.stc + +from helpers.logger import logger + +from structures.engines.database import SQLDatabase, SQLTable + +from windows.components.stc.autocomplete.autocomplete_popup import AutoCompletePopup +from windows.components.stc.autocomplete.completion_types import ( + CompletionItem, + CompletionItemType, + CompletionResult, +) +from windows.components.stc.autocomplete.context_detector import ContextDetector +from windows.components.stc.autocomplete.dot_completion_handler import ( + DotCompletionHandler, +) +from windows.components.stc.autocomplete.statement_extractor import StatementExtractor +from windows.components.stc.autocomplete.suggestion_builder import SuggestionBuilder + +from windows.state import CURRENT_SESSION + + +class SQLCompletionProvider: + def __init__( + self, + get_database: Callable[[], Optional[SQLDatabase]], + get_current_table: Optional[Callable[[], Optional[SQLTable]]] = None, + *, + is_filter_editor: bool = False, + ) -> None: + self._get_database = get_database + self._get_current_table = get_current_table or (lambda: None) + self._is_filter_editor = is_filter_editor + self._cached_database_id: Optional[int] = None + + self._context_detector: Optional[ContextDetector] = None + self._dot_handler: Optional[DotCompletionHandler] = None + self._statement_extractor = StatementExtractor() + + def _get_current_dialect(self) -> Optional[str]: + if session := CURRENT_SESSION.get_value(): + return session.engine.value.dialect + + def get(self, text: str, pos: int) -> Optional[CompletionResult]: + try: + database = self._get_database() + if database is None: + return None + + self._update_cache(database=database) + + safe_pos = self._clamp_position(pos=pos, text=text) + + statement, relative_pos = ( + self._statement_extractor.extract_current_statement(text, safe_pos) + ) + + if not self._context_detector: + return None + + context, scope, prefix = self._context_detector.detect( + statement, relative_pos, database + ) + scope.current_table = self._get_current_table() + + if self._dot_handler: + self._dot_handler.refresh(database, scope) + if self._dot_handler.is_dot_completion(statement, relative_pos): + items, prefix = self._dot_handler.get_completions( + statement, relative_pos + ) + if items is not None: + return CompletionResult( + items=tuple(items), + prefix=prefix or "", + prefix_length=len(prefix) if prefix else 0, + ) + + builder = SuggestionBuilder(database, scope.current_table) + items = builder.build(context, scope, prefix, statement, relative_pos) + + return CompletionResult( + items=tuple(items), prefix=prefix, prefix_length=len(prefix) + ) + except Exception as ex: + logger.error(ex, exc_info=True) + return None + + @staticmethod + def _clamp_position(*, pos: int, text: str) -> int: + if pos < 0: + return 0 + if pos > len(text): + return len(text) + return pos + + def _update_cache(self, *, database: SQLDatabase) -> None: + database_id = id(database) + if self._cached_database_id != database_id: + self._cached_database_id = database_id + + dialect = self._get_current_dialect() + self._context_detector = ContextDetector(dialect) + self._dot_handler = DotCompletionHandler(database, None) + + +class SQLAutoCompleteController: + def __init__( + self, + editor: wx.stc.StyledTextCtrl, + provider: SQLCompletionProvider, + *, + settings: Optional[object] = None, + theme_loader: Optional[object] = None, + debounce_ms: int = 80, + is_enabled: bool = True, + min_prefix_length: int = 1, + ) -> None: + self._editor = editor + self._provider = provider + self._settings = settings + self._theme_loader = theme_loader + + if settings: + self._debounce_ms = ( + settings.get_value("settings", "autocomplete", "debounce_ms") + or debounce_ms + ) + self._min_prefix_length = ( + settings.get_value("settings", "autocomplete", "min_prefix_length") + or min_prefix_length + ) + self._add_space_after_completion = settings.get_value( + "settings", "autocomplete", "add_space_after_completion" + ) + if self._add_space_after_completion is None: + self._add_space_after_completion = True + else: + self._debounce_ms = debounce_ms + self._min_prefix_length = min_prefix_length + self._add_space_after_completion = True + + self._is_enabled = is_enabled + + self._is_showing = False + self._pending_call: Optional[wx.CallLater] = None + self._popup: Optional[AutoCompletePopup] = None + self._current_result: Optional[CompletionResult] = None + + self._editor.Bind(wx.stc.EVT_STC_CHARADDED, self._on_char_added) + self._editor.Bind(wx.EVT_KEY_DOWN, self._on_key_down) + + def set_enabled(self, is_enabled: bool) -> None: + self._is_enabled = is_enabled + if not is_enabled: + self._cancel_pending() + self._hide_popup() + + def get_effective_separator(self) -> str: + if self._settings: + separator = self._settings.get_value("query_editor", "statement_separator") + if separator: + return separator + + session = CURRENT_SESSION.get_value() + if session and hasattr(session, "context"): + return session.context.DEFAULT_STATEMENT_SEPARATOR + + return ";" + + def show(self, *, force: bool) -> None: + if not self._is_enabled: + return + if self._is_showing: + return + + self._is_showing = True + try: + pos = self._editor.GetCurrentPos() + text = self._editor.GetText() + + result = self._provider.get(pos=pos, text=text) + + if result is None: + self._hide_popup() + return + + if not result.items: + self._hide_popup() + return + + self._current_result = result + items = self._unique_sorted_items(items=result.items) + self._show_popup(items) + except Exception as ex: + logger.error(f"Error in show(): {ex}", exc_info=True) + finally: + self._is_showing = False + + def _show_popup(self, items: list[CompletionItem]) -> None: + if not self._popup: + self._popup = AutoCompletePopup( + self._editor, settings=self._settings, theme_loader=self._theme_loader + ) + self._popup.set_on_item_selected(self._on_item_completed) + + caret_pos = self._editor.GetCurrentPos() + point = self._editor.PointFromPosition(caret_pos) + screen_point = self._editor.ClientToScreen(point) + + line_height = self._editor.TextHeight(self._editor.GetCurrentLine()) + popup_position = wx.Point(screen_point.x, screen_point.y + line_height) + + self._popup.show_items(items, popup_position) + + def _hide_popup(self) -> None: + if self._popup and self._popup.IsShown(): + self._popup.Hide() + + def _on_item_completed(self, item: CompletionItem) -> None: + if not self._current_result: + return + + current_pos = self._editor.GetCurrentPos() + start_pos = current_pos - self._current_result.prefix_length + + self._editor.SetSelection(start_pos, current_pos) + + should_add_space = ( + self._add_space_after_completion + and item.item_type == CompletionItemType.KEYWORD + ) + completion_text = item.name + " " if should_add_space else item.name + self._editor.ReplaceSelection(completion_text) + + self._current_result = None + self._hide_popup() + + if should_add_space: + trigger_keywords = [ + "SELECT", + "FROM", + "JOIN", + "UPDATE", + "INTO", + "WHERE", + "AND", + "OR", + ] + if item.name.upper() in trigger_keywords: + wx.CallAfter(lambda: self._schedule_show(force=False)) + + def _on_key_down(self, event: wx.KeyEvent) -> None: + if not self._is_enabled: + event.Skip() + return + + key_code = event.GetKeyCode() + + if key_code == wx.WXK_SPACE: + if self._popup and self._popup.IsShown(): + self._cancel_pending() + self._hide_popup() + if not event.ControlDown(): + event.Skip() + return + + if event.ControlDown() and key_code == wx.WXK_SPACE: + self._cancel_pending() + self.show(force=True) + return + + if key_code == wx.WXK_TAB and self._popup and self._popup.IsShown(): + self._cancel_pending() + selected_item = self._popup.get_selected_item() + if selected_item: + self._on_item_completed(selected_item) + return + + if key_code == wx.WXK_ESCAPE and self._popup and self._popup.IsShown(): + self._cancel_pending() + self._hide_popup() + return + + if key_code == wx.WXK_BACK and self._popup and self._popup.IsShown(): + event.Skip() + wx.CallAfter(self._schedule_show, force=False) + return + + if key_code == wx.WXK_RETURN and self._popup and self._popup.IsShown(): + self._cancel_pending() + selected_item = self._popup.get_selected_item() + if selected_item: + self._on_item_completed(selected_item) + return + + event.Skip() + + def _on_char_added(self, event: wx.stc.StyledTextEvent) -> None: + if not self._is_enabled: + return + + key_code = event.GetKey() + character = chr(key_code) + + if character == " ": + self._schedule_show(force=False) + return + + if character.isalnum() or character in {"_", "."}: + self._schedule_show(force=False) + + def _schedule_show(self, *, force: bool) -> None: + self._cancel_pending() + self._pending_call = wx.CallLater(self._debounce_ms, self.show, force=force) + + def _cancel_pending(self) -> None: + if self._pending_call is None: + return + if self._pending_call.IsRunning(): + self._pending_call.Stop() + self._pending_call = None + + @staticmethod + def _unique_sorted_items( + *, items: tuple[CompletionItem, ...] + ) -> list[CompletionItem]: + seen_names: set[str] = set() + unique_items: list[CompletionItem] = [] + + for item in items: + if item.name not in seen_names: + seen_names.add(item.name) + unique_items.append(item) + + return unique_items diff --git a/windows/components/stc/autocomplete/autocomplete_popup.py b/windows/components/stc/autocomplete/autocomplete_popup.py new file mode 100644 index 0000000..e7ada99 --- /dev/null +++ b/windows/components/stc/autocomplete/autocomplete_popup.py @@ -0,0 +1,166 @@ +import wx +import wx.dataview + +from windows.components.stc.autocomplete.completion_types import CompletionItem, CompletionItemType +from windows.components.stc.theme_loader import ThemeLoader + + +class AutoCompletePopup(wx.PopupWindow): + def __init__(self, parent: wx.Window, settings: object = None, theme_loader: ThemeLoader = None) -> None: + super().__init__(parent, wx.BORDER_SIMPLE) + + self._selected_index: int = 0 + self._items: list[CompletionItem] = [] + self._on_item_selected: callable = None + self._settings = settings + self._theme_loader = theme_loader + + if settings: + self._popup_width = settings.get_value("settings", "autocomplete", "popup_width") or 300 + self._popup_max_height = settings.get_value("settings", "autocomplete", "popup_max_height") or 10 + else: + self._popup_width = 300 + self._popup_max_height = 10 + + self._create_ui() + self._bind_events() + + def _create_ui(self) -> None: + panel = wx.Panel(self) + sizer = wx.BoxSizer(wx.VERTICAL) + + self._list_ctrl = wx.ListCtrl( + panel, + style=wx.LC_REPORT | wx.LC_NO_HEADER | wx.LC_SINGLE_SEL + ) + + self._image_list = wx.ImageList(16, 16) + self._list_ctrl.SetImageList(self._image_list, wx.IMAGE_LIST_SMALL) + + self._list_ctrl.InsertColumn(0, "", width=self._popup_width) + self._list_ctrl.SetMinSize((self._popup_width, 200)) + + sizer.Add(self._list_ctrl, 1, wx.EXPAND) + panel.SetSizer(sizer) + + main_sizer = wx.BoxSizer(wx.VERTICAL) + main_sizer.Add(panel, 1, wx.EXPAND) + self.SetSizer(main_sizer) + + def _bind_events(self) -> None: + self._list_ctrl.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self._on_item_activated) + self._list_ctrl.Bind(wx.EVT_KEY_DOWN, self._on_key_down) + self.Bind(wx.EVT_KILL_FOCUS, self._on_kill_focus) + + def show_items(self, items: list[CompletionItem], position: wx.Point) -> None: + self._items = items + self._selected_index = 0 + + self._list_ctrl.DeleteAllItems() + self._image_list.RemoveAll() + + for idx, item in enumerate(items): + bitmap = self._get_bitmap_for_type(item.item_type) + color = self._get_color_for_type(item.item_type) + + image_idx = self._image_list.Add(bitmap) + list_idx = self._list_ctrl.InsertItem(idx, item.name, image_idx) + + if color: + self._list_ctrl.SetItemTextColour(list_idx, color) + + if items: + self._list_ctrl.Select(0) + self._list_ctrl.Focus(0) + + self.SetPosition(position) + + item_count = min(len(items), self._popup_max_height) + item_height = 24 + height = item_count * item_height + 10 + self.SetSize((self._popup_width, height)) + + self.Show() + self._list_ctrl.SetFocus() + + def _get_bitmap_for_type(self, item_type: CompletionItemType) -> wx.Bitmap: + icon_map = { + CompletionItemType.KEYWORD: wx.ART_INFORMATION, + CompletionItemType.FUNCTION: wx.ART_EXECUTABLE_FILE, + CompletionItemType.TABLE: wx.ART_FOLDER, + CompletionItemType.COLUMN: wx.ART_NORMAL_FILE, + } + + art_id = icon_map.get(item_type, wx.ART_INFORMATION) + return wx.ArtProvider.GetBitmap(art_id, wx.ART_MENU, (16, 16)) + + def _get_color_for_type(self, item_type: CompletionItemType) -> wx.Colour: + if self._theme_loader: + colors = self._theme_loader.get_autocomplete_colors() + color_hex = colors.get(item_type.value) + if color_hex: + return wx.Colour(color_hex) + + color_map = { + CompletionItemType.KEYWORD: wx.Colour(0, 0, 255), + CompletionItemType.FUNCTION: wx.Colour(128, 0, 128), + CompletionItemType.TABLE: wx.Colour(0, 128, 0), + CompletionItemType.COLUMN: wx.Colour(0, 0, 0), + } + return color_map.get(item_type, wx.Colour(0, 0, 0)) + + def _on_item_activated(self, event: wx.Event) -> None: + row = self._list_ctrl.GetFirstSelected() + if row != wx.NOT_FOUND and row < len(self._items): + self._complete_with_item(self._items[row]) + + def _on_key_down(self, event: wx.KeyEvent) -> None: + key_code = event.GetKeyCode() + + if key_code == wx.WXK_ESCAPE: + self.Hide() + return + + if key_code in (wx.WXK_RETURN, wx.WXK_TAB): + row = self._list_ctrl.GetFirstSelected() + if row != wx.NOT_FOUND and row < len(self._items): + self._complete_with_item(self._items[row]) + return + + if key_code == wx.WXK_PAGEDOWN: + current = self._list_ctrl.GetFirstSelected() + if current != wx.NOT_FOUND: + new_index = min(current + self._popup_max_height, len(self._items) - 1) + self._list_ctrl.Select(new_index) + self._list_ctrl.Focus(new_index) + self._list_ctrl.EnsureVisible(new_index) + return + + if key_code == wx.WXK_PAGEUP: + current = self._list_ctrl.GetFirstSelected() + if current != wx.NOT_FOUND: + new_index = max(current - self._popup_max_height, 0) + self._list_ctrl.Select(new_index) + self._list_ctrl.Focus(new_index) + self._list_ctrl.EnsureVisible(new_index) + return + + event.Skip() + + def _on_kill_focus(self, event: wx.FocusEvent) -> None: + self.Hide() + event.Skip() + + def _complete_with_item(self, item: CompletionItem) -> None: + if self._on_item_selected: + self._on_item_selected(item) + self.Hide() + + def set_on_item_selected(self, callback: callable) -> None: + self._on_item_selected = callback + + def get_selected_item(self) -> CompletionItem: + row = self._list_ctrl.GetFirstSelected() + if row != wx.NOT_FOUND and row < len(self._items): + return self._items[row] + return None diff --git a/windows/components/stc/autocomplete/completion_types.py b/windows/components/stc/autocomplete/completion_types.py new file mode 100644 index 0000000..5b30034 --- /dev/null +++ b/windows/components/stc/autocomplete/completion_types.py @@ -0,0 +1,23 @@ +from dataclasses import dataclass +from enum import Enum + + +class CompletionItemType(Enum): + KEYWORD = "keyword" + FUNCTION = "function" + TABLE = "table" + COLUMN = "column" + + +@dataclass(frozen=True, slots=True) +class CompletionItem: + name: str + item_type: CompletionItemType + description: str = "" + + +@dataclass(frozen=True, slots=True) +class CompletionResult: + prefix: str + prefix_length: int + items: tuple[CompletionItem, ...] diff --git a/windows/components/stc/autocomplete/context_detector.py b/windows/components/stc/autocomplete/context_detector.py new file mode 100644 index 0000000..fdee062 --- /dev/null +++ b/windows/components/stc/autocomplete/context_detector.py @@ -0,0 +1,817 @@ +import re + +from typing import Optional + +import sqlglot +from sqlglot import exp + +from helpers.logger import logger + +from windows.components.stc.autocomplete.query_scope import ( + QueryScope, + TableReference, + VirtualColumn, + VirtualTable, +) +from windows.components.stc.autocomplete.sql_context import SQLContext + +from structures.engines.database import SQLDatabase, SQLTable + + +class ContextDetector: + _prefix_pattern = re.compile(r"[A-Za-z_][A-Za-z0-9_]*$") + _join_after_table_pattern = re.compile( + r"\b(?:(?:INNER|LEFT|RIGHT|FULL|CROSS)(?:\s+OUTER)?\s+)?JOIN\s+([A-Za-z_][A-Za-z0-9_]*)" + r"\s*(?:(?:AS\s+)?([A-Za-z_][A-Za-z0-9_]*))?\s*$", + re.IGNORECASE, + ) + _join_after_table_keywords = { + "AS", + "ON", + "USING", + "WHERE", + "GROUP", + "ORDER", + "LIMIT", + "JOIN", + "INNER", + "LEFT", + "RIGHT", + "FULL", + "CROSS", + "OUTER", + } + + def __init__(self, dialect: Optional[str] = None): + self._dialect = dialect + + def detect( + self, text: str, cursor_pos: int, database: Optional[SQLDatabase] + ) -> tuple[SQLContext, QueryScope, str]: + left_text = text[:cursor_pos] + left_text_stripped = left_text.strip() + + if not left_text_stripped: + return SQLContext.EMPTY, QueryScope.empty(), "" + + if " " not in left_text and "\n" not in left_text: + return SQLContext.SINGLE_TOKEN, QueryScope.empty(), left_text_stripped + + prefix = self._extract_prefix(text, cursor_pos) + + try: + scope = self._extract_scope_from_text(text, database) + except Exception: + scope = QueryScope.empty() + + dot_match = self._check_dot_completion(left_text, prefix) + if dot_match: + table_alias = dot_match.group(1) + column_prefix = dot_match.group(2) if dot_match.group(2) else "" + return SQLContext.DOT_COMPLETION, scope, column_prefix + + try: + context = self._detect_context_with_regex(left_text, prefix) + return context, scope, prefix + except Exception as ex: + logger.debug(f"context detection error: {ex}") + return SQLContext.UNKNOWN, scope, prefix + + def _extract_prefix(self, text: str, cursor_pos: int) -> str: + if cursor_pos == 0: + return "" + + left_text = text[:cursor_pos] + + if left_text and left_text[-1] in (" ", "\t", "\n"): + return "" + + match = self._prefix_pattern.search(left_text) + if match is None: + return "" + return match.group(0) + + _dot_pattern = re.compile(r"([A-Za-z_][A-Za-z0-9_]*)\.([A-Za-z_][A-Za-z0-9_]*)?$") + + def _check_dot_completion(self, left_text: str, prefix: str) -> Optional[re.Match]: + if "." in left_text: + return self._dot_pattern.search(left_text) + return None + + def _detect_context_with_regex(self, left_text: str, prefix: str) -> SQLContext: + left_upper = left_text.upper() + + select_pos = left_upper.rfind("SELECT") + from_pos = left_upper.rfind("FROM") + where_pos = left_upper.rfind("WHERE") + join_pos = left_upper.rfind("JOIN") + on_pos = left_upper.rfind(" ON ") + order_by_pos = left_upper.rfind("ORDER BY") + group_by_pos = left_upper.rfind("GROUP BY") + having_pos = left_upper.rfind("HAVING") + limit_pos = left_upper.rfind("LIMIT") + offset_pos = left_upper.rfind("OFFSET") + + if select_pos == -1: + return SQLContext.UNKNOWN + + if re.search(r"\bOVER\s*(?:\(\s*)?$", left_text, re.IGNORECASE): + return SQLContext.WINDOW_OVER + + max_pos = max(limit_pos, offset_pos) + if max_pos > select_pos and max_pos != -1: + if self._is_after_limit_number(left_text, limit_pos, prefix): + return SQLContext.AFTER_LIMIT_NUMBER + return SQLContext.LIMIT_OFFSET_CLAUSE + + if having_pos > select_pos and having_pos != -1: + if having_pos > max(group_by_pos, order_by_pos, -1): + if self._is_after_having_operator(left_text, having_pos, prefix): + return SQLContext.HAVING_AFTER_OPERATOR + if self._is_after_having_expression(left_text, having_pos, prefix): + return SQLContext.HAVING_AFTER_EXPRESSION + return SQLContext.HAVING_CLAUSE + + if group_by_pos > select_pos and group_by_pos != -1: + if group_by_pos > max(where_pos, order_by_pos, having_pos, -1): + return SQLContext.GROUP_BY_CLAUSE + + if order_by_pos > select_pos and order_by_pos != -1: + if order_by_pos > max(where_pos, group_by_pos, having_pos, -1): + if self._is_after_order_by_column(left_text, order_by_pos, prefix): + return SQLContext.ORDER_BY_AFTER_COLUMN + return SQLContext.ORDER_BY_CLAUSE + + if on_pos > select_pos and on_pos != -1: + if on_pos > max(join_pos, from_pos, where_pos, -1): + if self._is_after_join_on_operator(left_text, on_pos, prefix): + return SQLContext.JOIN_ON_AFTER_OPERATOR + if self._is_after_join_on_expression(left_text, on_pos, prefix): + return SQLContext.JOIN_ON_AFTER_EXPRESSION + return SQLContext.JOIN_ON + + if join_pos > select_pos and join_pos != -1: + if join_pos > max(from_pos, where_pos, -1): + if self._is_after_join_table(left_text, prefix): + return SQLContext.JOIN_AFTER_TABLE + return SQLContext.JOIN_CLAUSE + + if where_pos > select_pos and where_pos != -1: + if where_pos > max(from_pos, order_by_pos, group_by_pos, -1): + if self._is_after_where_operator(left_text, where_pos, prefix): + return SQLContext.WHERE_AFTER_OPERATOR + if self._is_after_where_is(left_text, where_pos, prefix): + return SQLContext.WHERE_AFTER_EXPRESSION + if self._is_after_where_expression(left_text, where_pos, prefix): + return SQLContext.WHERE_AFTER_EXPRESSION + return SQLContext.WHERE_CLAUSE + + if from_pos > select_pos and from_pos != -1: + if from_pos > max(where_pos, join_pos, order_by_pos, group_by_pos, -1): + return SQLContext.FROM_CLAUSE + + return SQLContext.SELECT_LIST + + def _is_after_join_table(self, left_text: str, prefix: str) -> bool: + if prefix: + return False + + if not (match := self._join_after_table_pattern.search(left_text.rstrip())): + return False + + alias = match.group(2) + if alias and alias.upper() in self._join_after_table_keywords: + return False + + return True + + @staticmethod + def _is_after_join_on_expression(left_text: str, on_pos: int, prefix: str) -> bool: + if prefix: + return False + + on_clause = left_text[on_pos + 4 :] + on_clause_stripped = on_clause.strip() + if not on_clause_stripped: + return False + + return bool( + re.search( + r"(?:[A-Za-z_][A-Za-z0-9_]*\.)?[A-Za-z_][A-Za-z0-9_]*\s*" + r"(?:=|!=|<>|<=|>=|<|>)\s*" + r"(?:[A-Za-z_][A-Za-z0-9_]*\.)?[A-Za-z_][A-Za-z0-9_]*$", + on_clause_stripped, + re.IGNORECASE, + ) + ) + + def _is_after_join_on_operator( + self, left_text: str, on_pos: int, prefix: str + ) -> bool: + if prefix: + return False + + on_clause = left_text[on_pos + 4 :] + if not ( + match := re.search( + r"(?:(?P[A-Za-z_][A-Za-z0-9_]*)\.)?[A-Za-z_][A-Za-z0-9_]*\s*" + r"(?:=|!=|<>|<=|>=|<|>)\s*$", + on_clause, + re.IGNORECASE, + ) + ): + return False + + left_qualifier = match.group("qualifier") + if not left_qualifier: + return False + + from_qualifier = self._extract_from_qualifier(left_text) + if not from_qualifier: + return False + + return left_qualifier.lower() == from_qualifier.lower() + + def _is_after_where_operator( + self, left_text: str, where_pos: int, prefix: str + ) -> bool: + if prefix: + return False + + where_clause = left_text[where_pos + 5 :] + if not ( + match := re.search( + r"(?:(?P[A-Za-z_][A-Za-z0-9_]*)\.)?[A-Za-z_][A-Za-z0-9_]*\s*" + r"(?:=|!=|<>|<=|>=|<|>|LIKE|IN|BETWEEN)\s*$", + where_clause, + re.IGNORECASE, + ) + ): + return False + + return True + + def _is_after_where_expression( + self, left_text: str, where_pos: int, prefix: str + ) -> bool: + if prefix: + return False + + where_clause = left_text[where_pos + 5 :] + where_clause_stripped = where_clause.strip() + if not where_clause_stripped: + return False + + # Match: column operator value (where value can be column, literal, or function) + # Note: IS is handled separately by _is_after_where_is + return bool( + re.search( + r"(?:[A-Za-z_][A-Za-z0-9_]*\.)?[A-Za-z_][A-Za-z0-9_]*\s*" + r"(?:=|!=|<>|<=|>=|<|>|LIKE|IN|BETWEEN)\s*" + r"(?:[A-Za-z_][A-Za-z0-9_]*|\d+|'[^']*'|\"[^\"]*\"|NULL|TRUE|FALSE|\w+\([^)]*\))\s*$", + where_clause_stripped, + re.IGNORECASE, + ) + ) + + def _is_after_where_is(self, left_text: str, where_pos: int, prefix: str) -> bool: + if prefix: + return False + + where_clause = left_text[where_pos + 5 :] + where_clause_stripped = where_clause.strip() + if not where_clause_stripped: + return False + + # Match: column IS (with optional NOT) followed by whitespace or end + return bool( + re.search( + r"(?:[A-Za-z_][A-Za-z0-9_]*\.)?[A-Za-z_][A-Za-z0-9_]*\s+IS(?:\s+NOT)?\s*$", + where_clause_stripped, + re.IGNORECASE, + ) + ) + + def _is_after_having_operator( + self, left_text: str, having_pos: int, prefix: str + ) -> bool: + if prefix: + return False + + having_clause = left_text[having_pos + 6 :] + return bool( + re.search( + r"(?:=|!=|<>|<=|>=|<|>|LIKE|IN|NOT\s+IN|BETWEEN)\s*$", + having_clause, + re.IGNORECASE, + ) + ) + + def _is_after_having_expression( + self, left_text: str, having_pos: int, prefix: str + ) -> bool: + if prefix: + return False + + having_clause = left_text[having_pos + 6 :] + if not having_clause or not having_clause[-1].isspace(): + return False + + clause = having_clause.strip() + if not clause: + return False + + if re.search( + r"(?:=|!=|<>|<=|>=|<|>|LIKE|IN|NOT\s+IN|BETWEEN)$", clause, re.IGNORECASE + ): + return False + + if re.search(r"(?:AND|OR|NOT|EXISTS|HAVING)\s*$", clause, re.IGNORECASE): + return False + + return True + + def _extract_from_qualifier(self, left_text: str) -> Optional[str]: + if not ( + from_match := re.search( + r"\bFROM\s+([A-Za-z_][A-Za-z0-9_]*)" + r"\s*(?:(?:AS\s+)?([A-Za-z_][A-Za-z0-9_]*))?", + left_text, + re.IGNORECASE, + ) + ): + return None + + table_name = from_match.group(1) + alias = from_match.group(2) + if alias and alias.upper() not in self._join_after_table_keywords: + return alias + + return table_name + + def _extract_scope_from_select( + self, parsed: exp.Select, database: Optional[SQLDatabase] + ) -> QueryScope: + from_tables = [] + join_tables = [] + aliases = {} + + if from_clause := parsed.args.get("from"): + if isinstance(from_clause, exp.From): + for table_exp in from_clause.find_all(exp.Table): + table_name = table_exp.name + alias = ( + table_exp.alias + if hasattr(table_exp, "alias") and table_exp.alias + else None + ) + + table_obj = ( + self._find_table_in_database(table_name, database) + if database + else None + ) + ref = TableReference(name=table_name, alias=alias, table=table_obj) + from_tables.append(ref) + + if alias: + aliases[alias.lower()] = ref + aliases[table_name.lower()] = ref + + for join_exp in parsed.find_all(exp.Join): + if table_exp := join_exp.this: + if isinstance(table_exp, exp.Table): + table_name = table_exp.name + alias = ( + table_exp.alias + if hasattr(table_exp, "alias") and table_exp.alias + else None + ) + + table_obj = ( + self._find_table_in_database(table_name, database) + if database + else None + ) + ref = TableReference(name=table_name, alias=alias, table=table_obj) + join_tables.append(ref) + + if alias: + aliases[alias.lower()] = ref + aliases[table_name.lower()] = ref + + return QueryScope( + from_tables=from_tables, + join_tables=join_tables, + current_table=None, + aliases=aliases, + ) + + def _extract_scope_from_text( + self, text: str, database: Optional[SQLDatabase] + ) -> QueryScope: + cleaned_text = re.sub(r"--[^\n]*|/\*.*?\*/", " ", text, flags=re.DOTALL) + cte_tables, cte_end_pos = self._parse_cte_definitions(cleaned_text) + main_text = cleaned_text[cte_end_pos:] if cte_end_pos else cleaned_text + cte_lookup = {ref.name.lower(): ref for ref in cte_tables} + + sql_keywords = { + "WHERE", + "ORDER", + "GROUP", + "HAVING", + "LIMIT", + "OFFSET", + "UNION", + "INTERSECT", + "EXCEPT", + "ON", + "USING", + "AND", + "OR", + "NOT", + "IN", + "EXISTS", + "BETWEEN", + "LIKE", + "IS", + "NULL", + "ASC", + "DESC", + "AS", + "JOIN", + "INNER", + "LEFT", + "RIGHT", + "FULL", + "CROSS", + "OUTER", + } + + join_pattern = re.compile( + r"\bJOIN\s+([A-Za-z_][A-Za-z0-9_]*)\s*(?:(?:AS\s+)?([A-Za-z_][A-Za-z0-9_]*))?\s*(?:\bON\b|\bUSING\b|$)", + re.IGNORECASE, + ) + + from_tables = [] + join_tables = [] + aliases = {} + + for table_name, alias, projected_columns in self._extract_from_table_tokens( + main_text + ): + if table_name.upper() in sql_keywords: + continue + if alias and alias.upper() in sql_keywords: + alias = None + + table_obj = None + if projected_columns is not None: + table_obj = self._build_virtual_table(table_name, projected_columns) + elif table_name.lower() in cte_lookup: + table_obj = cte_lookup[table_name.lower()].table + elif database: + table_obj = self._find_table_in_database(table_name, database) + + ref = TableReference(name=table_name, alias=alias, table=table_obj) + from_tables.append(ref) + + if alias: + aliases[alias.lower()] = ref + aliases[table_name.lower()] = ref + + for match in join_pattern.finditer(main_text): + table_name = match.group(1) + alias = match.group(2) if match.group(2) else None + + if table_name.upper() in sql_keywords: + continue + if alias and alias.upper() in sql_keywords: + alias = None + + if table_name.lower() in cte_lookup: + table_obj = cte_lookup[table_name.lower()].table + else: + table_obj = ( + self._find_table_in_database(table_name, database) + if database + else None + ) + ref = TableReference(name=table_name, alias=alias, table=table_obj) + join_tables.append(ref) + + if alias: + aliases[alias.lower()] = ref + aliases[table_name.lower()] = ref + + return QueryScope( + from_tables=from_tables, + join_tables=join_tables, + current_table=None, + aliases=aliases, + cte_tables=cte_tables, + ) + + @staticmethod + def _extract_from_table_tokens( + text: str, + ) -> list[tuple[str, Optional[str], Optional[list[str]]]]: + if not ( + from_match := re.search( + r"\bFROM\b(?P
.*?)(?:\bWHERE\b|\bGROUP\s+BY\b|\bORDER\s+BY\b|\bHAVING\b|\bLIMIT\b|\bJOIN\b|$)", + text, + re.IGNORECASE | re.DOTALL, + ) + ): + return [] + + from_section = from_match.group("section") + if not from_section: + return [] + + tables: list[tuple[str, Optional[str], Optional[list[str]]]] = [] + for raw_part in ContextDetector._split_top_level_parts(from_section): + part = raw_part.strip() + if not part: + continue + + subquery_match = re.match( + r"^\((?P.+)\)\s*(?:AS\s+)?(?P[A-Za-z_][A-Za-z0-9_]*)$", + part, + re.IGNORECASE | re.DOTALL, + ) + if subquery_match: + alias = subquery_match.group("alias") + columns = ContextDetector._extract_projected_columns( + subquery_match.group("subquery") + ) + tables.append((alias, alias, columns)) + continue + + if not ( + table_match := re.match( + r"(?P[A-Za-z_][A-Za-z0-9_]*)(?:\s+(?:AS\s+)?(?P[A-Za-z_][A-Za-z0-9_]*))?(?:\s+[A-Za-z_][A-Za-z0-9_]*)?$", + part, + re.IGNORECASE, + ) + ): + continue + + table_name = table_match.group("table") + alias = table_match.group("alias") if table_match.group("alias") else None + tables.append((table_name, alias, None)) + + return tables + + @staticmethod + def _split_top_level_parts(text: str) -> list[str]: + parts = [] + start = 0 + depth = 0 + for idx, ch in enumerate(text): + if ch == "(": + depth += 1 + elif ch == ")" and depth > 0: + depth -= 1 + elif ch == "," and depth == 0: + parts.append(text[start:idx]) + start = idx + 1 + parts.append(text[start:]) + return parts + + @staticmethod + def _extract_projected_columns(subquery_sql: str) -> list[str]: + match = re.search( + r"\bSELECT\b\s+(?P
[A-Za-z_][A-Za-z0-9_]*)\.)?(?P[A-Za-z_][A-Za-z0-9_]*)$", + part_clean, + re.IGNORECASE, + ): + table_name = token.group("table") + column_name = token.group("column") + ordered_names.add(column_name.lower()) + if table_name: + ordered_names.add(f"{table_name.lower()}.{column_name.lower()}") + + return ordered_names + + def _build_group_by( + self, + scope: QueryScope, + prefix: str, + statement: str = "", + cursor_pos: Optional[int] = None, + ) -> list[CompletionItem]: + left_statement = statement[:cursor_pos] if cursor_pos is not None else statement + columns = self._build_where_columns(scope, prefix, statement) + columns = self._exclude_group_by_existing_columns(columns, left_statement) + + items = list(columns) + items.extend(self._build_group_by_functions(prefix)) + return items + + def _build_group_by_functions(self, prefix: str) -> list[CompletionItem]: + functions = self._build_functions(prefix) + return [ + function + for function in functions + if function.name not in self._group_by_excluded_functions + ] + + @staticmethod + def _exclude_group_by_existing_columns( + columns: list[CompletionItem], left_statement: str + ) -> list[CompletionItem]: + grouped_column_names = SuggestionBuilder._extract_group_by_column_names( + left_statement + ) + if not grouped_column_names: + return columns + + filtered = [] + for column in columns: + column_name = column.name.lower() + base_name = ( + column_name.split(".", 1)[1] if "." in column_name else column_name + ) + if column_name in grouped_column_names or base_name in grouped_column_names: + continue + filtered.append(column) + return filtered + + @staticmethod + def _extract_group_by_column_names(left_statement: str) -> set[str]: + if not ( + match := re.search( + r"\bGROUP\s+BY\s+(?P.+)$", left_statement, re.IGNORECASE + ) + ): + return set() + + clause = match.group("clause") + if not clause: + return set() + + grouped_names: set[str] = set() + for raw_part in clause.split(","): + part = raw_part.strip() + if not part: + continue + + if token := re.match( + r"(?:(?P
[A-Za-z_][A-Za-z0-9_]*)\.)?(?P[A-Za-z_][A-Za-z0-9_]*)$", + part, + re.IGNORECASE, + ): + table_name = token.group("table") + column_name = token.group("column") + grouped_names.add(column_name.lower()) + if table_name: + grouped_names.add(f"{table_name.lower()}.{column_name.lower()}") + + return grouped_names + + def _build_having( + self, scope: QueryScope, prefix: str, statement: str = "" + ) -> list[CompletionItem]: + aggregate_funcs = self._build_aggregate_functions(prefix) + columns = self._build_where_columns(scope, prefix, statement) + other_funcs = self._build_having_other_functions(prefix) + return aggregate_funcs + columns + other_funcs + + def _build_having_after_operator( + self, scope: QueryScope, prefix: str, statement: str = "" + ) -> list[CompletionItem]: + literals = self._build_having_literals(prefix) + aggregate_funcs = self._build_aggregate_functions(prefix) + columns = self._build_where_columns(scope, prefix, statement) + return literals + aggregate_funcs + columns + + @staticmethod + def _build_having_after_expression(prefix: str) -> list[CompletionItem]: + keywords = ["AND", "OR", "NOT", "EXISTS", "ORDER BY", "LIMIT"] + if prefix: + prefix_upper = prefix.upper() + keywords = [ + keyword for keyword in keywords if keyword.startswith(prefix_upper) + ] + + return [ + CompletionItem(name=keyword, item_type=CompletionItemType.KEYWORD) + for keyword in keywords + ] + + @staticmethod + def _build_window_over(prefix: str) -> list[CompletionItem]: + keywords = ["ORDER BY", "PARTITION BY"] + if prefix: + prefix_upper = prefix.upper() + keywords = [ + keyword for keyword in keywords if keyword.startswith(prefix_upper) + ] + + return [ + CompletionItem(name=keyword, item_type=CompletionItemType.KEYWORD) + for keyword in keywords + ] + + @staticmethod + def _build_after_limit_number(prefix: str) -> list[CompletionItem]: + keywords = ["OFFSET"] + if prefix: + prefix_upper = prefix.upper() + keywords = [ + keyword for keyword in keywords if keyword.startswith(prefix_upper) + ] + + return [ + CompletionItem(name=keyword, item_type=CompletionItemType.KEYWORD) + for keyword in keywords + ] + + def _build_having_other_functions(self, prefix: str) -> list[CompletionItem]: + functions = self._build_functions(prefix) + return [ + function + for function in functions + if function.name not in self._aggregate_functions + and function.name not in self._having_excluded_functions + ] + + @staticmethod + def _build_having_literals(prefix: str) -> list[CompletionItem]: + literals = ["NULL", "TRUE", "FALSE"] + if prefix: + prefix_upper = prefix.upper() + literals = [ + literal for literal in literals if literal.startswith(prefix_upper) + ] + + return [ + CompletionItem(name=literal, item_type=CompletionItemType.KEYWORD) + for literal in literals + ] + + def _build_keywords(self, prefix: str) -> list[CompletionItem]: + if not self._database: + return [] + + try: + all_keywords = self._database.context.KEYWORDS + keywords = [ + CompletionItem( + name=str(kw).upper(), item_type=CompletionItemType.KEYWORD + ) + for kw in all_keywords + ] + except (AttributeError, TypeError): + return [] + + if prefix: + prefix_upper = prefix.upper() + keywords = [kw for kw in keywords if kw.name.startswith(prefix_upper)] + + return sorted(keywords, key=lambda x: x.name) + + def _build_select_keywords(self, prefix: str) -> list[CompletionItem]: + keywords = ["FROM", "WHERE", "LIMIT", "ORDER BY", "GROUP BY"] + + if prefix: + prefix_upper = prefix.upper() + keywords = [kw for kw in keywords if kw.startswith(prefix_upper)] + + return [ + CompletionItem(name=kw, item_type=CompletionItemType.KEYWORD) + for kw in keywords + ] + + def _build_select_completed_item_keywords( + self, + left_statement: str, + scope: QueryScope, + prefix: str, + ) -> list[CompletionItem]: + import re + + processed_statement = left_statement + if prefix and left_statement.endswith(prefix): + processed_statement = left_statement[: -len(prefix)] + + suggestions = [] + wildcard_table_match = re.search(r"(\w+)\.\*\s+$", processed_statement) + is_wildcard_select_item = bool( + wildcard_table_match or re.search(r"(?:^|\s)\*\s+$", processed_statement) + ) + + match = re.search(r"(\w+)(?:\.(\w+))?\s+$", processed_statement) + table_name = None + if wildcard_table_match: + table_name = wildcard_table_match.group(1) + elif match and match.group(2): + table_name = match.group(1) + + if ( + table_name is None + and self._current_table + and not scope.from_tables + and not scope.join_tables + ): + table_name = self._current_table.name + + if table_name: + suggestions.extend( + self._build_from_suggestions_for_qualifier(table_name, scope) + ) + + if is_wildcard_select_item: + suggestions.append("FROM") + else: + suggestions.extend(["AS", "FROM"]) + + # Preserve order while removing duplicates. + suggestions = list(dict.fromkeys(suggestions)) + + if prefix: + prefix_upper = prefix.upper() + suggestions = [ + item for item in suggestions if item.upper().startswith(prefix_upper) + ] + + return [ + CompletionItem(name=item, item_type=CompletionItemType.KEYWORD) + for item in suggestions + ] + + def _build_from_suggestions_for_qualifier( + self, qualifier: str, scope: QueryScope + ) -> list[str]: + qualifier_lower = qualifier.lower() + + if qualifier_lower in scope.aliases: + ref = scope.aliases[qualifier_lower] + if ref.alias and ref.alias.lower() != ref.name.lower(): + return [f"FROM {ref.name} {ref.alias}"] + return [f"FROM {ref.name}"] + + if not self._database: + return [] + + try: + tables = list(self._database.tables) + except (AttributeError, TypeError): + return [] + + exact_match = next( + (table for table in tables if table.name.lower() == qualifier_lower), None + ) + if exact_match is not None: + return [f"FROM {exact_match.name}"] + + prefix_matches = [ + table for table in tables if table.name.lower().startswith(qualifier_lower) + ] + if not prefix_matches: + return [] + + prefix_matches.sort(key=lambda t: self._table_name_sort_key(t.name)) + return [f"FROM {table.name} {qualifier}" for table in prefix_matches] + + def _build_select_list_functions(self, prefix: str) -> list[CompletionItem]: + functions = self._build_functions(prefix) + return [ + item + for item in functions + if item.name not in self._select_list_excluded_functions + ] + + def _build_functions(self, prefix: str) -> list[CompletionItem]: + if not self._database: + return [] + + try: + functions = self._database.context.FUNCTIONS + function_list = [ + CompletionItem( + name=str(func).upper(), item_type=CompletionItemType.FUNCTION + ) + for func in functions + ] + except (AttributeError, TypeError): + return [] + + if prefix: + prefix_upper = prefix.upper() + function_list = [ + f for f in function_list if f.name.startswith(prefix_upper) + ] + + return sorted(function_list, key=lambda x: x.name) + + def _build_aggregate_functions(self, prefix: str) -> list[CompletionItem]: + if not self._database: + return [] + + try: + functions = self._database.context.FUNCTIONS + aggregate_list = [ + CompletionItem( + name=str(func).upper(), item_type=CompletionItemType.FUNCTION + ) + for func in functions + if str(func).upper() in self._aggregate_functions + ] + except (AttributeError, TypeError): + return [] + + if prefix: + prefix_upper = prefix.upper() + aggregate_list = [ + f for f in aggregate_list if f.name.startswith(prefix_upper) + ] + + return sorted(aggregate_list, key=lambda x: x.name) + + def _resolve_columns_in_scope( + self, scope: QueryScope, prefix: str, context: Optional[SQLContext] = None + ) -> list[CompletionItem]: + if prefix and self._is_exact_alias_match(prefix, scope): + return self._get_alias_columns(prefix, scope) + + if prefix: + return self._resolve_columns_with_prefix(scope, prefix, context) + + return self._resolve_columns_without_prefix(scope, context) + + def _resolve_columns_without_prefix( + self, scope: QueryScope, context: Optional[SQLContext] = None + ) -> list[CompletionItem]: + columns = [] + + is_scope_restricted = context and self._is_scope_restricted_context(context) + has_scope = bool(scope.from_tables or scope.join_tables) + + if context == SQLContext.SELECT_LIST and not has_scope: + return [] + + if context == SQLContext.SELECT_LIST: + if not has_scope and self._current_table: + columns.extend(self._get_current_table_columns(scope, None)) + elif ( + has_scope + and self._current_table + and self._is_current_table_in_scope(scope) + ): + columns.extend(self._get_current_table_columns(scope, None)) + elif not is_scope_restricted and self._current_table: + columns.extend(self._get_current_table_columns(scope, None)) + + columns.extend(self._get_from_table_columns(scope, None)) + columns.extend(self._get_join_table_columns(scope, None)) + + include_database_columns = False + if context == SQLContext.SELECT_LIST: + include_database_columns = not has_scope + elif not is_scope_restricted: + include_database_columns = True + + if include_database_columns and len(columns) < self._max_database_columns: + columns.extend(self._get_database_columns(scope, None)) + + return columns + + def _resolve_columns_with_prefix( + self, scope: QueryScope, prefix: str, context: Optional[SQLContext] = None + ) -> list[CompletionItem]: + seen = set() + columns = [] + + is_scope_restricted = context and self._is_scope_restricted_context(context) + has_scope = bool(scope.from_tables or scope.join_tables) + include_database_columns = not is_scope_restricted and not ( + context == SQLContext.SELECT_LIST and has_scope + ) + + include_current_table = True + if context == SQLContext.SELECT_LIST and has_scope: + include_current_table = self._is_current_table_in_scope(scope) + elif is_scope_restricted: + include_current_table = False + + table_expansion_columns = self._get_table_name_expansion_columns( + scope, + prefix, + include_database_columns, + include_current_table, + ) + for col in table_expansion_columns: + if col.name.lower() not in seen: + seen.add(col.name.lower()) + columns.append(col) + + column_name_match_columns = self._get_column_name_match_columns( + scope, + prefix, + include_database_columns, + include_current_table, + context, + ) + for col in column_name_match_columns: + if col.name.lower() not in seen: + seen.add(col.name.lower()) + columns.append(col) + + return columns + + def _get_table_name_expansion_columns( + self, + scope: QueryScope, + prefix: str, + include_database_columns: bool, + include_current_table: bool = True, + ) -> list[CompletionItem]: + columns = [] + prefix_lower = prefix.lower() + + if ( + include_current_table + and self._current_table + and self._current_table.name.lower().startswith(prefix_lower) + ): + qualifier = self._get_table_qualifier(self._current_table.name, scope) + try: + for col in self._current_table.columns: + if col.name: + columns.append( + CompletionItem( + name=f"{qualifier}.{col.name}", + item_type=CompletionItemType.COLUMN, + description=self._current_table.name, + ) + ) + except (AttributeError, TypeError): + pass + + if self._is_exact_alias_match(prefix, scope): + columns.extend(self._get_alias_columns(prefix, scope)) + return columns + + for ref in scope.from_tables: + if not ref.table: + continue + + if ref.name.lower().startswith(prefix_lower): + qualifier = ref.name + + try: + for col in ref.table.columns: + if col.name: + columns.append( + CompletionItem( + name=f"{qualifier}.{col.name}", + item_type=CompletionItemType.COLUMN, + description=ref.name, + ) + ) + except (AttributeError, TypeError): + pass + + for ref in scope.join_tables: + if not ref.table: + continue + + if ref.name.lower().startswith(prefix_lower): + qualifier = ref.name + + try: + for col in ref.table.columns: + if col.name: + columns.append( + CompletionItem( + name=f"{qualifier}.{col.name}", + item_type=CompletionItemType.COLUMN, + description=ref.name, + ) + ) + except (AttributeError, TypeError): + pass + + if include_database_columns and self._database: + in_scope_table_names = set() + if self._current_table: + in_scope_table_names.add(self._current_table.name.lower()) + for ref in scope.from_tables + scope.join_tables: + in_scope_table_names.add(ref.name.lower()) + + try: + for table in self._database.tables: + if ( + table.name.lower().startswith(prefix_lower) + and table.name.lower() not in in_scope_table_names + ): + try: + for col in table.columns: + if col.name: + columns.append( + CompletionItem( + name=f"{table.name}.{col.name}", + item_type=CompletionItemType.COLUMN, + description=table.name, + ) + ) + except (AttributeError, TypeError): + pass + except (AttributeError, TypeError): + pass + + return columns + + def _get_column_name_match_columns( + self, + scope: QueryScope, + prefix: str, + include_database_columns: bool, + include_current_table: bool = True, + context: Optional[SQLContext] = None, + ) -> list[CompletionItem]: + columns = [] + database_columns = [] + prefix_lower = prefix.lower() + + if include_current_table and self._current_table: + qualifier = self._get_table_qualifier(self._current_table.name, scope) + try: + for col in self._current_table.columns: + if col.name and col.name.lower().startswith(prefix_lower): + columns.append( + CompletionItem( + name=f"{qualifier}.{col.name}", + item_type=CompletionItemType.COLUMN, + description=self._current_table.name, + ) + ) + except (AttributeError, TypeError): + pass + + for ref in scope.from_tables: + if ref.table: + qualifier = ref.alias if ref.alias else ref.name + try: + for col in ref.table.columns: + if col.name and col.name.lower().startswith(prefix_lower): + columns.append( + CompletionItem( + name=f"{qualifier}.{col.name}", + item_type=CompletionItemType.COLUMN, + description=ref.name, + ) + ) + except (AttributeError, TypeError): + pass + + for ref in scope.join_tables: + if ref.table: + qualifier = ref.alias if ref.alias else ref.name + try: + for col in ref.table.columns: + if col.name and col.name.lower().startswith(prefix_lower): + columns.append( + CompletionItem( + name=f"{qualifier}.{col.name}", + item_type=CompletionItemType.COLUMN, + description=ref.name, + ) + ) + except (AttributeError, TypeError): + pass + + if include_database_columns and self._database: + in_scope_table_names = set() + if self._current_table: + in_scope_table_names.add(self._current_table.name.lower()) + for ref in scope.from_tables + scope.join_tables: + in_scope_table_names.add(ref.name.lower()) + + try: + for table in self._database.tables: + if table.name.lower() not in in_scope_table_names: + try: + for col in table.columns: + if col.name and col.name.lower().startswith( + prefix_lower + ): + database_columns.append( + CompletionItem( + name=f"{table.name}.{col.name}", + item_type=CompletionItemType.COLUMN, + description=table.name, + ) + ) + except (AttributeError, TypeError): + pass + except (AttributeError, TypeError): + pass + + if ( + context == SQLContext.SELECT_LIST + and not scope.from_tables + and not scope.join_tables + and database_columns + ): + column_names = { + item.name.split(".", 1)[1].lower() + for item in database_columns + if "." in item.name + } + if len(column_names) == 1: + only_name = next(iter(column_names)) + if "_" in only_name: + database_columns = [] + else: + database_columns = database_columns # Preserve schema order (do NOT sort alphabetically) + + columns.extend(database_columns) + + return columns + + def _get_out_of_scope_table_hints( + self, scope: QueryScope, prefix: str, existing_columns: list[CompletionItem] + ) -> list[CompletionItem]: + if not self._database or not prefix: + return [] + + prefix_lower = prefix.lower() + + has_scope_table_match = any( + ref.name.lower().startswith(prefix_lower) + for ref in scope.from_tables + scope.join_tables + ) + if has_scope_table_match: + return [] + + in_scope_table_names = { + ref.name.lower() for ref in scope.from_tables + scope.join_tables + } + + has_scope_column_match = False + for col in existing_columns: + if col.item_type != CompletionItemType.COLUMN: + continue + + column_name = col.name + if "." in column_name: + parts = column_name.split(".", 1) + if len(parts) == 2: + table_part, col_part = parts + if ( + table_part.lower() in in_scope_table_names + and col_part.lower().startswith(prefix_lower) + ): + has_scope_column_match = True + break + continue + + if column_name.lower().startswith(prefix_lower): + has_scope_column_match = True + break + + if has_scope_column_match: + return [] + + hints = [] + try: + for table in self._database.tables: + if ( + table.name.lower().startswith(prefix_lower) + and table.name.lower() not in in_scope_table_names + ): + hints.append( + CompletionItem( + name=f"{table.name} (+ Add via FROM/JOIN)", + item_type=CompletionItemType.TABLE, + description="", + ) + ) + except (AttributeError, TypeError): + pass + + return hints + + def _is_exact_alias_match(self, prefix: str, scope: QueryScope) -> bool: + return prefix.lower() in scope.aliases + + def _get_alias_columns(self, alias: str, scope: QueryScope) -> list[CompletionItem]: + ref = scope.aliases.get(alias.lower()) + if not ref or not ref.table: + return [] + + qualifier = ref.alias if ref.alias else ref.name + + try: + columns = [ + CompletionItem( + name=f"{qualifier}.{col.name}", + item_type=CompletionItemType.COLUMN, + description=ref.name, + ) + for col in ref.table.columns + if col.name + ] + return columns + except (AttributeError, TypeError): + return [] + + def _get_current_table_columns( + self, scope: QueryScope, prefix: str + ) -> list[CompletionItem]: + if not self._current_table: + return [] + + qualifier = self._get_table_qualifier(self._current_table.name, scope) + + try: + columns = [ + CompletionItem( + name=f"{qualifier}.{col.name}", + item_type=CompletionItemType.COLUMN, + description=self._current_table.name, + ) + for col in self._current_table.columns + if col.name + ] + except (AttributeError, TypeError): + return [] + + if prefix: + columns = self._filter_columns_by_prefix(columns, prefix) + + return columns + + def _get_from_table_columns( + self, scope: QueryScope, prefix: str + ) -> list[CompletionItem]: + columns = [] + + for ref in scope.from_tables: + if not ref.table: + continue + + qualifier = ref.alias if ref.alias else ref.name + + try: + table_columns = [ + CompletionItem( + name=f"{qualifier}.{col.name}", + item_type=CompletionItemType.COLUMN, + description=ref.name, + ) + for col in ref.table.columns + if col.name + ] + columns.extend(table_columns) + except (AttributeError, TypeError): + continue + + if prefix: + columns = self._filter_columns_by_prefix(columns, prefix) + + return columns + + def _get_join_table_columns( + self, scope: QueryScope, prefix: str + ) -> list[CompletionItem]: + columns = [] + + for ref in scope.join_tables: + if not ref.table: + continue + + qualifier = ref.alias if ref.alias else ref.name + + try: + table_columns = [ + CompletionItem( + name=f"{qualifier}.{col.name}", + item_type=CompletionItemType.COLUMN, + description=ref.name, + ) + for col in ref.table.columns + if col.name + ] + columns.extend(table_columns) + except (AttributeError, TypeError): + continue + + if prefix: + columns = self._filter_columns_by_prefix(columns, prefix) + + return columns + + def _get_database_columns( + self, scope: QueryScope, prefix: str + ) -> list[CompletionItem]: + if not self._database: + return [] + + in_scope_table_names = set() + if self._current_table: + in_scope_table_names.add(self._current_table.name.lower()) + + for ref in scope.from_tables + scope.join_tables: + in_scope_table_names.add(ref.name.lower()) + + columns = [] + + try: + for table in self._database.tables: + if table.name.lower() in in_scope_table_names: + continue + + try: + table_columns = [ + CompletionItem( + name=f"{table.name}.{col.name}", + item_type=CompletionItemType.COLUMN, + description=table.name, + ) + for col in table.columns + if col.name + ] + columns.extend(table_columns) + except (AttributeError, TypeError): + continue + except (AttributeError, TypeError): + return [] + + if prefix: + columns = self._filter_columns_by_prefix(columns, prefix) + + return columns + + def _get_table_qualifier(self, table_name: str, scope: QueryScope) -> str: + table_lower = table_name.lower() + + if table_lower in scope.aliases: + ref = scope.aliases[table_lower] + return ref.alias if ref.alias else ref.name + + return table_name + + def _filter_columns_by_prefix( + self, columns: list[CompletionItem], prefix: str + ) -> list[CompletionItem]: + prefix_lower = prefix.lower() + filtered = [] + + for col in columns: + col_name_lower = col.name.lower() + + if col_name_lower.startswith(prefix_lower): + filtered.append(col) + elif "." in col_name_lower: + parts = col_name_lower.split(".", 1) + if parts[0].startswith(prefix_lower) or parts[1].startswith( + prefix_lower + ): + filtered.append(col) + + return filtered diff --git a/windows/components/stc/sql_templates.py b/windows/components/stc/sql_templates.py new file mode 100644 index 0000000..16ea97d --- /dev/null +++ b/windows/components/stc/sql_templates.py @@ -0,0 +1,75 @@ +from typing import Optional + +from structures.engines.database import SQLDatabase, SQLTable + + +class SQLTemplate: + def __init__(self, name: str, template: str, description: str = ""): + self.name = name + self.template = template + self.description = description + + def render( + self, + database: Optional[SQLDatabase] = None, + table: Optional[SQLTable] = None + ) -> str: + text = self.template + + if table: + text = text.replace("{table}", table.name) + if table.columns: + columns = ", ".join(col.name for col in table.columns[:5]) + text = text.replace("{columns}", columns) + else: + text = text.replace("{table}", "table_name") + text = text.replace("{columns}", "column1, column2") + + text = text.replace("{condition}", "condition") + text = text.replace("{values}", "value1, value2") + + return text + + +SQL_TEMPLATES = [ + SQLTemplate( + name="SELECT *", + template="SELECT * FROM {table}", + description="Select all columns from table" + ), + SQLTemplate( + name="SELECT with WHERE", + template="SELECT {columns}\nFROM {table}\nWHERE {condition}", + description="Select specific columns with condition" + ), + SQLTemplate( + name="INSERT", + template="INSERT INTO {table} ({columns})\nVALUES ({values})", + description="Insert new row" + ), + SQLTemplate( + name="UPDATE", + template="UPDATE {table}\nSET column = value\nWHERE {condition}", + description="Update rows" + ), + SQLTemplate( + name="DELETE", + template="DELETE FROM {table}\nWHERE {condition}", + description="Delete rows" + ), + SQLTemplate( + name="SELECT with JOIN", + template="SELECT t1.*, t2.*\nFROM {table} t1\nJOIN table2 t2 ON t1.id = t2.id", + description="Select with JOIN" + ), + SQLTemplate( + name="SELECT with GROUP BY", + template="SELECT {columns}, COUNT(*)\nFROM {table}\nGROUP BY {columns}", + description="Select with grouping" + ), + SQLTemplate( + name="CREATE TABLE", + template="CREATE TABLE {table} (\n id INTEGER PRIMARY KEY,\n name TEXT NOT NULL\n)", + description="Create new table" + ), +] diff --git a/windows/components/stc/styles.py b/windows/components/stc/styles.py index 7b69500..32ada92 100644 --- a/windows/components/stc/styles.py +++ b/windows/components/stc/styles.py @@ -4,9 +4,20 @@ from helpers import wx_colour_to_hex from windows.components.stc.profiles import SyntaxProfile +from windows.components.stc.theme_loader import ThemeLoader + +_theme_loader: ThemeLoader = None + + +def set_theme_loader(theme_loader: ThemeLoader) -> None: + global _theme_loader + _theme_loader = theme_loader def get_palette() -> dict[str, str]: + if _theme_loader: + return _theme_loader.get_palette() + background = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW) foreground = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT) line_number_background = wx.SystemSettings.GetColour(wx.SYS_COLOUR_3DFACE) @@ -14,10 +25,10 @@ def get_palette() -> dict[str, str]: is_dark = wx.SystemSettings.GetAppearance().IsDark() base = { - "background": background, - "foreground": foreground, - "line_number_background": line_number_background, - "line_number_foreground": line_number_foreground, + "background": wx_colour_to_hex(background), + "foreground": wx_colour_to_hex(foreground), + "line_number_background": wx_colour_to_hex(line_number_background), + "line_number_foreground": wx_colour_to_hex(line_number_foreground), } if is_dark: diff --git a/windows/components/stc/template_menu.py b/windows/components/stc/template_menu.py new file mode 100644 index 0000000..e964fc2 --- /dev/null +++ b/windows/components/stc/template_menu.py @@ -0,0 +1,68 @@ +from typing import Callable, Optional + +import wx +import wx.stc + +from structures.engines.database import SQLDatabase, SQLTable +from windows.components.stc.sql_templates import SQL_TEMPLATES, SQLTemplate + + +class SQLTemplateMenuController: + def __init__( + self, + editor: wx.stc.StyledTextCtrl, + get_database: Callable[[], Optional[SQLDatabase]], + get_current_table: Callable[[], Optional[SQLTable]] + ): + self._editor = editor + self._get_database = get_database + self._get_current_table = get_current_table + + self._editor.Bind(wx.EVT_CONTEXT_MENU, self._on_context_menu) + + def _on_context_menu(self, event: wx.ContextMenuEvent): + menu = wx.Menu() + + template_menu = wx.Menu() + + for template in SQL_TEMPLATES: + item = template_menu.Append(wx.ID_ANY, template.name, template.description) + self._editor.Bind( + wx.EVT_MENU, + lambda evt, t=template: self._insert_template(t), + item + ) + + menu.AppendSubMenu(template_menu, "Insert Template") + menu.AppendSeparator() + + menu.Append(wx.ID_UNDO, "Undo\tCtrl+Z") + menu.Append(wx.ID_REDO, "Redo\tCtrl+Y") + menu.AppendSeparator() + menu.Append(wx.ID_CUT, "Cut\tCtrl+X") + menu.Append(wx.ID_COPY, "Copy\tCtrl+C") + menu.Append(wx.ID_PASTE, "Paste\tCtrl+V") + menu.AppendSeparator() + menu.Append(wx.ID_SELECTALL, "Select All\tCtrl+A") + + self._editor.Bind(wx.EVT_MENU, lambda e: self._editor.Undo(), id=wx.ID_UNDO) + self._editor.Bind(wx.EVT_MENU, lambda e: self._editor.Redo(), id=wx.ID_REDO) + self._editor.Bind(wx.EVT_MENU, lambda e: self._editor.Cut(), id=wx.ID_CUT) + self._editor.Bind(wx.EVT_MENU, lambda e: self._editor.Copy(), id=wx.ID_COPY) + self._editor.Bind(wx.EVT_MENU, lambda e: self._editor.Paste(), id=wx.ID_PASTE) + self._editor.Bind(wx.EVT_MENU, lambda e: self._editor.SelectAll(), id=wx.ID_SELECTALL) + + self._editor.PopupMenu(menu) + menu.Destroy() + + def _insert_template(self, template: SQLTemplate): + database = self._get_database() + table = self._get_current_table() + + text = template.render(database=database, table=table) + + pos = self._editor.GetCurrentPos() + self._editor.InsertText(pos, text) + + self._editor.SetSelection(pos, pos + len(text)) + self._editor.SetCurrentPos(pos + len(text)) diff --git a/windows/components/stc/theme_loader.py b/windows/components/stc/theme_loader.py new file mode 100644 index 0000000..73a8926 --- /dev/null +++ b/windows/components/stc/theme_loader.py @@ -0,0 +1,118 @@ +from pathlib import Path +from typing import Optional + +import wx +import yaml + + +class ThemeLoader: + def __init__(self, themes_dir: Path) -> None: + self._themes_dir = themes_dir + self._current_theme: Optional[dict] = None + self._theme_name: Optional[str] = None + + def load_theme(self, theme_name: str) -> None: + theme_file = self._themes_dir / f"{theme_name}.yml" + if not theme_file.exists(): + raise FileNotFoundError(f"Theme file not found: {theme_file}") + + with open(theme_file, 'r') as f: + self._current_theme = yaml.safe_load(f) + self._theme_name = theme_name + + def get_palette(self) -> dict[str, str]: + if not self._current_theme: + return self._get_default_palette() + + is_dark = wx.SystemSettings.GetAppearance().IsDark() + mode = "dark" if is_dark else "light" + + editor_colors = self._current_theme.get("editor", {}).get(mode, {}) + + palette = {} + for key, value in editor_colors.items(): + if value == "auto": + palette[key] = self._get_system_color(key) + else: + palette[key] = value + + return palette + + def get_autocomplete_colors(self) -> dict[str, str]: + if not self._current_theme: + return self._get_default_autocomplete_colors() + + is_dark = wx.SystemSettings.GetAppearance().IsDark() + mode = "dark" if is_dark else "light" + + return self._current_theme.get("autocomplete", {}).get(mode, {}) + + def _get_system_color(self, key: str) -> str: + color_map = { + "background": wx.SYS_COLOUR_WINDOW, + "foreground": wx.SYS_COLOUR_WINDOWTEXT, + "line_number_background": wx.SYS_COLOUR_3DFACE, + "line_number_foreground": wx.SYS_COLOUR_GRAYTEXT, + } + + if key in color_map: + color = wx.SystemSettings.GetColour(color_map[key]) + return f"#{color.Red():02x}{color.Green():02x}{color.Blue():02x}" + + return "#000000" + + def _get_default_palette(self) -> dict[str, str]: + is_dark = wx.SystemSettings.GetAppearance().IsDark() + + if is_dark: + return { + "background": self._get_system_color("background"), + "foreground": self._get_system_color("foreground"), + "line_number_background": self._get_system_color("line_number_background"), + "line_number_foreground": self._get_system_color("line_number_foreground"), + "keyword": "#569cd6", + "string": "#ce9178", + "comment": "#6a9955", + "number": "#b5cea8", + "operator": self._get_system_color("foreground"), + "property": "#9cdcfe", + "error": "#f44747", + "uri": "#4ec9b0", + "reference": "#4ec9b0", + "document": "#c586c0", + } + + return { + "background": self._get_system_color("background"), + "foreground": self._get_system_color("foreground"), + "line_number_background": self._get_system_color("line_number_background"), + "line_number_foreground": self._get_system_color("line_number_foreground"), + "keyword": "#0000ff", + "string": "#990099", + "comment": "#007f00", + "number": "#ff6600", + "operator": "#000000", + "property": "#0033aa", + "error": "#cc0000", + "uri": "#006666", + "reference": "#006666", + "document": "#7a1fa2", + } + + def _get_default_autocomplete_colors(self) -> dict[str, str]: + is_dark = wx.SystemSettings.GetAppearance().IsDark() + + if is_dark: + return { + "keyword": "#569cd6", + "function": "#dcdcaa", + "table": "#4ec9b0", + "column": "#9cdcfe", + } + + return { + "keyword": "#0000ff", + "function": "#800080", + "table": "#008000", + "column": "#000000", + } diff --git a/windows/connections/manager.py b/windows/connections/manager.py deleted file mode 100644 index c446c61..0000000 --- a/windows/connections/manager.py +++ /dev/null @@ -1,264 +0,0 @@ -from typing import Optional -from gettext import gettext as _ - -import wx - -from helpers.logger import logger -from helpers.loader import Loader - -from structures.session import Session -from structures.connection import Connection, ConnectionEngine - -from windows import ConnectionsDialog -from windows.main import SESSIONS_LIST, CURRENT_SESSION - -from windows.connections import CURRENT_CONNECTION, PENDING_CONNECTION, ConnectionDirectory, CURRENT_DIRECTORY -from windows.connections.model import ConnectionModel -from windows.connections.controller import ConnectionsTreeController -from windows.connections.repository import ConnectionsRepository - - -class ConnectionsManager(ConnectionsDialog): - _app = wx.GetApp() - _repository = ConnectionsRepository() - - def __init__(self, parent): - super().__init__(parent) - self.engine.SetItems([e.name for e in ConnectionEngine.get_all()]) - - self.connections_tree_controller = ConnectionsTreeController(self.connections_tree_ctrl, self._repository) - self.connections_tree_controller.on_item_activated = lambda connection: self.on_open(None) - - self.connections_model = ConnectionModel() - self.connections_model.bind_controls( - name=self.name, - engine=self.engine, - hostname=self.hostname, port=self.port, - username=self.username, password=self.password, - filename=self.filename, - comments=self.comments, - ssh_tunnel_enabled=self.ssh_tunnel_enabled, ssh_tunnel_executable=self.ssh_tunnel_executable, - ssh_tunnel_hostname=self.ssh_tunnel_hostname, ssh_tunnel_port=self.ssh_tunnel_port, - ssh_tunnel_username=self.ssh_tunnel_username, ssh_tunnel_password=self.ssh_tunnel_password, - ssh_tunnel_local_port=self.ssh_tunnel_local_port, - ) - - self.connections_model.engine.subscribe(self._on_change_engine) - self.connections_model.ssh_tunnel_enabled.subscribe(self._on_change_ssh_tunnel) - - self._setup_event_handlers() - - def _on_current_directory(self, directory: Optional[ConnectionDirectory]): - self.btn_delete.Enable(bool(directory)) - self.btn_create_directory.Enable(not bool(directory)) - - def _on_current_connection(self, connection: Optional[Connection]): - self.btn_open.Enable(bool(connection and connection.is_valid)) - self.btn_delete.Enable(bool(connection)) - - def _on_pending_connection(self, connection: Connection): - item = self.connections_tree_controller.model.ObjectToItem(connection) - if item.IsOk(): - self.connections_tree_controller.model.ItemChanged(item) - - self.btn_save.Enable(bool(connection and connection.is_valid)) - self.btn_test.Enable(bool(connection and connection.is_valid)) - self.btn_open.Enable(bool(connection and connection.is_valid)) - - def _on_connection_activated(self, connection: Connection): - CURRENT_CONNECTION(connection) - # self._app.main_frame.show() - self.on_open(None) - - def _on_change_engine(self, value: str): - connection_engine = ConnectionEngine.from_name(value) - - self.panel_credentials.Show(connection_engine in [ConnectionEngine.MYSQL, ConnectionEngine.MARIADB, ConnectionEngine.POSTGRESQL]) - self.panel_ssh_tunnel.Show(connection_engine in [ConnectionEngine.MYSQL, ConnectionEngine.MARIADB, ConnectionEngine.POSTGRESQL]) - - self.panel_source.Show(connection_engine == ConnectionEngine.SQLITE) - - self.panel_source.GetParent().Layout() - - def _on_change_ssh_tunnel(self, enable: bool): - self.panel_ssh_tunnel.Show(enable) - self.panel_ssh_tunnel.Enable(enable) - self.panel_ssh_tunnel.GetParent().Layout() - - def _on_delete_connection(self, event): - connection = self.connections_tree_ctrl.get_selected_connection() - if connection: - if wx.MessageBox(_(f"Are you sure you want to delete connection '{connection.name}'?"), - "Confirm Delete", wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION) == wx.YES: - self._repository.delete_item(connection) - self.connections_tree_ctrl.remove_connection(connection) - - def _on_search_changed(self, event): - search_text = self.search_connection.GetValue() - self.connections_tree_controller.do_filter_connections(search_text) - - def _setup_event_handlers(self): - self.Bind(wx.EVT_CLOSE, self.on_exit) - self.search_connection.Bind(wx.EVT_TEXT, self._on_search_changed) - - CURRENT_DIRECTORY.subscribe(self._on_current_directory) - CURRENT_CONNECTION.subscribe(self._on_current_connection) - PENDING_CONNECTION.subscribe(self._on_pending_connection) - - def do_open_session(self, session : Session): - # CONNECTIONS_LIST.append(connection) - - SESSIONS_LIST.append(session) - CURRENT_SESSION(session) - - if not self.GetParent(): - # CURRENT_CONNECTION(connection) - self._app.open_main_frame() - - self.Hide() - - def on_save(self, *args): - connection = PENDING_CONNECTION.get_value() - if not connection: - return False - - dialog = wx.MessageDialog(None, - message=_(f'Do you want save the connection {connection.name}?'), - caption=_("Confirm save"), - style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION - ) - - if dialog.ShowModal() != wx.ID_YES: - return False - - parent_obj = None - parent_item = None - selected_item = self.connections_tree_ctrl.GetSelection() - if selected_item.IsOk(): - selected_obj = self.connections_tree_controller.model.ItemToObject(selected_item) - if isinstance(selected_obj, ConnectionDirectory): - parent_obj = selected_obj - parent_item = selected_item - - if connection.is_new: - self._repository.add_connection(connection, parent_obj) - else: - self._repository.save_connection(connection) - - PENDING_CONNECTION(None) - - - item_new_connection = self.connections_tree_controller.model.ObjectToItem(connection) - - print("item_new_connection",item_new_connection) - - self.connections_tree_ctrl.Select(item_new_connection) - - if parent_item : - self.connections_tree_ctrl.Expand(parent_item) - - CURRENT_CONNECTION(connection) - - - return True - - def on_create_connection(self, event): - self.connections_manager_model.do_create_connection() - - def on_create_directory(self, event): - # Get selected directory - parent = None - selected_item = self.connections_tree_ctrl.GetSelection() - if selected_item.IsOk(): - obj = self.connections_tree_controller.model.ItemToObject(selected_item) - if isinstance(obj, ConnectionDirectory): - return - new_dir = ConnectionDirectory(name=_("New directory")) - self._repository.add_directory(new_dir, parent) - # Select and edit - item = self.connections_tree_controller.model.ObjectToItem(new_dir) - if item.IsOk(): - self.connections_tree_ctrl.Select(item) - self.connections_tree_ctrl.EditItem(item, self.connections_tree_ctrl.GetColumn(0)) - # Expand parent - if parent: - parent_item = self.connections_tree_controller.model.ObjectToItem(parent) - self.connections_tree_ctrl.Expand(parent_item) - - def verify_connection(self, session: Session): - with Loader.cursor_wait(): - try: - session.connect(connect_timeout=10) - except Exception as ex: - wx.MessageDialog(None, - message=_(f'Connection error:\n{str(ex)}'), - caption=_("Connection error"), - style=wx.OK | wx.OK_DEFAULT | wx.ICON_ERROR).ShowModal() - raise ConnectionError(ex) - - def on_open(self, event): - if PENDING_CONNECTION() and not self.on_save(event): - return - - connection = CURRENT_CONNECTION() - - session = Session(connection) - - try: - self.verify_connection(session) - except ConnectionError as ex: - logger.info(ex) - except Exception as ex: - logger.error(ex, exc_info=True) - else: - self.do_open_session(session) - - def on_delete_connection(self, connection: Connection): - dialog = wx.MessageDialog(None, - message=_(f'Do you want delete the {_("connection")} {connection.name}?'), - caption=_(f"Confirm delete"), - style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION - ) - - if dialog.ShowModal() == wx.ID_YES: - PENDING_CONNECTION(None) - CURRENT_CONNECTION(None) - self._repository.delete_connection(connection) - self._repository.load() - - dialog.Destroy() - - def on_delete_directory(self, directory: ConnectionDirectory): - dialog = wx.MessageDialog(None, - message=_(f'Do you want delete the {_("directory")} {directory.name}?'), - caption=_(f"Confirm delete"), - style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION - ) - - if dialog.ShowModal() == wx.ID_YES: - PENDING_CONNECTION(None) - CURRENT_CONNECTION(None) - self._repository.delete_directory(directory) - # self._repository.refresh() - - dialog.Destroy() - - def on_delete(self, *args): - selected_item = self.connections_tree_ctrl.GetSelection() - if not selected_item.IsOk(): - return - - obj = self.connections_tree_controller.model.ItemToObject(selected_item) - - if isinstance(obj, Connection): - self.on_delete_connection(obj) - elif isinstance(obj, ConnectionDirectory): - self.on_delete_directory(obj) - - def on_exit(self, event): - if not self._app.main_frame: - self._app.do_exit(event) - else: - self.Hide() - - event.Skip() diff --git a/windows/connections/model.py b/windows/connections/model.py deleted file mode 100644 index cbee7de..0000000 --- a/windows/connections/model.py +++ /dev/null @@ -1,158 +0,0 @@ -from gettext import gettext as _ - -from helpers import wx_call_after_debounce -from helpers.bindings import AbstractModel -from helpers.observables import Observable, CallbackEvent - -from structures.connection import Connection, ConnectionEngine -from structures.configurations import CredentialsConfiguration, SourceConfiguration, SSHTunnelConfiguration - -from . import CURRENT_CONNECTION, PENDING_CONNECTION - - -class ConnectionModel(AbstractModel): - def __init__(self): - self.name = Observable[str]() - self.engine = Observable[str](initial=ConnectionEngine.MYSQL.value.name) - self.hostname = Observable[str]() - self.username = Observable[str]() - self.password = Observable[str]() - self.port = Observable[int](initial=3306) - self.filename = Observable[str]() - self.comments = Observable[str]("") - - self.ssh_tunnel_enabled = Observable[bool](initial=False) - self.ssh_tunnel_executable = Observable[str](initial="ssh") - self.ssh_tunnel_hostname = Observable[str]() - self.ssh_tunnel_port = Observable[int](initial=22) - self.ssh_tunnel_username = Observable[str]() - self.ssh_tunnel_password = Observable[str]() - self.ssh_tunnel_local_port = Observable[int](initial=3307) - - self.engine.subscribe(self._set_default_port) - - wx_call_after_debounce( - self.name, self.engine, self.hostname, self.username, self.password, self.port, - self.filename, self.comments, - self.ssh_tunnel_enabled, self.ssh_tunnel_executable, self.ssh_tunnel_hostname, - self.ssh_tunnel_port, self.ssh_tunnel_username, self.ssh_tunnel_password, self.ssh_tunnel_local_port, - callback=self._build - ) - - CURRENT_CONNECTION.subscribe(self.clear, CallbackEvent.BEFORE_CHANGE) - CURRENT_CONNECTION.subscribe(self.apply, CallbackEvent.AFTER_CHANGE) - - def _set_default_port(self, connection_engine_name: str): - connection_engine = ConnectionEngine.from_name(connection_engine_name) - if connection_engine == ConnectionEngine.POSTGRESQL: - self.port(5432) - elif connection_engine in [ConnectionEngine.MYSQL, ConnectionEngine.MARIADB]: - self.port(3306) - - def clear(self, *args): - defaults = { - self.name: None, - self.engine: ConnectionEngine.MYSQL.value.name, - self.hostname: None, self.username: None, self.password: None, self.port: 3306, - self.filename: None, - self.comments: None, - self.ssh_tunnel_enabled: False, self.ssh_tunnel_executable: "ssh", self.ssh_tunnel_hostname: None, - self.ssh_tunnel_port: 22, self.ssh_tunnel_username: None, self.ssh_tunnel_password: None, - self.ssh_tunnel_local_port: 3307, - } - - for observable, value in defaults.items(): - observable(value) - - def apply(self, connection: Connection): - if not connection: - return - - self.name(connection.name) - - if connection.engine is not None: - self.engine(connection.engine.value.name) - - self.comments(connection.comments) - - if isinstance(connection.configuration, CredentialsConfiguration): - self.hostname(connection.configuration.hostname) - self.username(connection.configuration.username) - self.password(connection.configuration.password) - self.port(connection.configuration.port) - - elif isinstance(connection.configuration, SourceConfiguration): - self.filename(connection.configuration.filename) - - if ssh_tunnel := connection.ssh_tunnel: - self.ssh_tunnel_enabled(ssh_tunnel.enabled) - self.ssh_tunnel_executable(ssh_tunnel.executable) - self.ssh_tunnel_hostname(ssh_tunnel.hostname) - self.ssh_tunnel_port(ssh_tunnel.port) - self.ssh_tunnel_username(ssh_tunnel.username) - self.ssh_tunnel_password(ssh_tunnel.password) - self.ssh_tunnel_local_port(ssh_tunnel.local_port) - else: - self.ssh_tunnel_enabled(False) - - def _build_empty_connection(self): - return Connection( - id=-1, - name=self.name() or _("New connection"), - engine=ConnectionEngine.MYSQL, - configuration=CredentialsConfiguration( - hostname="localhost", - username="root", - password="", - port=3306 - ) - ) - - def _build(self, *args): - if any([self.name.is_empty, self.engine.is_empty]): - return - - current_connection = CURRENT_CONNECTION() - pending_connection = PENDING_CONNECTION() - - if not pending_connection: - pending_connection = current_connection.copy() if current_connection else self._build_empty_connection() - - connection_engine = ConnectionEngine.from_name(self.engine()) - - pending_connection.name = self.name() or "" - pending_connection.engine = connection_engine - pending_connection.comments = self.comments() - - if connection_engine in [ConnectionEngine.MYSQL, ConnectionEngine.MARIADB, ConnectionEngine.POSTGRESQL]: - pending_connection.configuration = CredentialsConfiguration( - hostname=self.hostname.get_value() or "localhost", - username=self.username.get_value() or "root", - password=self.password.get_value() or "", - port=self.port.get_value() or 3306 - ) - - if ssh_tunnel_enabled := bool(self.ssh_tunnel_enabled()): - pending_connection.ssh_tunnel = SSHTunnelConfiguration( - enabled=ssh_tunnel_enabled, - executable=self.ssh_tunnel_executable.get_value() or "ssh", - hostname=self.ssh_tunnel_hostname.get_value() or "", - port=self.ssh_tunnel_port.get_value() or 22, - username=self.ssh_tunnel_username.get_value(), - password=self.ssh_tunnel_password.get_value(), - local_port=self.ssh_tunnel_local_port.get_value(), - ) - - elif connection_engine == ConnectionEngine.SQLITE: - pending_connection.configuration = SourceConfiguration( - filename=self.filename() - ) - pending_connection.ssh_tunnel = None - - if not pending_connection.is_valid: - return - - if pending_connection == current_connection: - return - - PENDING_CONNECTION(pending_connection) diff --git a/windows/connections/repository.py b/windows/connections/repository.py deleted file mode 100644 index c2b178c..0000000 --- a/windows/connections/repository.py +++ /dev/null @@ -1,180 +0,0 @@ -import os -from typing import Any, Optional, Union - -import yaml - -from helpers.observables import ObservableList, ObservableLazyList - -from windows.connections import ConnectionDirectory - -from structures.connection import ( - Connection, - ConnectionEngine, - CredentialsConfiguration, - SourceConfiguration, - SSHTunnelConfiguration, -) - -WORKDIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) - -CONNECTIONS_CONFIG_FILE = os.path.join(WORKDIR, "connections.yml") - - -class ConnectionsRepository: - def __init__(self, config_file: Optional[str] = None): - self._config_file = config_file or CONNECTIONS_CONFIG_FILE - self._id_counter = 0 - - self.connections = ObservableLazyList(self.load) - - def _next_id(self): - id = self._id_counter - self._id_counter += 1 - return id - - def _read(self) -> list[dict[str, Any]]: - try: - connections = yaml.full_load(open(self._config_file)) - return connections or [] - except Exception: - return [] - - def _write(self) -> None: - connections = self.connections.get_value() - payload = [item.to_dict() for item in connections] - with open(self._config_file, 'w') as file_handler: - yaml.dump(payload, file_handler, sort_keys=False) - - def load(self) -> list[Union[ConnectionDirectory, Connection]]: - return [self._item_from_dict(data) for data in self._read()] - - def _item_from_dict(self, data: dict[str, Any], parent: Optional[ConnectionDirectory] = None) -> Union[ConnectionDirectory, Connection]: - if data.get('type') == 'directory': - directory = ConnectionDirectory(name=data['name'], children=[]) - children = [self._item_from_dict(child_data, directory) for child_data in data.get('children', [])] - directory.children = children - return directory - else: - return self._connection_from_dict(data) - - def _connection_from_dict(self, data: dict[str, Any]) -> Connection: - engine = ConnectionEngine.from_name(data.get('engine', ConnectionEngine.MYSQL.value.name)) - - configuration: Optional[Union[CredentialsConfiguration, SourceConfiguration]] = None - - if data.get('configuration'): - config_data = data['configuration'] - if engine in [ConnectionEngine.MYSQL, ConnectionEngine.MARIADB, ConnectionEngine.POSTGRESQL]: - configuration = CredentialsConfiguration(**config_data) - elif engine == ConnectionEngine.SQLITE: - configuration = SourceConfiguration(**config_data) - - ssh_config = self._build_ssh_configuration(data.get('ssh_tunnel', {})) - - if data.get("id") is not None: - self._id_counter = max(self._id_counter, data["id"] + 1) - - comments = data.get('comments') - if comments is None: - comments = "" - - return Connection( - id=data["id"], - name=data['name'], - engine=engine, - configuration=configuration, - comments=comments, - ssh_tunnel=ssh_config, - ) - - def add_connection(self, connection: Connection, parent: Optional[ConnectionDirectory] = None) -> int: - - self.connections.get_value() - - if connection.is_new: - connection.id = self._next_id() - - if parent: - parent.children.append(connection) - else: - self.connections.append(connection) - - self._write() - self.connections.refresh() - - return connection.id - - def save_connection(self, connection: Connection) -> int: - self.connections.get_value() - - def _find_and_replace(connections, target_id): - for i, item in enumerate(connections): - if isinstance(item, ConnectionDirectory): - if _find_and_replace(item.children, target_id): - return True - elif isinstance(item, Connection) and item.id == target_id: - connections[i] = connection - return True - return False - - if _find_and_replace(self.connections.get_value(), connection.id): - self._write() - self.connections.refresh() - - return connection.id - - def save_directory(self, directory: ConnectionDirectory) -> None: - self.connections.append(directory, replace_existing=True) - - self._write() - - def add_directory(self, directory: ConnectionDirectory, parent: Optional[ConnectionDirectory] = None) -> None: - self.connections.get_value() - - if parent: - parent.children.append(directory) - else: - self.connections.append(directory) - - self._write() - - def delete_directory(self, directory: ConnectionDirectory): - self.connections.get_value() - - self.connections.remove(directory) - - self._write() - - def delete_connection(self, connection: Connection) -> None: - self.connections.get_value() - - def _find_and_delete(connections, target_id): - for i, item in enumerate(connections): - if isinstance(item, ConnectionDirectory): - if _find_and_delete(item.children, target_id): - return True - elif isinstance(item, Connection) and item.id == target_id: - del connections[i] - return True - return False - - if _find_and_delete(self.connections.get_value(), connection.id): - self._write() - self.connections.refresh() - - def _build_ssh_configuration(self, data: dict[str, Any]) -> Optional[SSHTunnelConfiguration]: - if not data: - return None - - try: - return SSHTunnelConfiguration( - enabled=bool(data.get('enabled')), - executable=data.get('executable', 'ssh'), - hostname=data.get('hostname', ''), - port=int(data.get('port', 22)), - username=data.get('username', ''), - password=data.get('password', ''), - local_port=int(data.get('local_port', 0)), - ) - except (TypeError, ValueError): - return None diff --git a/windows/dialogs/__init__.py b/windows/dialogs/__init__.py new file mode 100644 index 0000000..622e1d3 --- /dev/null +++ b/windows/dialogs/__init__.py @@ -0,0 +1,7 @@ +from windows.dialogs.connections.view import ConnectionsManager +from windows.dialogs.settings.controller import SettingsController + +__all__ = [ + "ConnectionsManager", + "SettingsController", +] diff --git a/windows/dialogs/advanced_cell_editor/__init__.py b/windows/dialogs/advanced_cell_editor/__init__.py new file mode 100644 index 0000000..c844cb4 --- /dev/null +++ b/windows/dialogs/advanced_cell_editor/__init__.py @@ -0,0 +1,3 @@ +from windows.dialogs.advanced_cell_editor.controller import AdvancedCellEditorController + +__all__ = ["AdvancedCellEditorController"] diff --git a/windows/dialogs/advanced_cell_editor/controller.py b/windows/dialogs/advanced_cell_editor/controller.py new file mode 100644 index 0000000..15ceb12 --- /dev/null +++ b/windows/dialogs/advanced_cell_editor/controller.py @@ -0,0 +1,60 @@ +import wx + +from windows.components.stc.detectors import detect_syntax_id +from windows.components.stc.profiles import SyntaxProfile +from windows.components.stc.styles import apply_stc_theme +from windows.views import AdvancedCellEditorDialog + + +class AdvancedCellEditorController(AdvancedCellEditorDialog): + app = wx.GetApp() + + def __init__(self, parent, value: str): + super().__init__(parent) + + self.syntax_choice.AppendItems(self.app.syntax_registry.labels()) + self.advanced_stc_editor.SetText(value or "") + self.advanced_stc_editor.EmptyUndoBuffer() + + self.app.theme_manager.register(self.advanced_stc_editor, self._get_current_syntax_profile) + + self.syntax_choice.SetStringSelection(self._auto_syntax_profile().label) + + self.do_apply_syntax(do_format=True) + + def _auto_syntax_profile(self) -> SyntaxProfile: + text = self.advanced_stc_editor.GetText() + + syntax_id = detect_syntax_id(text) + return self.app.syntax_registry.get(syntax_id) + + def _get_current_syntax_profile(self) -> SyntaxProfile: + label = self.syntax_choice.GetStringSelection() + return self.app.syntax_registry.get(label) + + def on_syntax_changed(self, _evt): + label = self.syntax_choice.GetStringSelection() + self.do_apply_syntax(label) + + def do_apply_syntax(self, do_format: bool = True): + label = self.syntax_choice.GetStringSelection() + syntax_profile = self.app.syntax_registry.by_label(label) + + apply_stc_theme(self.advanced_stc_editor, syntax_profile) + + if do_format and syntax_profile.formatter: + old = self.advanced_stc_editor.GetText() + try: + formatted = syntax_profile.formatter(old) + except Exception: + return + + if formatted != old: + self._replace_text_undo_friendly(formatted) + + def _replace_text_undo_friendly(self, new_text: str): + self.advanced_stc_editor.BeginUndoAction() + try: + self.advanced_stc_editor.SetText(new_text) + finally: + self.advanced_stc_editor.EndUndoAction() diff --git a/windows/connections/__init__.py b/windows/dialogs/connections/__init__.py similarity index 100% rename from windows/connections/__init__.py rename to windows/dialogs/connections/__init__.py diff --git a/windows/connections/controller.py b/windows/dialogs/connections/controller.py similarity index 68% rename from windows/connections/controller.py rename to windows/dialogs/connections/controller.py index 4c916f6..7acfbb8 100644 --- a/windows/connections/controller.py +++ b/windows/dialogs/connections/controller.py @@ -8,11 +8,10 @@ from structures.connection import Connection from . import CURRENT_CONNECTION, ConnectionDirectory, CURRENT_DIRECTORY -from windows.connections.repository import ConnectionsRepository +from windows.dialogs.connections.repository import ConnectionsRepository class ConnectionsTreeModel(BaseDataViewTreeModel): - def __init__(self): super().__init__(column_count=2) self._parent_map = {} @@ -51,7 +50,12 @@ def GetValue(self, item, col): if isinstance(node, Connection): bitmap = node.engine.value.bitmap - mapper = {0: wx.dataview.DataViewIconText(node.name, wx.GetApp().icon_registry_16.get_bitmap(bitmap) ), 1: ""} + mapper = { + 0: wx.dataview.DataViewIconText( + node.name, wx.GetApp().icon_registry_16.get_bitmap(bitmap) + ), + 1: node.last_connection_at or "", + } elif isinstance(node, ConnectionDirectory): mapper = {0: wx.dataview.DataViewIconText(node.name), 1: ""} else: @@ -60,73 +64,89 @@ def GetValue(self, item, col): return mapper[col] def SetValue(self, variant, item, col): - print("SetValue") node = self.ItemToObject(item) if isinstance(node, ConnectionDirectory): node.name = variant.GetText() - # self.repository.save_directory(node) if isinstance(node, Connection): node.name = variant.GetText() - # self.repository.save_connection(node) return True -class ConnectionsTreeController(): +class ConnectionsTreeController: on_selection_chance: Callable[[Optional[Any]], Optional[Any]] = None on_item_activated: Callable[[Optional[Any]], Optional[Any]] = None - def __init__(self, connections_tree_ctrl: wx.dataview.DataViewCtrl, repository: ConnectionsRepository): + def __init__( + self, + connections_tree_ctrl: wx.dataview.DataViewCtrl, + repository: ConnectionsRepository, + ): self.connections_tree_ctrl = connections_tree_ctrl self.repository = repository self.model = ConnectionsTreeModel() self.model.set_observable(self.repository.connections) self.connections_tree_ctrl.AssociateModel(self.model) - - self.connections_tree_ctrl.Bind(wx.dataview.EVT_DATAVIEW_ITEM_EDITING_DONE, self._on_item_editing_done) - self.connections_tree_ctrl.Bind(wx.dataview.EVT_DATAVIEW_ITEM_START_EDITING, self._on_item_start_editing) - - self.connections_tree_ctrl.Bind(wx.dataview.EVT_DATAVIEW_SELECTION_CHANGED, self._on_selection_changed) - self.connections_tree_ctrl.Bind(wx.dataview.EVT_DATAVIEW_ITEM_ACTIVATED, self._on_item_activated) + self._allow_next_edit = False + + self.connections_tree_ctrl.Bind( + wx.dataview.EVT_DATAVIEW_ITEM_EDITING_DONE, self._on_item_editing_done + ) + self.connections_tree_ctrl.Bind( + wx.dataview.EVT_DATAVIEW_ITEM_START_EDITING, self._on_item_start_editing + ) + + self.connections_tree_ctrl.Bind( + wx.dataview.EVT_DATAVIEW_SELECTION_CHANGED, self._on_selection_changed + ) + self.connections_tree_ctrl.Bind( + wx.dataview.EVT_DATAVIEW_ITEM_ACTIVATED, self._on_item_activated + ) CURRENT_CONNECTION.subscribe(self._on_current_connection) def _on_item_editing_done(self, event): item = event.GetItem() - if not item.IsOk: + if not item.IsOk(): return obj = self.model.ItemToObject(item) if isinstance(obj, ConnectionDirectory): self.repository.save_directory(obj) + CURRENT_DIRECTORY(obj) elif isinstance(obj, Connection): self.repository.save_connection(obj) + CURRENT_CONNECTION(obj) - def _on_item_start_editing(self, event): - item = event.GetItem() + self.model.ItemChanged(item) - if not item.IsOk: + def _on_item_start_editing(self, event): + if not self._allow_next_edit: + event.Veto() return - obj = self.model.ItemToObject(item) - - if isinstance(obj, Connection): - event.Veto() + self._allow_next_edit = False def do_filter_connections(self, search_text): # self.search_text = search_text # self._update_displayed_connections() - self.repository.connections.filter(lambda x: search_text.lower() in x.name.lower()) + self.repository.connections.filter( + lambda x: search_text.lower() in x.name.lower() + ) def _on_selection_changed(self, event): item = event.GetItem() + if not item.IsOk(): + item = self.connections_tree_ctrl.GetSelection() if not item.IsOk(): + CURRENT_DIRECTORY(None) + CURRENT_CONNECTION(None) return CURRENT_DIRECTORY(None) @@ -157,17 +177,25 @@ def _on_item_activated(self, event): return elif isinstance(obj, Connection): - CURRENT_CONNECTION(None)(obj) + CURRENT_CONNECTION(obj) if self.on_item_activated: self.on_item_activated(obj) event.Skip() + def edit_item(self, item: wx.dataview.DataViewItem): + if not item.IsOk(): + return + + self._allow_next_edit = True + self.connections_tree_ctrl.EditItem( + item, self.connections_tree_ctrl.GetColumn(0) + ) + def _on_current_connection(self, connection: Optional[Connection]): if connection: item = self.model.ObjectToItem(connection) - if not self.connections_tree_ctrl.IsSelected(item) : - print("select") + if not self.connections_tree_ctrl.IsSelected(item): self.connections_tree_ctrl.Select(item) - self.connections_tree_ctrl.EnsureVisible(item) \ No newline at end of file + self.connections_tree_ctrl.EnsureVisible(item) diff --git a/windows/dialogs/connections/model.py b/windows/dialogs/connections/model.py new file mode 100644 index 0000000..bd9624e --- /dev/null +++ b/windows/dialogs/connections/model.py @@ -0,0 +1,270 @@ +from gettext import gettext as _ + +from helpers.bindings import AbstractModel, wx_call_after_debounce +from helpers.observables import Observable, CallbackEvent + +from structures.connection import Connection, ConnectionEngine +from structures.configurations import ( + CredentialsConfiguration, + SourceConfiguration, + SSHTunnelConfiguration, +) + +from . import CURRENT_CONNECTION, PENDING_CONNECTION + + +class ConnectionModel(AbstractModel): + def __init__(self): + self.name = Observable[str]() + self.engine = Observable[str](initial=ConnectionEngine.MYSQL.value.name) + self.hostname = Observable[str]() + self.username = Observable[str]() + self.password = Observable[str]() + self.use_tls_enabled = Observable[bool](initial=False) + self.port = Observable[int](initial=3306) + self.filename = Observable[str]() + self.comments = Observable[str]("") + + self.created_at = Observable[str]("") + self.last_connection_at = Observable[str]("") + self.successful_connected = Observable[str]("0") + self.unsuccessful_connections = Observable[str]("0") + self.last_successful_connection = Observable[str]("") + self.last_failure_raison = Observable[str]("") + self.total_connection_attempts = Observable[str]("0") + self.average_connection_time = Observable[str]("") + self.most_recent_connection_duration = Observable[str]("") + + self.ssh_tunnel_enabled = Observable[bool](initial=False) + self.ssh_tunnel_executable = Observable[str](initial="ssh") + self.ssh_tunnel_hostname = Observable[str]() + self.ssh_tunnel_port = Observable[int](initial=22) + self.ssh_tunnel_username = Observable[str]() + self.ssh_tunnel_password = Observable[str]() + self.ssh_tunnel_local_port = Observable[int](initial=3307) + self.ssh_tunnel_identity_file = Observable[str]() + self.ssh_tunnel_remote_hostname = Observable[str]() + self.ssh_tunnel_remote_port = Observable[int](initial=3306) + + self.engine.subscribe(self._set_default_port) + + wx_call_after_debounce( + self.name, + self.engine, + self.hostname, + self.username, + self.password, + self.use_tls_enabled, + self.port, + self.filename, + self.comments, + self.created_at, + self.last_connection_at, + self.successful_connected, + self.unsuccessful_connections, + self.last_successful_connection, + self.last_failure_raison, + self.total_connection_attempts, + self.average_connection_time, + self.most_recent_connection_duration, + self.ssh_tunnel_enabled, + self.ssh_tunnel_executable, + self.ssh_tunnel_hostname, + self.ssh_tunnel_port, + self.ssh_tunnel_username, + self.ssh_tunnel_password, + self.ssh_tunnel_local_port, + self.ssh_tunnel_identity_file, + self.ssh_tunnel_remote_hostname, + self.ssh_tunnel_remote_port, + callback=self._build, + ) + + CURRENT_CONNECTION.subscribe(self.clear, CallbackEvent.BEFORE_CHANGE) + CURRENT_CONNECTION.subscribe(self.apply, CallbackEvent.AFTER_CHANGE) + + def _set_default_port(self, connection_engine_name: str): + connection_engine = ConnectionEngine.from_name(connection_engine_name) + if connection_engine == ConnectionEngine.POSTGRESQL: + self.port(5432) + elif connection_engine in [ConnectionEngine.MYSQL, ConnectionEngine.MARIADB]: + self.port(3306) + + def clear(self, *args): + defaults = { + self.name: None, + self.engine: ConnectionEngine.MYSQL.value.name, + self.hostname: None, + self.username: None, + self.password: None, + self.use_tls_enabled: False, + self.port: 3306, + self.filename: None, + self.comments: None, + self.created_at: "", + self.last_connection_at: "", + self.successful_connected: "0", + self.unsuccessful_connections: "0", + self.last_successful_connection: "", + self.last_failure_raison: "", + self.total_connection_attempts: "0", + self.average_connection_time: "", + self.most_recent_connection_duration: "", + self.ssh_tunnel_enabled: False, + self.ssh_tunnel_executable: "ssh", + self.ssh_tunnel_hostname: None, + self.ssh_tunnel_port: 22, + self.ssh_tunnel_username: None, + self.ssh_tunnel_password: None, + self.ssh_tunnel_local_port: 3307, + self.ssh_tunnel_identity_file: None, + self.ssh_tunnel_remote_hostname: None, + self.ssh_tunnel_remote_port: 3306, + } + + for observable, value in defaults.items(): + observable(value) + + def apply(self, connection: Connection): + if not connection: + return + + self.name(connection.name) + + if connection.engine is not None: + self.engine(connection.engine.value.name) + + self.comments(connection.comments) + self.created_at(connection.created_at or "") + self.last_connection_at(connection.last_connection_at or "") + self.successful_connected(str(connection.successful_connections)) + self.unsuccessful_connections(str(connection.unsuccessful_connections)) + self.last_successful_connection(connection.last_successful_connection_at or "") + self.last_failure_raison(connection.last_failure_reason or "") + self.total_connection_attempts(str(connection.total_connection_attempts)) + self.average_connection_time( + str(connection.average_connection_time_ms) + if connection.average_connection_time_ms is not None + else "" + ) + self.most_recent_connection_duration( + str(connection.most_recent_connection_duration_ms) + if connection.most_recent_connection_duration_ms is not None + else "" + ) + + if isinstance(connection.configuration, CredentialsConfiguration): + self.hostname(connection.configuration.hostname) + self.username(connection.configuration.username) + self.password(connection.configuration.password) + self.use_tls_enabled( + getattr(connection.configuration, "use_tls_enabled", False) + ) + self.port(connection.configuration.port) + + elif isinstance(connection.configuration, SourceConfiguration): + self.filename(connection.configuration.filename) + + if ssh_tunnel := connection.ssh_tunnel: + self.ssh_tunnel_enabled(ssh_tunnel.enabled) + self.ssh_tunnel_executable(ssh_tunnel.executable) + self.ssh_tunnel_hostname(ssh_tunnel.hostname) + self.ssh_tunnel_port(ssh_tunnel.port) + self.ssh_tunnel_username(ssh_tunnel.username) + self.ssh_tunnel_password(ssh_tunnel.password) + self.ssh_tunnel_local_port(ssh_tunnel.local_port) + self.ssh_tunnel_identity_file(getattr(ssh_tunnel, "identity_file", None)) + self.ssh_tunnel_remote_hostname(getattr(ssh_tunnel, "remote_host", None)) + self.ssh_tunnel_remote_port( + getattr(ssh_tunnel, "remote_port", None) or self.port() + ) + else: + self.ssh_tunnel_enabled(False) + + def _build_empty_connection(self): + return Connection( + id=-1, + name=self.name() or _("New connection"), + engine=ConnectionEngine.MYSQL, + configuration=CredentialsConfiguration( + hostname="localhost", username="root", password="", port=3306 + ), + ) + + def _build(self, *args): + if any([self.name.is_empty, self.engine.is_empty]): + return + + current_connection = CURRENT_CONNECTION() + pending_connection = PENDING_CONNECTION() + + if not pending_connection: + pending_connection = ( + current_connection.copy() + if current_connection + else self._build_empty_connection() + ) + + connection_engine = ConnectionEngine.from_name(self.engine()) + + pending_connection.name = self.name() or "" + pending_connection.engine = connection_engine + pending_connection.comments = self.comments() + + if connection_engine in [ + ConnectionEngine.MYSQL, + ConnectionEngine.MARIADB, + ConnectionEngine.POSTGRESQL, + ]: + db_password = self.password.get_value() or "" + db_hostname = (self.hostname.get_value() or "localhost").strip() + db_username = (self.username.get_value() or "root").strip() + pending_connection.configuration = CredentialsConfiguration( + hostname=db_hostname, + username=db_username, + password=db_password, + port=self.port.get_value() or 3306, + use_tls_enabled=bool(self.use_tls_enabled.get_value()), + ) + + if ssh_tunnel_enabled := bool(self.ssh_tunnel_enabled()): + ssh_username = ( + self.ssh_tunnel_username.get_value() or "" + ).strip() or None + remote_host = ( + self.ssh_tunnel_remote_hostname.get_value() or "" + ).strip() or None + remote_port = self.ssh_tunnel_remote_port.get_value() or None + + if remote_host == db_hostname and remote_port == ( + self.port.get_value() or 3306 + ): + remote_host = None + remote_port = None + + pending_connection.ssh_tunnel = SSHTunnelConfiguration( + enabled=ssh_tunnel_enabled, + executable=self.ssh_tunnel_executable.get_value() or "ssh", + hostname=(self.ssh_tunnel_hostname.get_value() or "").strip(), + port=self.ssh_tunnel_port.get_value() or 22, + username=ssh_username, + password=self.ssh_tunnel_password.get_value(), + local_port=self.ssh_tunnel_local_port.get_value() or 0, + identity_file=self.ssh_tunnel_identity_file.get_value() or None, + remote_host=remote_host, + remote_port=remote_port, + ) + + elif connection_engine == ConnectionEngine.SQLITE: + pending_connection.configuration = SourceConfiguration( + filename=self.filename() + ) + pending_connection.ssh_tunnel = None + + if not pending_connection.is_valid: + return + + if pending_connection == current_connection: + return + + PENDING_CONNECTION(pending_connection) diff --git a/windows/dialogs/connections/repository.py b/windows/dialogs/connections/repository.py new file mode 100644 index 0000000..5902c22 --- /dev/null +++ b/windows/dialogs/connections/repository.py @@ -0,0 +1,332 @@ +from pathlib import Path +from typing import Any, Optional, Union + +from constants import WORKDIR +from helpers.logger import logger +from helpers.observables import ObservableLazyList +from helpers.repository import YamlRepository + +from windows.dialogs.connections import ConnectionDirectory + +from structures.connection import ( + Connection, + ConnectionEngine, + CredentialsConfiguration, + SourceConfiguration, + SSHTunnelConfiguration, +) + +CONNECTIONS_CONFIG_FILE = WORKDIR / "connections.yml" + + +class ConnectionsRepository( + YamlRepository[list[Union[ConnectionDirectory, Connection]]] +): + def __init__(self, config_file: Optional[str] = None): + super().__init__(Path(config_file or CONNECTIONS_CONFIG_FILE)) + self._id_counter = 0 + self.connections = ObservableLazyList(self.load) + + def _next_id(self): + id = self._id_counter + self._id_counter += 1 + return id + + def _read(self) -> list[dict[str, Any]]: + data = self._read_yaml() + if isinstance(data, list): + return data + return [] + + def _write(self) -> None: + connections = self.connections.get_value() + payload = [item.to_dict() for item in connections] + self._write_yaml(payload) + + def load(self) -> list[Union[ConnectionDirectory, Connection]]: + data = self._read() + self._id_counter = 0 + return [self._item_from_dict(item) for item in data] + + def _item_from_dict( + self, data: dict[str, Any], parent: Optional[ConnectionDirectory] = None + ) -> Union[ConnectionDirectory, Connection]: + if data.get("type") == "directory": + directory_id = data.get("id") + if directory_id is not None: + self._id_counter = max(self._id_counter, int(directory_id) + 1) + else: + logger.warning( + "Directory '%s' has no id. Fallback id=-1 will be used.", + data.get("name", ""), + ) + directory_id = -1 + + directory = ConnectionDirectory( + id=int(directory_id), + name=data["name"], + children=[], + ) + children = [ + self._item_from_dict(child_data, directory) + for child_data in data.get("children", []) + ] + directory.children = children + return directory + else: + return self._connection_from_dict(data, parent) + + def _connection_from_dict( + self, + data: dict[str, Any], + parent: Optional[ConnectionDirectory] = None, + ) -> Connection: + engine = ConnectionEngine.from_name( + data.get("engine", ConnectionEngine.MYSQL.value.name) + ) + + configuration: Optional[ + Union[CredentialsConfiguration, SourceConfiguration] + ] = None + + if data.get("configuration"): + config_data = data["configuration"] + if engine in [ + ConnectionEngine.MYSQL, + ConnectionEngine.MARIADB, + ConnectionEngine.POSTGRESQL, + ]: + configuration = CredentialsConfiguration(**config_data) + elif engine == ConnectionEngine.SQLITE: + configuration = SourceConfiguration(**config_data) + + ssh_config = self._build_ssh_configuration(data.get("ssh_tunnel", {})) + + if data.get("id") is not None: + self._id_counter = max(self._id_counter, data["id"] + 1) + + comments = data.get("comments") + if comments is None: + comments = "" + + successful_connections = int(data.get("successful_connections", 0) or 0) + unsuccessful_connections = int(data.get("unsuccessful_connections", 0) or 0) + total_connection_attempts = int(data.get("total_connection_attempts", 0) or 0) + + average_connection_time_ms = data.get("average_connection_time_ms") + if average_connection_time_ms is not None: + average_connection_time_ms = int(average_connection_time_ms) + + most_recent_connection_duration_ms = data.get( + "most_recent_connection_duration_ms" + ) + if most_recent_connection_duration_ms is not None: + most_recent_connection_duration_ms = int(most_recent_connection_duration_ms) + + return Connection( + id=data["id"], + name=data["name"], + engine=engine, + configuration=configuration, + comments=comments, + ssh_tunnel=ssh_config, + parent=parent, + created_at=data.get("created_at"), + last_connection_at=data.get("last_connection_at"), + last_successful_connection_at=data.get("last_successful_connection_at"), + last_failure_reason=data.get("last_failure_reason"), + successful_connections=successful_connections, + unsuccessful_connections=unsuccessful_connections, + total_connection_attempts=total_connection_attempts, + average_connection_time_ms=average_connection_time_ms, + most_recent_connection_duration_ms=most_recent_connection_duration_ms, + ) + + def add_connection( + self, connection: Connection, parent: Optional[ConnectionDirectory] = None + ) -> int: + self.connections.get_value() + + if connection.is_new: + connection.id = self._next_id() + + if parent: + parent.children.append(connection) + connection.parent = parent + else: + self.connections.append(connection) + connection.parent = None + + self._write() + self.connections.refresh() + + return connection.id + + def save_connection(self, connection: Connection) -> int: + self.connections.get_value() + + def _find_and_replace( + connections: list[Union[ConnectionDirectory, Connection]], + target_id: int, + parent: Optional[ConnectionDirectory] = None, + ) -> bool: + for i, item in enumerate(connections): + if isinstance(item, ConnectionDirectory): + if _find_and_replace(item.children, target_id, item): + return True + elif isinstance(item, Connection) and item.id == target_id: + connection.parent = parent + connections[i] = connection + return True + return False + + if _find_and_replace(self.connections.get_value(), connection.id): + self._write() + self.connections.refresh() + + return connection.id + + def save_directory(self, directory: ConnectionDirectory) -> None: + self.connections.get_value() + + def _find_and_replace( + nodes: list[Union[ConnectionDirectory, Connection]], + target_id: int, + parent: Optional[ConnectionDirectory] = None, + ) -> bool: + for index, node in enumerate(nodes): + if isinstance(node, ConnectionDirectory): + if node.id == target_id: + directory.children = node.children + nodes[index] = directory + return True + + if _find_and_replace(node.children, target_id, node): + return True + + return False + + if _find_and_replace(self.connections.get_value(), directory.id): + self._write() + self.connections.refresh() + + def add_directory( + self, + directory: ConnectionDirectory, + parent: Optional[ConnectionDirectory] = None, + ) -> None: + self.connections.get_value() + + if directory.is_new: + directory.id = self._next_id() + + if parent: + parent.children.append(directory) + else: + self.connections.append(directory) + + self._write() + + def delete_directory(self, directory: ConnectionDirectory): + self.connections.get_value() + target_id = directory.id + + def _find_and_delete( + nodes: list[Union[ConnectionDirectory, Connection]], + target: ConnectionDirectory, + ): + for idx, item in enumerate(nodes): + if isinstance(item, ConnectionDirectory): + if item.id == target_id: + del nodes[idx] + return True + + if _find_and_delete(item.children, target): + return True + + return False + + if _find_and_delete(self.connections.get_value(), directory): + self._write() + self.connections.refresh() + + def find_connection_parent_directory( + self, connection_id: int + ) -> Optional[ConnectionDirectory]: + self.connections.get_value() + + def _walk( + nodes: list[Union[ConnectionDirectory, Connection]], + parent: Optional[ConnectionDirectory] = None, + ) -> Optional[ConnectionDirectory]: + for node in nodes: + if isinstance(node, ConnectionDirectory): + if result := _walk(node.children, node): + return result + continue + + if isinstance(node, Connection) and node.id == connection_id: + node.parent = parent + return parent + + return None + + return _walk(self.connections.get_value()) + + def get_all_connection_names(self) -> set[str]: + self.connections.get_value() + names: set[str] = set() + + def _walk(nodes: list[Union[ConnectionDirectory, Connection]]) -> None: + for node in nodes: + if isinstance(node, ConnectionDirectory): + _walk(node.children) + continue + + if isinstance(node, Connection): + names.add(node.name) + + _walk(self.connections.get_value()) + return names + + def delete_connection(self, connection: Connection) -> None: + self.connections.get_value() + + def _find_and_delete(connections, target_id): + for i, item in enumerate(connections): + if isinstance(item, ConnectionDirectory): + if _find_and_delete(item.children, target_id): + return True + elif isinstance(item, Connection) and item.id == target_id: + del connections[i] + return True + return False + + if _find_and_delete(self.connections.get_value(), connection.id): + self._write() + self.connections.refresh() + + def _build_ssh_configuration( + self, data: dict[str, Any] + ) -> Optional[SSHTunnelConfiguration]: + if not data: + return None + + try: + return SSHTunnelConfiguration( + enabled=bool(data.get("enabled")), + executable=data.get("executable", "ssh"), + hostname=data.get("hostname", ""), + port=int(data.get("port", 22)), + username=data.get("username", ""), + password=data.get("password", ""), + local_port=int(data.get("local_port", 0)), + remote_host=data.get("remote_host"), + remote_port=int(data["remote_port"]) + if data.get("remote_port") + else None, + identity_file=data.get("identity_file"), + extra_args=data.get("extra_args"), + ) + except (TypeError, ValueError): + return None diff --git a/windows/dialogs/connections/view.py b/windows/dialogs/connections/view.py new file mode 100644 index 0000000..f15ea6d --- /dev/null +++ b/windows/dialogs/connections/view.py @@ -0,0 +1,779 @@ +import dataclasses +import time + +from datetime import datetime +from gettext import gettext as _ +from typing import Optional + +import wx +import wx.dataview + +from helpers.loader import Loader +from helpers.logger import logger + +from structures.session import Session +from structures.connection import Connection, ConnectionEngine +from structures.configurations import CredentialsConfiguration, SourceConfiguration + +from windows.views import ConnectionsDialog + +from windows.main import CURRENT_SESSION, SESSIONS_LIST + +from windows.dialogs.connections import ( + CURRENT_CONNECTION, + CURRENT_DIRECTORY, + PENDING_CONNECTION, + ConnectionDirectory, +) +from windows.dialogs.connections.model import ConnectionModel +from windows.dialogs.connections.controller import ConnectionsTreeController +from windows.dialogs.connections.repository import ConnectionsRepository + + +class ConnectionsManager(ConnectionsDialog): + def __init__(self, parent): + super().__init__(parent) + self._app = wx.GetApp() + self._repository = ConnectionsRepository() + self.engine.SetItems([e.name for e in ConnectionEngine.get_all()]) + + self.connections_tree_controller = ConnectionsTreeController( + self.connections_tree_ctrl, self._repository + ) + self.connections_tree_controller.on_item_activated = ( + lambda connection: self.on_connect(None) + ) + + self.connections_model = ConnectionModel() + self.connections_model.bind_controls( + name=self.name, + engine=self.engine, + hostname=self.hostname, + port=self.port, + username=self.username, + password=self.password, + use_tls_enabled=self.use_tls_enabled, + filename=self.filename, + comments=self.comments, + created_at=self.created_at, + last_connection_at=self.last_connection_at, + successful_connected=self.successful_connected, + unsuccessful_connections=self.unsuccessful_connections, + last_successful_connection=self.last_successful_connection, + last_failure_raison=self.last_failure_raison, + total_connection_attempts=self.total_connection_attempts, + average_connection_time=self.average_connection_time, + most_recent_connection_duration=self.most_recent_connection_duration, + ssh_tunnel_enabled=self.ssh_tunnel_enabled, + ssh_tunnel_executable=self.ssh_tunnel_executable, + ssh_tunnel_hostname=self.ssh_tunnel_hostname, + ssh_tunnel_port=self.ssh_tunnel_port, + ssh_tunnel_username=self.ssh_tunnel_username, + ssh_tunnel_password=self.ssh_tunnel_password, + ssh_tunnel_local_port=self.ssh_tunnel_local_port, + ssh_tunnel_identity_file=self.identity_file, + ssh_tunnel_remote_hostname=self.remote_hostname, + ssh_tunnel_remote_port=self.remote_port, + ) + + self.connections_model.engine.subscribe(self._on_change_engine) + self.connections_model.ssh_tunnel_enabled.subscribe(self._on_change_ssh_tunnel) + + self._context_menu_item = None + self._pending_parent_directory_id = None + self._setup_event_handlers() + self._update_tree_menu_state() + + def _update_tree_menu_state(self): + selected_connection = CURRENT_CONNECTION() + selected_directory = CURRENT_DIRECTORY() + + if self._context_menu_item is not None and self._context_menu_item.IsOk(): + obj = self.connections_tree_controller.model.ItemToObject( + self._context_menu_item + ) + if isinstance(obj, Connection): + selected_connection = obj + selected_directory = None + elif isinstance(obj, ConnectionDirectory): + selected_connection = None + selected_directory = obj + + self.m_menuItem19.Enable(bool(selected_connection)) + self.m_menuItem18.Enable(bool(selected_connection or selected_directory)) + + def _current_timestamp(self) -> str: + return datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + def _record_connection_attempt( + self, + connection: Connection, + success: bool, + duration_ms: int, + failure_reason: Optional[str] = None, + ) -> None: + connection.total_connection_attempts += 1 + connection.last_connection_at = self._current_timestamp() + connection.most_recent_connection_duration_ms = duration_ms + + if success: + connection.successful_connections += 1 + connection.last_successful_connection_at = connection.last_connection_at + connection.last_failure_reason = None + else: + connection.unsuccessful_connections += 1 + connection.last_failure_reason = failure_reason or _("Unknown error") + + if connection.total_connection_attempts > 0: + previous_total = connection.average_connection_time_ms or 0 + attempt_count = connection.total_connection_attempts + connection.average_connection_time_ms = int( + ((previous_total * (attempt_count - 1)) + duration_ms) / attempt_count + ) + + if not connection.is_new: + self._repository.save_connection(connection) + + def _sync_statistics_to_model(self, connection: Connection) -> None: + self.connections_model.created_at(connection.created_at or "") + self.connections_model.last_connection_at(connection.last_connection_at or "") + self.connections_model.successful_connected( + str(connection.successful_connections) + ) + self.connections_model.unsuccessful_connections( + str(connection.unsuccessful_connections) + ) + self.connections_model.last_successful_connection( + connection.last_successful_connection_at or "" + ) + self.connections_model.last_failure_raison(connection.last_failure_reason or "") + self.connections_model.total_connection_attempts( + str(connection.total_connection_attempts) + ) + self.connections_model.average_connection_time( + str(connection.average_connection_time_ms) + if connection.average_connection_time_ms is not None + else "" + ) + self.connections_model.most_recent_connection_duration( + str(connection.most_recent_connection_duration_ms) + if connection.most_recent_connection_duration_ms is not None + else "" + ) + + def _on_current_directory(self, directory: Optional[ConnectionDirectory]): + self.btn_delete.Enable(bool(directory)) + self.btn_create_directory.Enable(not bool(directory)) + self._update_tree_menu_state() + + def _on_current_connection(self, connection: Optional[Connection]): + self.btn_open.Enable(bool(connection and connection.is_valid)) + self.btn_test.Enable(bool(connection and connection.is_valid)) + self.btn_delete.Enable(bool(connection)) + if connection is not None: + parent_directory = self._repository.find_connection_parent_directory( + connection.id + ) + self._pending_parent_directory_id = ( + parent_directory.id if parent_directory else None + ) + self._sync_statistics_to_model(connection) + self._update_tree_menu_state() + + def _on_pending_connection(self, connection: Connection): + if connection is None: + self.btn_save.Enable(False) + current = CURRENT_CONNECTION() + self.btn_test.Enable(bool(current and current.is_valid)) + self.btn_open.Enable(bool(current and current.is_valid)) + return + + item = self.connections_tree_controller.model.ObjectToItem(connection) + if item.IsOk(): + self.connections_tree_controller.model.ItemChanged(item) + + self.btn_save.Enable(bool(connection and connection.is_valid)) + self.btn_test.Enable(bool(connection and connection.is_valid)) + self.btn_open.Enable(bool(connection and connection.is_valid)) + + def _on_connection_activated(self, connection: Connection): + CURRENT_CONNECTION(connection) + self.on_connect(None) + + def _on_change_engine(self, value: str): + connection_engine = ConnectionEngine.from_name(value) + + self.panel_credentials.Show( + connection_engine + in [ + ConnectionEngine.MYSQL, + ConnectionEngine.MARIADB, + ConnectionEngine.POSTGRESQL, + ] + ) + self.panel_ssh_tunnel.Show( + connection_engine + in [ + ConnectionEngine.MYSQL, + ConnectionEngine.MARIADB, + ConnectionEngine.POSTGRESQL, + ] + ) + + self.panel_source.Show(connection_engine == ConnectionEngine.SQLITE) + + self.panel_source.GetParent().Layout() + + def _on_change_ssh_tunnel(self, enable: bool): + self.panel_ssh_tunnel.Show(enable) + self.panel_ssh_tunnel.Enable(enable) + self.panel_ssh_tunnel.GetParent().Layout() + + def _on_search_changed(self, event): + search_text = self.search_connection.GetValue() + self.connections_tree_controller.do_filter_connections(search_text) + + def _setup_event_handlers(self): + self.Bind(wx.EVT_CLOSE, self.on_exit) + self.search_connection.Bind(wx.EVT_TEXT, self._on_search_changed) + self.connections_tree_ctrl.Bind( + wx.dataview.EVT_DATAVIEW_ITEM_CONTEXT_MENU, + self._on_tree_item_context_menu, + ) + + CURRENT_DIRECTORY.subscribe(self._on_current_directory) + CURRENT_CONNECTION.subscribe(self._on_current_connection) + PENDING_CONNECTION.subscribe(self._on_pending_connection) + + def _on_tree_item_context_menu(self, event): + position = event.GetPosition() + if position == wx.DefaultPosition: + position = self.connections_tree_ctrl.ScreenToClient(wx.GetMousePosition()) + + if isinstance(position, tuple): + position = wx.Point(position[0], position[1]) + + self._show_tree_menu(position) + + def _show_tree_menu(self, position: wx.Point): + if position.x < 0 or position.y < 0: + position = wx.Point(8, 8) + + self._context_menu_item, _ = self.connections_tree_ctrl.HitTest(position) + + self._update_tree_menu_state() + self.connections_tree_ctrl.PopupMenu(self.connection_tree_menu, position) + self._context_menu_item = None + + def _get_action_item(self): + if self._context_menu_item is not None and self._context_menu_item.IsOk(): + return self._context_menu_item + + selected_item = self.connections_tree_ctrl.GetSelection() + if selected_item is not None and selected_item.IsOk(): + return selected_item + + return None + + def _capture_expanded_directory_paths(self): + expanded_paths = set() + + def _walk(nodes, parent_path=()): + for node in nodes: + if isinstance(node, ConnectionDirectory): + path = parent_path + (node.id,) + item = self.connections_tree_controller.model.ObjectToItem(node) + if ( + item is not None + and item.IsOk() + and self.connections_tree_ctrl.IsExpanded(item) + ): + expanded_paths.add(path) + _walk(node.children, path) + + _walk(self._repository.connections.get_value()) + return expanded_paths + + def _restore_expanded_directory_paths(self, expanded_paths): + if not expanded_paths: + return + + def _walk(nodes, parent_path=()): + for node in nodes: + if isinstance(node, ConnectionDirectory): + path = parent_path + (node.id,) + if path in expanded_paths: + item = self.connections_tree_controller.model.ObjectToItem(node) + if item is not None and item.IsOk(): + self.connections_tree_ctrl.Expand(item) + _walk(node.children, path) + + _walk(self._repository.connections.get_value()) + + def do_open_session(self, session: Session): + # CONNECTIONS_LIST.append(connection) + + SESSIONS_LIST.append(session) + CURRENT_SESSION(session) + + if not self.GetParent(): + # CURRENT_CONNECTION(connection) + self._app.open_main_frame() + + self.Hide() + + def on_test_session(self, *args): + connection = CURRENT_CONNECTION() + + session = Session(connection) + + try: + self.verify_session(session) + except Exception as ex: + pass + else: + wx.MessageDialog( + None, + message=_("Connection established successfully"), + caption=_("Connection"), + style=wx.OK, + ).ShowModal() + + def on_save(self, *args): + connection = PENDING_CONNECTION.get_value() + if not connection: + return False + + dialog = wx.MessageDialog( + None, + message=_(f"Do you want save the connection {connection.name}?"), + caption=_("Confirm save"), + style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION, + ) + + if dialog.ShowModal() != wx.ID_YES: + return False + + expanded_paths = self._capture_expanded_directory_paths() + + if not connection.created_at: + connection.created_at = self._current_timestamp() + + parent_obj = None + parent_item = None + if isinstance(connection.parent, ConnectionDirectory): + parent_obj = connection.parent + parent_item = self.connections_tree_controller.model.ObjectToItem( + parent_obj + ) + elif self._pending_parent_directory_id is not None: + parent_obj = self._find_directory_by_id(self._pending_parent_directory_id) + if parent_obj is not None: + parent_item = self.connections_tree_controller.model.ObjectToItem( + parent_obj + ) + + if connection.is_new: + self._repository.add_connection(connection, parent_obj) + else: + self._repository.save_connection(connection) + + refreshed_connection = self._find_connection_by_id(connection.id) + + PENDING_CONNECTION(None) + + if refreshed_connection is None: + refreshed_connection = connection + + CURRENT_CONNECTION(refreshed_connection) + + wx.CallAfter(self._restore_expanded_directory_paths, expanded_paths) + wx.CallAfter(self._select_connection_in_tree, refreshed_connection, parent_item) + + return True + + def _confirm_save_pending_changes(self) -> bool: + if PENDING_CONNECTION() is None: + return True + + dialog = wx.MessageDialog( + None, + message=_( + "You have unsaved changes. Do you want to save them before continuing?" + ), + caption=_("Unsaved changes"), + style=wx.YES_NO | wx.CANCEL | wx.ICON_QUESTION, + ) + result = dialog.ShowModal() + dialog.Destroy() + + if result == wx.ID_YES: + return bool(self.on_save(None)) + + if result == wx.ID_NO: + PENDING_CONNECTION(None) + return True + + return False + + def _get_selected_parent_directory(self) -> Optional[ConnectionDirectory]: + selected_item = self.connections_tree_ctrl.GetSelection() + if not selected_item.IsOk(): + return None + + selected_obj = self.connections_tree_controller.model.ItemToObject( + selected_item + ) + if isinstance(selected_obj, ConnectionDirectory): + return selected_obj + + if isinstance(selected_obj, Connection): + parent_item = self.connections_tree_controller.model.GetParent( + selected_item + ) + if parent_item and parent_item.IsOk(): + parent_obj = self.connections_tree_controller.model.ItemToObject( + parent_item + ) + if isinstance(parent_obj, ConnectionDirectory): + return parent_obj + + return None + + def _create_connection(self): + if not self._confirm_save_pending_changes(): + return + + selected_engine_name = self.engine.GetStringSelection() or "MySQL" + engine = ConnectionEngine.from_name(selected_engine_name) + if engine == ConnectionEngine.POSTGRESQL: + port = 5432 + elif engine in [ConnectionEngine.MYSQL, ConnectionEngine.MARIADB]: + port = 3306 + else: + port = 3306 + + if engine in [ + ConnectionEngine.MYSQL, + ConnectionEngine.MARIADB, + ConnectionEngine.POSTGRESQL, + ]: + configuration = CredentialsConfiguration( + hostname="localhost", + username="root", + password="", + port=port, + ) + else: + configuration = SourceConfiguration(filename="") + + new_connection = Connection( + id=-1, + name=self._generate_unique_new_connection_name(), + engine=engine, + configuration=configuration, + comments="", + ssh_tunnel=None, + ) + + expanded_paths = self._capture_expanded_directory_paths() + parent = self._get_selected_parent_directory() + self._pending_parent_directory_id = parent.id if parent else None + self._repository.add_connection(new_connection, parent) + + refreshed_connection = self._find_connection_by_id(new_connection.id) + if refreshed_connection is None: + refreshed_connection = new_connection + + CURRENT_CONNECTION(refreshed_connection) + PENDING_CONNECTION(None) + wx.CallAfter(self._restore_expanded_directory_paths, expanded_paths) + wx.CallAfter(self._select_connection_in_tree, refreshed_connection) + wx.CallAfter(self.on_rename, None) + + def on_create(self, event): + self._create_connection() + + def on_new_connection(self, event): + self._create_connection() + + def _find_connection_by_id(self, connection_id: int) -> Optional[Connection]: + def _search(nodes): + for node in nodes: + if isinstance(node, ConnectionDirectory): + found = _search(node.children) + if found: + return found + continue + + if isinstance(node, Connection) and node.id == connection_id: + return node + + return None + + return _search(self._repository.connections.get_value()) + + def _find_directory_by_id(self, directory_id: int) -> Optional[ConnectionDirectory]: + def _search(nodes): + for node in nodes: + if not isinstance(node, ConnectionDirectory): + continue + + if node.id == directory_id: + return node + + found = _search(node.children) + if found: + return found + + return None + + return _search(self._repository.connections.get_value()) + + def _generate_unique_new_connection_name(self) -> str: + base_name = _("New connection") + existing_names = self._repository.get_all_connection_names() + if base_name not in existing_names: + return base_name + + index = 1 + while True: + candidate = f"{base_name}({index})" + if candidate not in existing_names: + return candidate + index += 1 + + def _expand_item_parents(self, item): + parent = self.connections_tree_controller.model.GetParent(item) + while parent is not None and parent.IsOk(): + self.connections_tree_ctrl.Expand(parent) + parent = self.connections_tree_controller.model.GetParent(parent) + + def _select_connection_in_tree( + self, + connection: Connection, + parent_item=None, + ): + item = self.connections_tree_controller.model.ObjectToItem(connection) + if item is None or not item.IsOk(): + return + + if parent_item is not None and parent_item.IsOk(): + self.connections_tree_ctrl.Expand(parent_item) + + self._expand_item_parents(item) + self.connections_tree_ctrl.Select(item) + self.connections_tree_ctrl.EnsureVisible(item) + + def on_create_directory(self, event): + if not self._confirm_save_pending_changes(): + return + + parent = self._get_selected_parent_directory() + expanded_paths = self._capture_expanded_directory_paths() + new_dir = ConnectionDirectory(id=-1, name=_("New directory")) + self._repository.add_directory(new_dir, parent) + + item = self.connections_tree_controller.model.ObjectToItem(new_dir) + if item.IsOk(): + self.connections_tree_ctrl.Select(item) + self.connections_tree_controller.edit_item(item) + + if parent: + parent_item = self.connections_tree_controller.model.ObjectToItem(parent) + self.connections_tree_ctrl.Expand(parent_item) + + wx.CallAfter(self._restore_expanded_directory_paths, expanded_paths) + + def on_new_directory(self, event): + self.on_create_directory(event) + + def _generate_clone_name(self, base_name: str) -> str: + existing_names = set() + + def _collect(nodes): + for node in nodes: + if isinstance(node, ConnectionDirectory): + _collect(node.children) + elif isinstance(node, Connection): + existing_names.add(node.name) + + _collect(self._repository.connections.get_value()) + + idx = 1 + while True: + candidate = f"{base_name}(clone)({idx})" + if candidate not in existing_names: + return candidate + idx += 1 + + def on_clone_connection(self, event): + selected_item = self._get_action_item() + if selected_item is None or not selected_item.IsOk(): + return + + connection = self.connections_tree_controller.model.ItemToObject(selected_item) + if not isinstance(connection, Connection): + return + + expanded_paths = self._capture_expanded_directory_paths() + + cloned_connection = dataclasses.replace( + connection, + id=-1, + name=self._generate_clone_name(connection.name), + ) + + parent = self._repository.find_connection_parent_directory(connection.id) + self._pending_parent_directory_id = parent.id if parent else None + self._repository.add_connection(cloned_connection, parent) + + refreshed_connection = self._find_connection_by_id(cloned_connection.id) + if refreshed_connection is None: + refreshed_connection = cloned_connection + + CURRENT_CONNECTION(refreshed_connection) + PENDING_CONNECTION(None) + wx.CallAfter(self._restore_expanded_directory_paths, expanded_paths) + wx.CallAfter(self._select_connection_in_tree, refreshed_connection) + + def on_rename(self, event): + selected_item = self._get_action_item() + if selected_item is None or not selected_item.IsOk(): + return + + self.connections_tree_controller.edit_item(selected_item) + + def verify_session(self, session: Session): + started_at = time.perf_counter() + + with Loader.cursor_wait(): + try: + tls_was_enabled = bool( + getattr(session.connection.configuration, "use_tls_enabled", False) + ) + + logger.debug( + "Verifying session connection=%s engine=%s host=%s port=%s user=%s use_tls_enabled=%s", + session.connection.name, + session.connection.engine, + getattr(session.connection.configuration, "hostname", None), + getattr(session.connection.configuration, "port", None), + getattr(session.connection.configuration, "username", None), + tls_was_enabled, + ) + session.connect(connect_timeout=10) + + tls_is_enabled = bool( + getattr(session.connection.configuration, "use_tls_enabled", False) + ) + if not tls_was_enabled and tls_is_enabled: + self.connections_model.use_tls_enabled(True) + + if not session.connection.is_new: + self._repository.save_connection(session.connection) + + wx.MessageDialog( + None, + message=_( + "This connection cannot work without TLS. TLS has been enabled automatically." + ), + caption=_("Connection"), + style=wx.OK | wx.ICON_INFORMATION, + ).ShowModal() + + duration_ms = int((time.perf_counter() - started_at) * 1000) + self._record_connection_attempt( + session.connection, + success=True, + duration_ms=duration_ms, + ) + self._sync_statistics_to_model(session.connection) + except Exception as ex: + duration_ms = int((time.perf_counter() - started_at) * 1000) + self._record_connection_attempt( + session.connection, + success=False, + duration_ms=duration_ms, + failure_reason=str(ex), + ) + self._sync_statistics_to_model(session.connection) + + wx.MessageDialog( + None, + message=_(f"Connection error:\n{str(ex)}"), + caption=_("Connection error"), + style=wx.OK | wx.OK_DEFAULT | wx.ICON_ERROR, + ).ShowModal() + raise ConnectionError(ex) + + def on_connect(self, event): + if PENDING_CONNECTION() and not self.on_save(event): + return + + connection = CURRENT_CONNECTION() + + session = Session(connection) + + try: + self.verify_session(session) + except ConnectionError as ex: + logger.info(ex) + except Exception as ex: + logger.error(ex, exc_info=True) + else: + self.do_open_session(session) + + def on_delete_connection(self, connection: Connection): + dialog = wx.MessageDialog( + None, + message=_(f"Do you want to delete the connection '{connection.name}'?"), + caption=_("Confirm delete"), + style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION, + ) + + if dialog.ShowModal() == wx.ID_YES: + PENDING_CONNECTION(None) + CURRENT_CONNECTION(None) + self._repository.delete_connection(connection) + + dialog.Destroy() + + def on_delete_directory(self, directory: ConnectionDirectory): + dialog = wx.MessageDialog( + None, + message=_(f"Do you want to delete the directory '{directory.name}'?"), + caption=_("Confirm delete"), + style=wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION, + ) + + if dialog.ShowModal() == wx.ID_YES: + PENDING_CONNECTION(None) + CURRENT_CONNECTION(None) + CURRENT_DIRECTORY(None) + self._repository.delete_directory(directory) + + dialog.Destroy() + + def on_delete(self, *args): + selected_item = self._get_action_item() + if selected_item is None or not selected_item.IsOk(): + return + + expanded_paths = self._capture_expanded_directory_paths() + obj = self.connections_tree_controller.model.ItemToObject(selected_item) + + if isinstance(obj, Connection): + self.on_delete_connection(obj) + elif isinstance(obj, ConnectionDirectory): + self.on_delete_directory(obj) + + wx.CallAfter(self._restore_expanded_directory_paths, expanded_paths) + + def on_exit(self, event): + if not self._app.main_frame: + self._app.do_exit(event) + else: + self.Hide() + + event.Skip() diff --git a/windows/dialogs/settings/__init__.py b/windows/dialogs/settings/__init__.py new file mode 100644 index 0000000..0b61f6d --- /dev/null +++ b/windows/dialogs/settings/__init__.py @@ -0,0 +1,3 @@ +from windows.dialogs.settings.controller import SettingsController + +__all__ = ["SettingsController"] diff --git a/windows/dialogs/settings/controller.py b/windows/dialogs/settings/controller.py new file mode 100644 index 0000000..f608583 --- /dev/null +++ b/windows/dialogs/settings/controller.py @@ -0,0 +1,223 @@ +from pathlib import Path + +import wx +import wx.dataview + +from constants import Language, LogLevel + +from windows.dialogs.settings.repository import Settings +from windows.views import SettingsDialog + + +class SettingsController: + def __init__(self, parent: wx.Window, settings: Settings) -> None: + self.settings = settings + self.dialog = SettingsDialog(parent) + + self._load_settings() + self._populate_controls() + self._bind_events() + + def _bind_events(self) -> None: + self.dialog.apply.Bind(wx.EVT_BUTTON, self._on_apply) + self.dialog.cancel.Bind(wx.EVT_BUTTON, self._on_cancel) + self.dialog.shortcuts_filter.Bind(wx.EVT_SEARCH, self._on_filter_shortcuts) + + def _populate_controls(self) -> None: + self._populate_languages() + self._populate_themes() + self._populate_advanced_settings() + self._populate_shortcuts() + + def _populate_languages(self) -> None: + self.dialog.language.Clear() + for lang in Language: + self.dialog.language.Append(lang.label) + + def _populate_shortcuts(self) -> None: + self.dialog.shortcuts_list.DeleteAllItems() + + shortcuts = self.settings.get_value("shortcuts") or {} + for action, shortcut_data in shortcuts.items(): + if isinstance(shortcut_data, dict): + shortcut = shortcut_data.get("key", "") + context = shortcut_data.get("context", "Global") + else: + shortcut = str(shortcut_data) + context = "Global" + + self.dialog.shortcuts_list.AppendItem([action, shortcut, context]) + + def _populate_themes(self) -> None: + themes_dir = Path(wx.GetApp().GetAppName()).parent / "themes" + if not themes_dir.exists(): + themes_dir = Path.cwd() / "themes" + + self.dialog.theme.Clear() + + if themes_dir.exists(): + for theme_file in sorted(themes_dir.glob("*.yml")): + theme_name = theme_file.stem + self.dialog.theme.Append(theme_name) + + if self.dialog.theme.GetCount() > 0: + self.dialog.theme.SetSelection(0) + + def _load_settings(self) -> None: + self._load_general_settings() + self._load_appearance_settings() + self._load_query_editor_settings() + self._load_advanced_settings() + + def _load_advanced_settings(self) -> None: + self.dialog.advanced_connection_timeout.SetValue( + self.settings.get_value("advanced", "connection_timeout") or 60 + ) + self.dialog.advanced_query_timeout.SetValue( + self.settings.get_value("advanced", "query_timeout") or 60 + ) + + levels = [LogLevel.DEBUG, LogLevel.INFO, LogLevel.WARNING, LogLevel.ERROR] + logging_level = self.settings.get_value("advanced", "logging_level") or "INFO" + try: + selection = next(i for i, level in enumerate(levels) if level.value == logging_level) + except StopIteration: + selection = 1 + self.dialog.advanced_logging_level.SetSelection(selection) + + def _load_appearance_settings(self) -> None: + current_theme = self.settings.get_value("appearance", "theme") or "" + if current_theme: + idx = self.dialog.theme.FindString(current_theme) + if idx != wx.NOT_FOUND: + self.dialog.theme.SetSelection(idx) + + appearance_mode = self.settings.get_value("appearance", "mode") or "auto" + if appearance_mode == "auto": + self.dialog.appearance_mode_auto.SetValue(True) + elif appearance_mode == "light": + self.dialog.appearance_mode_light.SetValue(True) + elif appearance_mode == "dark": + self.dialog.appearance_mode_dark.SetValue(True) + + def _load_general_settings(self) -> None: + language_map = {lang.code: idx for idx, lang in enumerate(Language)} + language = self.settings.get_value("language") or "en_US" + self.dialog.language.SetSelection(language_map.get(language, 0)) + + def _load_query_editor_settings(self) -> None: + self.dialog.query_editor_statement_separator.SetValue( + self.settings.get_value("query_editor", "statement_separator") or ";" + ) + self.dialog.query_editor_trim_whitespace.SetValue( + self.settings.get_value("query_editor", "trim_whitespace") or False + ) + self.dialog.query_editor_execute_selected_only.SetValue( + self.settings.get_value("query_editor", "execute_selected_only") or False + ) + + autocomplete = self.settings.get_value("query_editor", "autocomplete") + self.dialog.query_editor_autocomplete.SetValue( + autocomplete if autocomplete is not None else True + ) + + autoformat = self.settings.get_value("query_editor", "autoformat") + self.dialog.query_editor_format.SetValue( + autoformat if autoformat is not None else True + ) + + def _save_settings(self) -> None: + self._save_general_settings() + self._save_appearance_settings() + self._save_query_editor_settings() + self._save_advanced_settings() + + def _save_advanced_settings(self) -> None: + if not self.settings.get_value("advanced"): + self.settings.set_value("advanced", value={}) + + self.settings.set_value("advanced", "connection_timeout", + value=self.dialog.advanced_connection_timeout.GetValue() + ) + self.settings.set_value("advanced", "query_timeout", + value=self.dialog.advanced_query_timeout.GetValue() + ) + + levels = [LogLevel.DEBUG, LogLevel.INFO, LogLevel.WARNING, LogLevel.ERROR] + selection = self.dialog.advanced_logging_level.GetSelection() + self.settings.set_value("advanced", "logging_level", + value=levels[selection].value if 0 <= selection < len(levels) else LogLevel.INFO.value + ) + + def _save_appearance_settings(self) -> None: + if not self.settings.get_value("appearance"): + self.settings.set_value("appearance", value={}) + + theme_idx = self.dialog.theme.GetSelection() + if theme_idx != wx.NOT_FOUND: + self.settings.set_value("appearance", "theme", value=self.dialog.theme.GetString(theme_idx)) + + if self.dialog.appearance_mode_auto.GetValue(): + appearance_mode = "auto" + elif self.dialog.appearance_mode_light.GetValue(): + appearance_mode = "light" + else: + appearance_mode = "dark" + self.settings.set_value("appearance", "mode", value=appearance_mode) + + def _save_general_settings(self) -> None: + language_map = {idx: lang.code for idx, lang in enumerate(Language)} + self.settings.set_value("language", value=language_map.get( + self.dialog.language.GetSelection(), "en_US" + )) + + def _save_query_editor_settings(self) -> None: + if not self.settings.get_value("query_editor"): + self.settings.set_value("query_editor", value={}) + + self.settings.set_value("query_editor", "statement_separator", + value=self.dialog.query_editor_statement_separator.GetValue() + ) + self.settings.set_value("query_editor", "trim_whitespace", + value=self.dialog.query_editor_trim_whitespace.GetValue() + ) + self.settings.set_value("query_editor", "execute_selected_only", + value=self.dialog.query_editor_execute_selected_only.GetValue() + ) + self.settings.set_value("query_editor", "autocomplete", + value=self.dialog.query_editor_autocomplete.GetValue() + ) + self.settings.set_value("query_editor", "autoformat", + value=self.dialog.query_editor_format.GetValue() + ) + + def _on_apply(self, event: wx.Event) -> None: + self._save_settings() + self.dialog.EndModal(wx.ID_OK) + + def _on_cancel(self, event: wx.Event) -> None: + self.dialog.EndModal(wx.ID_CANCEL) + + def _on_filter_shortcuts(self, event: wx.Event) -> None: + filter_text = self.dialog.shortcuts_filter.GetValue().lower() + + self.dialog.shortcuts_list.DeleteAllItems() + + shortcuts = self.settings.get_value("shortcuts") or {} + + for action, shortcut_data in shortcuts.items(): + if isinstance(shortcut_data, dict): + shortcut = shortcut_data.get("key", "") + context = shortcut_data.get("context", "Global") + else: + shortcut = str(shortcut_data) + context = "Global" + + if (not filter_text or + filter_text in action.lower() or + filter_text in shortcut.lower() or + filter_text in context.lower()): + self.dialog.shortcuts_list.AppendItem([action, shortcut, context]) + + def show_modal(self) -> int: + return self.dialog.ShowModal() diff --git a/windows/dialogs/settings/repository.py b/windows/dialogs/settings/repository.py new file mode 100644 index 0000000..b3ee7b1 --- /dev/null +++ b/windows/dialogs/settings/repository.py @@ -0,0 +1,28 @@ +from pathlib import Path +from typing import Optional + +from helpers.observables import ObservableObject +from helpers.repository import YamlRepository + + +class SettingsRepository(YamlRepository[ObservableObject]): + def __init__(self, config_file: Path): + super().__init__(config_file) + self.settings: Optional[ObservableObject] = None + + def _write(self) -> None: + if self.settings is None: + return + + data = dict(self.settings.get_value()) + self._write_yaml(data) + + def load(self) -> ObservableObject: + data = self._read_yaml() + self.settings = ObservableObject(data) + self.settings.subscribe(lambda _: self._write()) + return self.settings + + +class Settings(SettingsRepository): + pass diff --git a/windows/explorer.bkp.py b/windows/explorer.bkp.py deleted file mode 100755 index 70f4b20..0000000 --- a/windows/explorer.bkp.py +++ /dev/null @@ -1,473 +0,0 @@ -import dataclasses -from typing import Callable, List - -import wx -import wx.dataview -import wx.lib.agw.hypertreelist - -from helpers import bytes_to_human -from helpers.dataview import BaseDataViewTreeModel -from icons import IconList, ImageList, BitmapList -from structures.connection import Connection - -from structures.engines.database import SQLDatabase, SQLTable, SQLView, SQLTrigger, SQLProcedure, SQLFunction, SQLEvent - -from windows.main import CURRENT_DATABASE, CURRENT_TABLE, CURRENT_CONNECTION, CURRENT_VIEW, CURRENT_TRIGGER, CONNECTIONS_LIST, CURRENT_EVENT, CURRENT_FUNCTION, CURRENT_PROCEDURE -from windows.main.table import NEW_TABLE - - -@dataclasses.dataclass -class TreeCategory: - name : str - icon : wx.BitmapBundle - database : SQLDatabase - - -# class GaugeWithLabel(wx.Panel): -# def __init__(self, parent, max_range=100, size=(100, 20)): -# super().__init__(parent, size=size) -# self.SetBackgroundStyle(wx.BG_STYLE_PAINT) -# -# self.gauge = wx.Gauge(self, range=max_range, size=size) -# self.label = wx.StaticText(self, label="0%", style=wx.ALIGN_CENTER) -# -# self.sizer = wx.BoxSizer(wx.VERTICAL) -# self.sizer.Add(self.gauge, 1, wx.EXPAND, 5) -# self.sizer.Add(self.label, 0, wx.ALIGN_CENTER | wx.TOP, -size[1]) -# -# self.SetSizer(self.sizer) -# -# self.label.SetForegroundColour(wx.WHITE) -# font = self.label.GetFont() -# font.SetWeight(wx.FONTWEIGHT_BOLD) -# self.label.SetFont(font) -# -# def SetValue(self, val): -# self.gauge.SetValue(val) -# self.label.SetLabel(f"{val}%") -# self.label.Refresh() -# -# -# class TreeExplorerController: -# on_cancel_table: Callable -# do_cancel_table: Callable -# -# def __init__(self, tree_ctrl_explorer: wx.lib.agw.hypertreelist.HyperTreeList): -# self.app = wx.GetApp() -# -# self.tree_ctrl_explorer = tree_ctrl_explorer -# -# self.tree_ctrl_explorer.AddColumn("Name", width=200) -# self.tree_ctrl_explorer.AddColumn("Usage", width=100, flag=wx.ALIGN_RIGHT) -# -# self.tree_ctrl_explorer.SetMainColumn(0) -# self.tree_ctrl_explorer.AssignImageList(ImageList) -# self.tree_ctrl_explorer._main_win.Bind( -# wx.EVT_MOUSE_EVENTS, lambda e: None if e.LeftDown() else e.Skip() -# ) -# -# self.populate_tree() -# -# self.tree_ctrl_explorer.Bind(wx.EVT_TREE_SEL_CHANGED, self._load_items) -# self.tree_ctrl_explorer.Bind(wx.EVT_TREE_ITEM_EXPANDING, self._load_items) -# self.tree_ctrl_explorer.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self._load_items) -# -# CONNECTIONS_LIST.subscribe(self.append_connection, CallbackEvent.ON_APPEND) -# -# # CURRENT_DATABASE.get_value().tables.subscribe(self.load_observables, CallbackEvent.ON_APPEND) -# # CURRENT_DATABASE.get_value().views.subscribe(self.load_observables, CallbackEvent.ON_APPEND) -# # CURRENT_DATABASE.get_value().procedures.subscribe(self.load_observables, CallbackEvent.ON_APPEND) -# # CURRENT_DATABASE.get_value().functions.subscribe(self.load_observables, CallbackEvent.ON_APPEND) -# # CURRENT_DATABASE.get_value().triggers.subscribe(self.load_observables, CallbackEvent.ON_APPEND) -# # CURRENT_DATABASE.get_value().events.subscribe(self.load_observables, CallbackEvent.ON_APPEND) -# -# def _load_items(self, event: wx.lib.agw.hypertreelist.TreeEvent): -# with Loader.cursor_wait(): -# item = event.GetItem() -# if not item.IsOk(): -# event.Skip() -# return -# -# if NEW_TABLE.get_value() and not self.on_cancel_table(event): -# event.Skip() -# return -# -# self.reset_current_objects() -# -# obj = self.tree_ctrl_explorer.GetItemPyData(item) -# if obj is None: -# event.Skip() -# return -# -# if isinstance(obj, Connection): -# self.select_connection(obj, event) -# elif isinstance(obj, SQLDatabase): -# self.select_database(obj, item, event) -# elif isinstance( -# obj, -# (SQLTable, SQLView, SQLTrigger, SQLProcedure, SQLFunction, SQLEvent), -# ): -# self.select_sql_object(obj) -# -# event.Skip() -# -# def populate_tree(self): -# self.tree_ctrl_explorer.DeleteAllItems() -# self.root_item = self.tree_ctrl_explorer.AddRoot("") -# -# for connection in CONNECTIONS_LIST.get_value(): -# self.append_connection(connection) -# -# def append_connection(self, connection: Connection): -# self.root_item = self.tree_ctrl_explorer.GetRootItem() -# -# connection_item = self.tree_ctrl_explorer.AppendItem(self.root_item, connection.name, image=getattr(IconList, f"ENGINE_{connection.engine.name}"), data=connection) -# for database in connection.context.databases.get_value(): -# db_item = self.tree_ctrl_explorer.AppendItem(connection_item, database.name, image=IconList.SYSTEM_DATABASE, data=database) -# self.tree_ctrl_explorer.SetItemText(db_item, bytes_to_human(database.total_bytes), column=1) -# self.tree_ctrl_explorer.AppendItem(db_item, "Loading...", image=IconList.CLOCK, data=None) -# -# self.tree_ctrl_explorer.Expand(connection_item) -# self.tree_ctrl_explorer.EnsureVisible(connection_item) -# -# self.tree_ctrl_explorer.Layout() -# -# def load_observables(self, db_item, database: SQLDatabase): -# for observable_name in ["tables", "views", "procedures", "functions", "triggers", "events"]: -# observable = getattr(database, observable_name, None) -# -# category_item = self.tree_ctrl_explorer.AppendItem( -# db_item, -# observable_name.capitalize(), -# image=getattr(IconList, f"SYSTEM_{observable_name[:-1].upper()}", IconList.NOT_FOUND), -# data=None -# ) -# -# if observable is None: -# continue -# -# if database != CURRENT_DATABASE.get_value() and not observable.is_loaded: -# continue -# -# objs = observable.get_value() -# if not objs: -# continue -# -# -# # wx.CallAfter(self.tree_ctrl_explorer.Expand, category_item) -# -# for obj in objs: -# obj_item = self.tree_ctrl_explorer.AppendItem( -# category_item, -# obj.name, -# image=getattr(IconList, f"SYSTEM_{observable_name[:-1].upper()}", IconList.NOT_FOUND), -# data=obj -# ) -# -# if isinstance(obj, SQLTable): -# percentage = int((obj.total_bytes / database.total_bytes) * 100) if database.total_bytes else 0 -# -# gauge_panel = GaugeWithLabel(self.tree_ctrl_explorer, max_range=100, size=(self.tree_ctrl_explorer.GetColumnWidth(1) - 20, self.tree_ctrl_explorer.CharHeight)) -# gauge_panel.SetValue(percentage) -# self.tree_ctrl_explorer.SetItemWindow(obj_item, gauge_panel, column=1) -# else: -# self.tree_ctrl_explorer.SetItemText(obj_item, "", column=1) -# -# loading_item, index_item = self.tree_ctrl_explorer.GetFirstChild(db_item) -# if loading_item and loading_item.GetData() is None: -# self.tree_ctrl_explorer.Delete(loading_item) -# -# def reset_current_objects(self): -# CURRENT_TABLE.set_value(None) -# CURRENT_VIEW.set_value(None) -# CURRENT_TRIGGER.set_value(None) -# CURRENT_PROCEDURE.set_value(None) -# CURRENT_EVENT.set_value(None) -# CURRENT_FUNCTION.set_value(None) -# -# def select_connection(self, connection: Connection, event): -# if connection == CURRENT_CONNECTION.get_value() and CURRENT_DATABASE.get_value(): -# event.Skip() -# return -# CURRENT_CONNECTION.set_value(connection) -# CURRENT_DATABASE.set_value(None) -# -# def select_database(self, database: SQLDatabase, item, event): -# if database != CURRENT_DATABASE.get_value(): -# connection = database.context.connection -# if connection != CURRENT_CONNECTION.get_value(): -# CURRENT_CONNECTION.set_value(connection) -# CURRENT_DATABASE.set_value(database) -# self.load_observables(item, database) -# -# if not self.tree_ctrl_explorer.IsExpanded(item): -# wx.CallAfter(self.tree_ctrl_explorer.Expand, item) -# -# def select_sql_object(self, sql_obj): -# database = sql_obj.database -# connection = database.context.connection -# -# if connection != CURRENT_CONNECTION.get_value(): -# CURRENT_CONNECTION.set_value(connection) -# -# if database != CURRENT_DATABASE.get_value(): -# CURRENT_DATABASE.set_value(database) -# -# if isinstance(sql_obj, SQLTable): -# if not CURRENT_TABLE.get_value() or sql_obj != CURRENT_TABLE.get_value(): -# CURRENT_TABLE.set_value(sql_obj.copy()) -# -# elif isinstance(sql_obj, SQLView): -# if not CURRENT_VIEW.get_value() or sql_obj != CURRENT_VIEW.get_value(): -# CURRENT_VIEW.set_value(sql_obj.copy()) -# -# elif isinstance(sql_obj, SQLTrigger): -# if not CURRENT_TRIGGER.get_value() or sql_obj != CURRENT_TRIGGER.get_value(): -# CURRENT_TRIGGER.set_value(sql_obj.copy()) -# -# elif isinstance(sql_obj, SQLProcedure): -# if not CURRENT_PROCEDURE.get_value() or sql_obj != CURRENT_PROCEDURE.get_value(): -# CURRENT_PROCEDURE.set_value(sql_obj.copy()) -# -# elif isinstance(sql_obj, SQLFunction): -# if not CURRENT_FUNCTION.get_value() or sql_obj != CURRENT_FUNCTION.get_value(): -# CURRENT_FUNCTION.set_value(sql_obj.copy()) -# -# elif isinstance(sql_obj, SQLEvent): -# if not CURRENT_EVENT.get_value() or sql_obj != CURRENT_EVENT.get_value(): -# CURRENT_EVENT.set_value(sql_obj.copy()) - - -class ExplorerTreeModel(BaseDataViewTreeModel): - def __init__(self): - super().__init__(column_count=2) - self._parent_map = {} - - def GetColumnType(self, col): - if col == 0: - return wx.dataview.DataViewIconText - - return "long" - - def GetChildren(self, parent, children): - if not parent: - for connection in CONNECTIONS_LIST.get_value(): - children.append(self.ObjectToItem(connection)) - else: - obj = self.ItemToObject(parent) - if isinstance(obj, Connection): - for db in obj.context.databases.get_value(): - children.append(self.ObjectToItem(db)) - self._parent_map[self.ObjectToItem(db)] = parent - elif isinstance(obj, SQLDatabase): - categories = [ - ("Tables", BitmapList.DATABASE_TABLE, "tables"), - ("Views", BitmapList.SYSTEM_VIEW, "views"), - ("Procedures", BitmapList.SYSTEM_PROCEDURE, "procedures"), - ("Functions", BitmapList.SYSTEM_FUNCTION, "functions"), - ("Triggers", BitmapList.SYSTEM_TRIGGER, "triggers"), - ("Events", BitmapList.SYSTEM_EVENT, "events"), - ] - for name, icon, attr in categories: - cat = TreeCategory(name, icon, obj) - children.append(self.ObjectToItem(cat)) - self._parent_map[self.ObjectToItem(cat)] = parent - - elif isinstance(obj, TreeCategory): - observable = getattr(obj.database, obj.name.lower(), None) - if observable and observable.is_loaded: - objs = observable.get_value() - for o in objs: - children.append(self.ObjectToItem(o)) - self._parent_map[self.ObjectToItem(o)] = parent - - return len(children) - - def IsContainer(self, item): - if not item: - return True - - obj = self.ItemToObject(item) - return isinstance(obj, (Connection, SQLDatabase, TreeCategory)) - - def GetParent(self, item): - return self._parent_map.get(item, wx.dataview.NullDataViewItem) - - def GetValue(self, item, col): - node = self.ItemToObject(item) - - # if isinstance(node, Connection): - # bitmap = node.engine.value.bitmap - # mapper = {0: wx.dataview.DataViewIconText(node.name, bitmap), 1: ""} - # elif isinstance(node, SQLDatabase): - # mapper = {0: wx.dataview.DataViewIconText(node.name, BitmapList.SYSTEM_DATABASE), 1: bytes_to_human(node.total_bytes)} - # elif isinstance(node, Category): - # mapper = {0: wx.dataview.DataViewIconText(node.name, node.icon), 1: ""} - # elif isinstance(node, (SQLTable, SQLView, SQLTrigger, SQLProcedure, SQLFunction, SQLEvent)): - # icon_name = f"SYSTEM_{type(node).__name__[3:].upper()}" - # icon = getattr(BitmapList, icon_name, BitmapList.NOT_FOUND) - # mapper = {0: wx.dataview.DataViewIconText(node.name, icon), 1: ""} - # else: - mapper = {} - - - if isinstance(node, Connection): - mapper = {0: wx.dataview.DataViewIconText(node.name, node.engine.value.bitmap), 1: "", } - elif isinstance(node, SQLDatabase): - mapper = {0: wx.dataview.DataViewIconText(node.name, BitmapList.DATABASE), 1: bytes_to_human(node.total_bytes)} - elif isinstance(node, TreeCategory): - mapper = {0: wx.dataview.DataViewIconText(node.name, node.icon), 1: ""} - elif isinstance(node, SQLTable): - mapper = {0: wx.dataview.DataViewIconText(node.name, BitmapList.DATABASE_TABLE), 1: int((node.total_bytes / node.database.total_bytes) * 100)} - elif isinstance(node, SQLView): - mapper = {0: wx.dataview.DataViewIconText(node.name, BitmapList.SYSTEM_VIEW), 1: 0} - elif isinstance(node, SQLProcedure): - mapper = {0: wx.dataview.DataViewIconText(node.name, BitmapList.SYSTEM_PROCEDURE), 1: 0} - elif isinstance(node, SQLFunction): - mapper = {0: wx.dataview.DataViewIconText(node.name, BitmapList.SYSTEM_FUNCTION), 1: 0} - elif isinstance(node, SQLTrigger): - mapper = {0: wx.dataview.DataViewIconText(node.name, BitmapList.SYSTEM_TRIGGER), 1: 0} - elif isinstance(node, SQLEvent): - mapper = {0: wx.dataview.DataViewIconText(node.name, BitmapList.SYSTEM_EVENT), 1: 0} - - try : - return mapper[col] - except Exception as ex : - print("qsdq") - -class TreeExplorerController: - on_cancel_table: Callable - do_cancel_table: Callable - - def __init__(self, tree_ctrl_explorer: wx.dataview.DataViewCtrl): - self.app = wx.GetApp() - - self.tree_ctrl_explorer = tree_ctrl_explorer - - self.model = ExplorerTreeModel() - self.tree_ctrl_explorer.AssociateModel(self.model) - - # Set up columns - column0 = wx.dataview.DataViewColumn("Name", wx.dataview.DataViewIconTextRenderer(), 0, width=200, align=wx.ALIGN_LEFT) - column1 = wx.dataview.DataViewColumn("Usage", wx.dataview.DataViewProgressRenderer(), 1, width=100, align=wx.ALIGN_LEFT) - self.tree_ctrl_explorer.AppendColumn(column0) - self.tree_ctrl_explorer.AppendColumn(column1) - - self.tree_ctrl_explorer.Bind(wx.dataview.EVT_DATAVIEW_SELECTION_CHANGED, self._on_selection_changed) - self.tree_ctrl_explorer.Bind(wx.dataview.EVT_DATAVIEW_ITEM_EXPANDING, self._on_item_expanding) - self.tree_ctrl_explorer.Bind(wx.dataview.EVT_DATAVIEW_ITEM_ACTIVATED, self._on_item_activated) - - CONNECTIONS_LIST.subscribe(self._on_connections_changed) - - def _on_connections_changed(self, connections): - self.model.Cleared() - - def _on_item_expanding(self, event): - item = event.GetItem() - if not item.IsOk(): - return - - obj = self.model.ItemToObject(item) - if isinstance(obj, SQLDatabase): - for attr in ['tables', 'views', 'procedures', 'functions', 'triggers', 'events']: - observable = getattr(obj, attr, None) - if observable and not observable.is_loaded: - observable.load() - elif isinstance(obj, TreeCategory): - observable = getattr(obj.database, obj.name.lower(), None) - if observable and not observable.is_loaded: - observable.load() - event.Skip() - - def _on_selection_changed(self, event): - item = event.GetItem() - if not item.IsOk(): - return - - if NEW_TABLE.get_value() and not self.on_cancel_table(event): - return - - self.reset_current_objects() - - obj = self.model.ItemToObject(item) - - if isinstance(obj, Connection): - self.select_connection(obj) - elif isinstance(obj, SQLDatabase): - self.select_database(obj) - elif isinstance(obj, (SQLTable, SQLView, SQLTrigger, SQLProcedure, SQLFunction, SQLEvent)): - self.select_sql_object(obj) - - event.Skip() - - def _on_item_activated(self, event): - item = event.GetItem() - if not item.IsOk(): - return - - obj = self.model.ItemToObject(item) - - if isinstance(obj, (Connection, SQLDatabase, TreeCategory)): - if self.tree_ctrl_explorer.IsExpanded(item): - self.tree_ctrl_explorer.Collapse(item) - else: - self.tree_ctrl_explorer.Expand(item) - event.Skip() - - def reset_current_objects(self): - CURRENT_TABLE.set_value(None) - CURRENT_VIEW.set_value(None) - CURRENT_TRIGGER.set_value(None) - CURRENT_PROCEDURE.set_value(None) - CURRENT_EVENT.set_value(None) - CURRENT_FUNCTION.set_value(None) - - def select_connection(self, connection: Connection): - if connection == CURRENT_CONNECTION.get_value() and CURRENT_DATABASE.get_value(): - return - CURRENT_CONNECTION.set_value(connection) - CURRENT_DATABASE.set_value(None) - - def select_database(self, database: SQLDatabase): - if database != CURRENT_DATABASE.get_value(): - connection = database.context.connection - if connection != CURRENT_CONNECTION.get_value(): - CURRENT_CONNECTION.set_value(connection) - CURRENT_DATABASE.set_value(database) - - if not self.tree_ctrl_explorer.IsExpanded(self.model.ObjectToItem(database)): - wx.CallAfter(self.tree_ctrl_explorer.Expand, self.model.ObjectToItem(database)) - - def select_sql_object(self, sql_obj): - database = sql_obj.database - connection = database.context.connection - - if connection != CURRENT_CONNECTION.get_value(): - CURRENT_CONNECTION.set_value(connection) - - if database != CURRENT_DATABASE.get_value(): - CURRENT_DATABASE.set_value(database) - - if isinstance(sql_obj, SQLTable): - if not CURRENT_TABLE.get_value() or sql_obj != CURRENT_TABLE.get_value(): - CURRENT_TABLE.set_value(sql_obj.copy()) - - elif isinstance(sql_obj, SQLView): - if not CURRENT_VIEW.get_value() or sql_obj != CURRENT_VIEW.get_value(): - CURRENT_VIEW.set_value(sql_obj.copy()) - - elif isinstance(sql_obj, SQLTrigger): - if not CURRENT_TRIGGER.get_value() or sql_obj != CURRENT_TRIGGER.get_value(): - CURRENT_TRIGGER.set_value(sql_obj.copy()) - - elif isinstance(sql_obj, SQLProcedure): - if not CURRENT_PROCEDURE.get_value() or sql_obj != CURRENT_PROCEDURE.get_value(): - CURRENT_PROCEDURE.set_value(sql_obj.copy()) - - elif isinstance(sql_obj, SQLFunction): - if not CURRENT_FUNCTION.get_value() or sql_obj != CURRENT_FUNCTION.get_value(): - CURRENT_FUNCTION.set_value(sql_obj.copy()) - - elif isinstance(sql_obj, SQLEvent): - if not CURRENT_EVENT.get_value() or sql_obj != CURRENT_EVENT.get_value(): - CURRENT_EVENT.set_value(sql_obj.copy()) diff --git a/windows/main/__init__.py b/windows/main/__init__.py index f326963..8d7fd9e 100755 --- a/windows/main/__init__.py +++ b/windows/main/__init__.py @@ -1,24 +1,35 @@ -from helpers.observables import Observable, ObservableList +from windows.state import ( + AUTO_APPLY, + CURRENT_COLUMN, + CURRENT_CONNECTION, + CURRENT_DATABASE, + CURRENT_EVENT, + CURRENT_FOREIGN_KEY, + CURRENT_FUNCTION, + CURRENT_INDEX, + CURRENT_PROCEDURE, + CURRENT_RECORDS, + CURRENT_SESSION, + CURRENT_TABLE, + CURRENT_TRIGGER, + CURRENT_VIEW, + SESSIONS_LIST, +) -from structures.session import Session -from structures.connection import Connection -from structures.engines.database import SQLDatabase, SQLTable, SQLColumn, SQLForeignKey, SQLIndex, SQLRecord, SQLTrigger, SQLView - -SESSIONS_LIST: ObservableList[Session] = ObservableList() -# CONNECTIONS_LIST: ObservableList[Connection] = ObservableList() - -CURRENT_SESSION: Observable[Session] = Observable() -CURRENT_CONNECTION: Observable[Connection] = Observable() -CURRENT_DATABASE: Observable[SQLDatabase] = Observable() -CURRENT_TABLE: Observable[SQLTable] = Observable() -CURRENT_VIEW: Observable[SQLView] = Observable() -CURRENT_TRIGGER: Observable[SQLTrigger] = Observable() -CURRENT_FUNCTION: Observable[SQLTrigger] = Observable() -CURRENT_PROCEDURE: Observable[SQLTrigger] = Observable() -CURRENT_EVENT: Observable[SQLTrigger] = Observable() -CURRENT_COLUMN: Observable[SQLColumn] = Observable() -CURRENT_INDEX: Observable[SQLIndex] = Observable() -CURRENT_FOREIGN_KEY: Observable[SQLForeignKey] = Observable() -CURRENT_RECORDS: ObservableList[SQLRecord] = ObservableList() - -AUTO_APPLY: Observable[bool] = Observable(True) +__all__ = [ + "AUTO_APPLY", + "CURRENT_COLUMN", + "CURRENT_CONNECTION", + "CURRENT_DATABASE", + "CURRENT_EVENT", + "CURRENT_FOREIGN_KEY", + "CURRENT_FUNCTION", + "CURRENT_INDEX", + "CURRENT_PROCEDURE", + "CURRENT_RECORDS", + "CURRENT_SESSION", + "CURRENT_TABLE", + "CURRENT_TRIGGER", + "CURRENT_VIEW", + "SESSIONS_LIST", +] diff --git a/windows/main/main_frame.py b/windows/main/controller.py similarity index 83% rename from windows/main/main_frame.py rename to windows/main/controller.py index 06e6d42..78a58da 100755 --- a/windows/main/main_frame.py +++ b/windows/main/controller.py @@ -16,25 +16,29 @@ from helpers.logger import logger from helpers.observables import CallbackEvent +from structures.session import Session from structures.connection import Connection, ConnectionEngine from structures.engines.context import QUERY_LOGS from structures.engines.database import SQLTable, SQLColumn, SQLIndex, SQLForeignKey, SQLRecord, SQLView, SQLTrigger, SQLDatabase -from windows import MainFrameView +from windows.views import MainFrameView from windows.components.stc.styles import apply_stc_theme from windows.components.stc.profiles import SQL -from windows.components.stc.auto_complete import SQLAutoCompleteController, SQLCompletionProvider +from windows.components.stc.autocomplete.auto_complete import SQLAutoCompleteController, SQLCompletionProvider +from windows.components.stc.template_menu import SQLTemplateMenuController from windows.main import CURRENT_CONNECTION, CURRENT_SESSION, CURRENT_DATABASE, CURRENT_TABLE, CURRENT_COLUMN, CURRENT_INDEX, CURRENT_FOREIGN_KEY, CURRENT_RECORDS, AUTO_APPLY, CURRENT_VIEW, CURRENT_TRIGGER -from windows.main.table import EditTableModel, NEW_TABLE -from windows.main.index import TableIndexController -from windows.main.check import TableCheckController -from windows.main.column import TableColumnsController -from windows.main.records import TableRecordsController -from windows.main.database import ListDatabaseTable -from windows.main.explorer import TreeExplorerController -from windows.main.foreign_key import TableForeignKeyController +from windows.main.tabs.query import QueryResultsController +from windows.main.tabs.table import EditTableModel, NEW_TABLE +from windows.main.tabs.index import TableIndexController +from windows.main.tabs.check import TableCheckController +from windows.main.tabs.column import TableColumnsController +from windows.main.tabs.records import TableRecordsController +from windows.main.tabs.database import ListDatabaseTable +from windows.main.tabs.explorer import TreeExplorerController +from windows.main.tabs.foreign_key import TableForeignKeyController +from windows.main.tabs.view import ViewEditorController class MainFrameController(MainFrameView): @@ -43,7 +47,7 @@ class MainFrameController(MainFrameView): def __init__(self): super().__init__(None) - self.styled_text_ctrls_name = ["sql_query_logs", "sql_view", "sql_query_filters", "sql_create_table", "sql_query"] + self.styled_text_ctrls_name = ["sql_query_logs", "stc_view_select", "sql_query_filters", "sql_create_table", "sql_query_editor"] self.edit_table_model = EditTableModel() self.edit_table_model.bind_controls( @@ -66,6 +70,10 @@ def __init__(self): self.controller_list_table_check = TableCheckController(self.dv_table_checks) self.controller_list_table_foreign_key = TableForeignKeyController(self.dv_table_foreign_keys) + self.controller_query_records = QueryResultsController(self.sql_query_editor, self.notebook_sql_results) + + self.controller_view_editor = ViewEditorController(self) + self._setup_query_editors() self._setup_subscribers() @@ -101,6 +109,14 @@ def _setup_query_editors(self): sql_autocomplete_controller = SQLAutoCompleteController( editor=styled_text_ctrl, provider=sql_completion_provider, + settings=wx.GetApp().settings, + theme_loader=wx.GetApp().theme_loader, + ) + + sql_template_menu = SQLTemplateMenuController( + editor=styled_text_ctrl, + get_database=lambda: CURRENT_DATABASE.get_value(), + get_current_table=lambda: CURRENT_TABLE.get_value(), ) def _setup_subscribers(self): @@ -188,56 +204,79 @@ def do_close(self, event): wx.GetApp().ExitMainLoop() def do_open_connection_manager(self, event): - from windows.connections.manager import ConnectionsManager + from windows.dialogs.connections.view import ConnectionsManager sm = ConnectionsManager(self) sm.Show() - def toggle_panel(self, current: Optional[Union[Connection, SQLDatabase, SQLTable, SQLView, SQLTrigger]] = None): - current_connection = CURRENT_CONNECTION() - current_database = CURRENT_DATABASE() - current_table = CURRENT_TABLE() - current_view = CURRENT_VIEW() - current_trigger = CURRENT_TRIGGER() + def on_open_settings(self, event): + from windows.dialogs.settings.controller import SettingsController + + controller = SettingsController(self, wx.GetApp().settings) + if controller.show_modal() == wx.ID_OK: + wx.MessageBox(_("Settings saved successfully"), _("Settings"), wx.OK | wx.ICON_INFORMATION) - self.MainFrameNotebook.SetSelection(0) + def toggle_panel(self, current: Optional[Union[SQLDatabase, SQLTable, SQLView, SQLTrigger]] = None): + # self.MainFrameNotebook.SetSelection(0) - if not current_database: - self.MainFrameNotebook.GetPage(1).Hide() + current_session = CURRENT_SESSION.get_value() + current_database = CURRENT_DATABASE.get_value() + current_table = CURRENT_TABLE.get_value() + current_view = CURRENT_VIEW.get_value() + current_trigger = CURRENT_TRIGGER.get_value() - if not current_table: - self.MainFrameNotebook.GetPage(2).Hide() + total_pages = self.MainFrameNotebook.GetPageCount() - if not current_view: - self.MainFrameNotebook.GetPage(3).Hide() + if not current: + if not current_session: + for page in range(total_pages): + self.MainFrameNotebook.GetPage(page).Hide() - if not current_trigger: - self.MainFrameNotebook.GetPage(4).Hide() + if not current_database: + for page in range(1, total_pages): + self.MainFrameNotebook.GetPage(page).Hide() - if not current_table and not current_view: - self.MainFrameNotebook.GetPage(5).Hide() + if not current_table: + self.MainFrameNotebook.GetPage(2).Hide() + self.MainFrameNotebook.GetPage(5).Hide() - if isinstance(current, Connection): + if not current_view: + self.MainFrameNotebook.GetPage(3).Hide() + self.MainFrameNotebook.GetPage(5).Hide() + + if not current_trigger: + self.MainFrameNotebook.GetPage(4).Hide() + + return + + if isinstance(current, Session): + self.MainFrameNotebook.GetPage(0).Show() self.MainFrameNotebook.SetSelection(0) elif isinstance(current, SQLDatabase): self.MainFrameNotebook.GetPage(1).Show() + self.MainFrameNotebook.GetPage(6).Show() self.MainFrameNotebook.SetSelection(1) elif isinstance(current, SQLTable) or isinstance(current, SQLView): if isinstance(current, SQLTable): self.MainFrameNotebook.GetPage(2).Show() - self.MainFrameNotebook.SetSelection(2) + if self.MainFrameNotebook.GetSelection() < 2: + self.MainFrameNotebook.SetSelection(2) if isinstance(current, SQLView): self.MainFrameNotebook.GetPage(3).Show() - self.MainFrameNotebook.SetSelection(3) + if self.MainFrameNotebook.GetSelection() < 3: + self.MainFrameNotebook.SetSelection(3) self.MainFrameNotebook.GetPage(5).Show() + self.MainFrameNotebook.GetPage(6).Show() elif isinstance(current, SQLTrigger): self.MainFrameNotebook.GetPage(4).Show() - self.MainFrameNotebook.SetSelection(3) + self.MainFrameNotebook.GetPage(6).Show() + if self.MainFrameNotebook.GetSelection() < 4: + self.MainFrameNotebook.SetSelection(3) def on_page_chaged(self, event): if int(event.Selection) == 5: diff --git a/windows/main/tabs/__init__.py b/windows/main/tabs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/windows/main/check.py b/windows/main/tabs/check.py similarity index 98% rename from windows/main/check.py rename to windows/main/tabs/check.py index e50faa1..2928d75 100755 --- a/windows/main/check.py +++ b/windows/main/tabs/check.py @@ -9,7 +9,7 @@ from structures.engines.database import SQLCheck, SQLTable from windows.main import CURRENT_INDEX, CURRENT_TABLE -from windows.main.column import NEW_TABLE +from windows.main.tabs.column import NEW_TABLE class TableCheckModel(BaseDataViewListModel): diff --git a/windows/main/column.py b/windows/main/tabs/column.py similarity index 98% rename from windows/main/column.py rename to windows/main/tabs/column.py index be40b72..c4aca9b 100644 --- a/windows/main/column.py +++ b/windows/main/tabs/column.py @@ -12,9 +12,8 @@ from structures.engines.database import SQLColumn, SQLDatabase, SQLIndex, SQLTable from structures.engines.indextype import SQLIndexType -from windows import TableColumnsDataViewCtrl -from windows.main import CURRENT_COLUMN, CURRENT_SESSION, CURRENT_DATABASE, CURRENT_TABLE -from windows.main.table import NEW_TABLE +from windows.views import TableColumnsDataViewCtrl +from windows.state import CURRENT_COLUMN, CURRENT_SESSION, CURRENT_DATABASE, CURRENT_TABLE, NEW_TABLE class ColumnModel(BaseDataViewListModel): diff --git a/windows/main/database.py b/windows/main/tabs/database.py similarity index 100% rename from windows/main/database.py rename to windows/main/tabs/database.py diff --git a/windows/main/explorer.py b/windows/main/tabs/explorer.py similarity index 96% rename from windows/main/explorer.py rename to windows/main/tabs/explorer.py index a323bdb..db28fd1 100755 --- a/windows/main/explorer.py +++ b/windows/main/tabs/explorer.py @@ -14,8 +14,7 @@ from structures.connection import Connection from structures.engines.database import SQLDatabase, SQLTable, SQLView, SQLTrigger, SQLProcedure, SQLFunction, SQLEvent -from windows.main import CURRENT_DATABASE, CURRENT_TABLE, CURRENT_CONNECTION, CURRENT_SESSION, CURRENT_VIEW, CURRENT_TRIGGER, SESSIONS_LIST, CURRENT_EVENT, CURRENT_FUNCTION, CURRENT_PROCEDURE -from windows.main.table import NEW_TABLE +from windows.state import CURRENT_DATABASE, CURRENT_TABLE, CURRENT_CONNECTION, CURRENT_SESSION, CURRENT_VIEW, CURRENT_TRIGGER, SESSIONS_LIST, CURRENT_EVENT, CURRENT_FUNCTION, CURRENT_PROCEDURE, NEW_TABLE @dataclasses.dataclass @@ -192,7 +191,7 @@ def select_session(self, session: Session, event): return CURRENT_SESSION.set_value(session) CURRENT_CONNECTION.set_value(session.connection) - CURRENT_DATABASE.set_value(None) + # CURRENT_DATABASE.set_value(None) def select_database(self, database: SQLDatabase, item, event): if database != CURRENT_DATABASE.get_value(): @@ -206,6 +205,7 @@ def select_database(self, database: SQLDatabase, item, event): session.connect() CURRENT_DATABASE.set_value(database) + CURRENT_SESSION.get_value().context.set_database(database) self.load_observables(item, database) @@ -221,6 +221,8 @@ def select_sql_object(self, sql_obj): if database != CURRENT_DATABASE.get_value(): CURRENT_DATABASE.set_value(database) + database.context + CURRENT_SESSION.get_value().context.set_database(database) if isinstance(sql_obj, SQLTable): if not CURRENT_TABLE.get_value() or sql_obj != CURRENT_TABLE.get_value(): diff --git a/windows/main/foreign_key.py b/windows/main/tabs/foreign_key.py similarity index 97% rename from windows/main/foreign_key.py rename to windows/main/tabs/foreign_key.py index 95260ff..20c2c54 100755 --- a/windows/main/foreign_key.py +++ b/windows/main/tabs/foreign_key.py @@ -10,9 +10,8 @@ from structures.helpers import merge_original_current -from windows import TableForeignKeysDataViewCtrl -from windows.main import CURRENT_TABLE, CURRENT_FOREIGN_KEY, CURRENT_SESSION -from windows.main.table import NEW_TABLE +from windows.views import TableForeignKeysDataViewCtrl +from windows.state import CURRENT_TABLE, CURRENT_FOREIGN_KEY, CURRENT_SESSION, NEW_TABLE from structures.engines.database import SQLForeignKey, SQLTable diff --git a/windows/main/index.py b/windows/main/tabs/index.py similarity index 98% rename from windows/main/index.py rename to windows/main/tabs/index.py index a9c10d0..00bce6e 100755 --- a/windows/main/index.py +++ b/windows/main/tabs/index.py @@ -6,7 +6,7 @@ from structures.helpers import merge_original_current from windows.main import CURRENT_TABLE, CURRENT_INDEX -from windows.main.column import NEW_TABLE +from windows.main.tabs.column import NEW_TABLE from structures.engines.database import SQLTable, SQLIndex diff --git a/windows/main/tabs/query.py b/windows/main/tabs/query.py new file mode 100644 index 0000000..bc35b70 --- /dev/null +++ b/windows/main/tabs/query.py @@ -0,0 +1,501 @@ +import dataclasses +import enum +import threading +import time + +from typing import Any, Callable, Optional +from gettext import gettext as _ + +import wx +import wx.dataview + +from helpers.logger import logger + +from structures.session import Session +from structures.connection import ConnectionEngine + +from windows.components.dataview import QueryEditorResultsDataViewCtrl + + +@dataclasses.dataclass +class ParsedStatement: + text: str + start_pos: int + end_pos: int + statement_index: int + + +@dataclasses.dataclass +class ExecutionResult: + statement: ParsedStatement + success: bool + columns: Optional[list[str]] = None + rows: Optional[list[tuple]] = None + affected_rows: Optional[int] = None + elapsed_ms: float = 0.0 + error: Optional[str] = None + warnings: list[str] = dataclasses.field(default_factory=list) + + +class ExecutionMode(enum.Enum): + ALL = "all" + SELECTION = "selection" + CURRENT = "current" + + +class SQLStatementParser: + def __init__(self, engine: ConnectionEngine): + self.engine = engine + + def parse(self, sql_text: str) -> list[ParsedStatement]: + if not sql_text.strip(): + return [] + + statements = [] + statement_index = 0 + current_start = 0 + i = 0 + length = len(sql_text) + + in_single_quote = False + in_double_quote = False + in_line_comment = False + in_block_comment = False + + while i < length: + char = sql_text[i] + + if in_line_comment: + if char == '\n': + in_line_comment = False + i += 1 + continue + + if in_block_comment: + if i + 1 < length and sql_text[i:i+2] == '*/': + in_block_comment = False + i += 2 + continue + i += 1 + continue + + if not in_single_quote and not in_double_quote: + if self._is_line_comment_start(sql_text, i): + in_line_comment = True + i += 2 + continue + + if self._is_block_comment_start(sql_text, i): + in_block_comment = True + i += 2 + continue + + if char == "'" and not in_double_quote: + if i + 1 < length and sql_text[i+1] == "'": + i += 2 + continue + in_single_quote = not in_single_quote + + elif char == '"' and not in_single_quote: + if i + 1 < length and sql_text[i+1] == '"': + i += 2 + continue + in_double_quote = not in_double_quote + + elif char == ';' and not in_single_quote and not in_double_quote: + statement_text = sql_text[current_start:i].strip() + if statement_text: + statements.append(ParsedStatement( + text=statement_text, + start_pos=current_start, + end_pos=i, + statement_index=statement_index + )) + statement_index += 1 + current_start = i + 1 + + i += 1 + + final_statement = sql_text[current_start:].strip() + if final_statement: + statements.append(ParsedStatement( + text=final_statement, + start_pos=current_start, + end_pos=length, + statement_index=statement_index + )) + + return statements + + def _is_line_comment_start(self, text: str, pos: int) -> bool: + if pos + 1 >= len(text): + return False + return text[pos:pos+2] in ('--', '# ') + + def _is_block_comment_start(self, text: str, pos: int) -> bool: + if pos + 1 >= len(text): + return False + return text[pos:pos+2] == '/*' + + +class StatementSelector: + def __init__(self, stc_editor: wx.stc.StyledTextCtrl): + self.editor = stc_editor + + def get_execution_scope( + self, + statements: list[ParsedStatement] + ) -> tuple[ExecutionMode, list[ParsedStatement]]: + selection_start = self.editor.GetSelectionStart() + selection_end = self.editor.GetSelectionEnd() + + if selection_start != selection_end: + selected_text = self.editor.GetSelectedText().strip() + if selected_text: + return (ExecutionMode.SELECTION, [ParsedStatement( + text=selected_text, + start_pos=selection_start, + end_pos=selection_end, + statement_index=0 + )]) + + caret_pos = self.editor.GetCurrentPos() + current_stmt = self._find_statement_at_caret(caret_pos, statements) + + if current_stmt: + return (ExecutionMode.CURRENT, [current_stmt]) + + return (ExecutionMode.ALL, statements) + + def _find_statement_at_caret( + self, + caret_pos: int, + statements: list[ParsedStatement] + ) -> Optional[ParsedStatement]: + for stmt in statements: + if stmt.start_pos <= caret_pos <= stmt.end_pos: + return stmt + + # Caret is in whitespace: execute next statement + for stmt in statements: + if caret_pos < stmt.start_pos: + return stmt + + # Caret after all statements: execute last + if statements: + return statements[-1] + + return None + + +class QueryExecutor: + def __init__(self, session: Session): + self.session = session + self._cancel_requested = False + self._current_thread: Optional[threading.Thread] = None + self._lock = threading.Lock() + + def execute_statements( + self, + statements: list[ParsedStatement], + on_statement_complete: Callable[[ExecutionResult], None], + on_all_complete: Callable[[], None], + stop_on_error: bool = True + ) -> None: + self._cancel_requested = False + + self._current_thread = threading.Thread( + target=self._execute_worker, + args=(statements, on_statement_complete, on_all_complete, stop_on_error), + daemon=True + ) + self._current_thread.start() + + def _execute_worker( + self, + statements: list[ParsedStatement], + on_statement_complete: Callable[[ExecutionResult], None], + on_all_complete: Callable[[], None], + stop_on_error: bool + ) -> None: + try: + for stmt in statements: + if self._cancel_requested: + break + + result = self._execute_single(stmt) + # Thread-safe UI update + wx.CallAfter(on_statement_complete, result) + + if not result.success and stop_on_error: + break + + except Exception as ex: + logger.error(f"Execution worker error: {ex}", exc_info=True) + finally: + wx.CallAfter(on_all_complete) + + def _execute_single(self, statement: ParsedStatement) -> ExecutionResult: + start_time = time.time() + + try: + self.session.context.execute(statement.text) + + elapsed_ms = (time.time() - start_time) * 1000 + + cursor = self.session.context.cursor + if cursor.description: + columns = [desc[0] for desc in cursor.description] + rows = self.session.context.fetchall() + + return ExecutionResult( + statement=statement, + success=True, + columns=columns, + rows=rows, + affected_rows=len(rows), + elapsed_ms=elapsed_ms + ) + else: + affected = cursor.rowcount if cursor.rowcount >= 0 else 0 + + return ExecutionResult( + statement=statement, + success=True, + affected_rows=affected, + elapsed_ms=elapsed_ms + ) + + except Exception as ex: + elapsed_ms = (time.time() - start_time) * 1000 + + return ExecutionResult( + statement=statement, + success=False, + error=str(ex), + elapsed_ms=elapsed_ms + ) + + def cancel(self) -> None: + self._cancel_requested = True + + def is_running(self) -> bool: + return self._current_thread is not None and self._current_thread.is_alive() + + +class QueryResultsRenderer: + def __init__(self, notebook: wx.Notebook, session: Session): + self.notebook = notebook + self.session = session + self._tab_counter = 0 + + def create_result_tab(self, result: ExecutionResult) -> wx.Panel: + self._tab_counter += 1 + + panel = wx.Panel(self.notebook) + sizer = wx.BoxSizer(wx.VERTICAL) + + if result.success and result.columns: + grid = QueryEditorResultsDataViewCtrl(panel) + self._populate_grid(grid, result) + sizer.Add(grid, 1, wx.EXPAND | wx.ALL, 5) + + tab_name = self._generate_tab_name(result) + elif result.success: + msg = wx.StaticText(panel, label=_("{} rows affected").format(result.affected_rows or 0)) + msg.SetFont(msg.GetFont().MakeBold()) + sizer.Add(msg, 1, wx.ALIGN_CENTER | wx.ALL, 20) + + tab_name = _("Query {}").format(self._tab_counter) + else: + error_panel = self._create_error_panel(panel, result) + sizer.Add(error_panel, 1, wx.EXPAND | wx.ALL, 5) + + tab_name = _("Query {} (Error)").format(self._tab_counter) + + footer = self._create_footer(panel, result) + sizer.Add(footer, 0, wx.EXPAND | wx.ALL, 5) + + panel.SetSizer(sizer) + self.notebook.AddPage(panel, tab_name, select=True) + + return panel + + def _generate_tab_name(self, result: ExecutionResult) -> str: + if result.columns and result.rows is not None: + return _("Query {} ({} rows × {} cols)").format( + self._tab_counter, + len(result.rows), + len(result.columns) + ) + return _("Query {}").format(self._tab_counter) + + def _populate_grid( + self, + grid: QueryEditorResultsDataViewCtrl, + result: ExecutionResult + ) -> None: + if not result.columns or not result.rows: + return + + for col_name in result.columns: + grid.AppendTextColumn(col_name, wx.dataview.DATAVIEW_CELL_INERT) + + model = grid.GetModel() + if hasattr(model, 'data'): + model.data = list(result.rows) + model.Reset(len(result.rows)) + + def _create_footer(self, parent: wx.Panel, result: ExecutionResult) -> wx.StaticText: + parts = [] + + if result.affected_rows is not None: + parts.append(_("{} rows").format(result.affected_rows)) + + parts.append(_("{:.1f} ms").format(result.elapsed_ms)) + + if result.warnings: + parts.append(_("{} warnings").format(len(result.warnings))) + + footer_text = " | ".join(parts) + footer = wx.StaticText(parent, label=footer_text) + footer.SetForegroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT)) + + return footer + + def _create_error_panel(self, parent: wx.Panel, result: ExecutionResult) -> wx.Panel: + error_panel = wx.Panel(parent) + error_sizer = wx.BoxSizer(wx.VERTICAL) + + error_label = wx.StaticText(error_panel, label=_("Error:")) + error_label.SetFont(error_label.GetFont().MakeBold()) + error_sizer.Add(error_label, 0, wx.ALL, 5) + + error_text = wx.TextCtrl( + error_panel, + value=result.error or _("Unknown error"), + style=wx.TE_MULTILINE | wx.TE_READONLY | wx.TE_WORDWRAP + ) + error_text.SetBackgroundColour(wx.Colour(255, 240, 240)) + error_sizer.Add(error_text, 1, wx.EXPAND | wx.ALL, 5) + + error_panel.SetSizer(error_sizer) + return error_panel + + def clear_all_tabs(self) -> None: + while self.notebook.GetPageCount() > 0: + self.notebook.DeletePage(0) + self._tab_counter = 0 + + +class QueryEditorController: + def __init__( + self, + stc_editor: wx.stc.StyledTextCtrl, + results_notebook: wx.Notebook, + session_provider: Callable[[], Optional[Session]] + ): + self.editor = stc_editor + self.notebook = results_notebook + self.get_session = session_provider + + self.parser: Optional[SQLStatementParser] = None + self.selector = StatementSelector(stc_editor) + self.executor: Optional[QueryExecutor] = None + self.renderer: Optional[QueryResultsRenderer] = None + + self._bind_shortcuts() + + def _bind_shortcuts(self) -> None: + self.editor.Bind(wx.EVT_KEY_DOWN, self._on_key_down) + + def _on_key_down(self, event: wx.KeyEvent) -> None: + key_code = event.GetKeyCode() + ctrl_down = event.ControlDown() + shift_down = event.ShiftDown() + + if key_code == wx.WXK_F5: + if shift_down: + self.cancel_execution(event) + else: + self.execute_all(event) + return + + if ctrl_down and key_code == wx.WXK_RETURN: + self.execute_current(event) + return + + if ctrl_down and shift_down and key_code == ord('C'): + self.cancel_execution(event) + return + + event.Skip() + + def execute_all(self, event: wx.Event) -> None: + self._execute(ExecutionMode.ALL) + + def execute_current(self, event: wx.Event) -> None: + self._execute(ExecutionMode.CURRENT) + + def cancel_execution(self, event: wx.Event) -> None: + if self.executor and self.executor.is_running(): + self.executor.cancel() + logger.info("Query execution cancelled") + + def _execute(self, mode: ExecutionMode) -> None: + session = self.get_session() + if not session or not session.is_connected: + wx.MessageBox( + _("No active database connection"), + _("Error"), + wx.OK | wx.ICON_ERROR + ) + return + + if not self.parser or self.parser.engine != session.engine: + self.parser = SQLStatementParser(session.engine) + self.executor = QueryExecutor(session) + self.renderer = QueryResultsRenderer(self.notebook, session) + + sql_text = self.editor.GetText() + if not sql_text.strip(): + return + + statements = self.parser.parse(sql_text) + if not statements: + return + + if mode == ExecutionMode.CURRENT or mode == ExecutionMode.SELECTION: + _, statements_to_execute = self.selector.get_execution_scope(statements) + else: + statements_to_execute = statements + + if not statements_to_execute: + return + + self.renderer.clear_all_tabs() + + self.executor.execute_statements( + statements=statements_to_execute, + on_statement_complete=self._on_statement_complete, + on_all_complete=self._on_all_complete, + stop_on_error=True + ) + + def _on_statement_complete(self, result: ExecutionResult) -> None: + if self.renderer: + self.renderer.create_result_tab(result) + + def _on_all_complete(self) -> None: + logger.info("Query execution completed") + + +class QueryResultsController(QueryEditorController): + def __init__(self, stc_sql_query: wx.stc.StyledTextCtrl, notebook_sql_results: wx.Notebook): + from windows.main import CURRENT_SESSION + + super().__init__( + stc_editor=stc_sql_query, + results_notebook=notebook_sql_results, + session_provider=lambda: CURRENT_SESSION.get_value() + ) diff --git a/windows/main/records.py b/windows/main/tabs/records.py similarity index 82% rename from windows/main/records.py rename to windows/main/tabs/records.py index 5d45755..4f15fd9 100644 --- a/windows/main/records.py +++ b/windows/main/tabs/records.py @@ -13,11 +13,9 @@ from structures.engines.database import SQLTable, SQLDatabase, SQLColumn, SQLRecord from structures.engines.datatype import DataTypeCategory -from windows import TableRecordsDataViewCtrl, AdvancedCellEditorDialog +from windows.views import TableRecordsDataViewCtrl -from windows.components.stc.detectors import detect_syntax_id -from windows.components.stc.profiles import SyntaxProfile -from windows.components.stc.styles import apply_stc_theme +from windows.dialogs.advanced_cell_editor import AdvancedCellEditorController from windows.main import CURRENT_TABLE, CURRENT_SESSION, CURRENT_DATABASE, AUTO_APPLY, CURRENT_RECORDS @@ -302,60 +300,3 @@ def do_delete_record(self): # records = sorted(self.list_ctrl_records.GetModel().records, key=sort_func) # model = RecordsModel(self.table, records) # self.list_ctrl_records.AssociateModel(model) - - -class AdvancedCellEditorController(AdvancedCellEditorDialog): - app = wx.GetApp() - - def __init__(self, parent, value: str): - super().__init__(parent) - - self.syntax_choice.AppendItems(self.app.syntax_registry.labels()) - self.advanced_stc_editor.SetText(value or "") - self.advanced_stc_editor.EmptyUndoBuffer() - - self.app.theme_manager.register(self.advanced_stc_editor, self._get_current_syntax_profile) - - self.syntax_choice.SetStringSelection(self._auto_syntax_profile().label) - - self.do_apply_syntax(do_format=True) - - def _auto_syntax_profile(self) -> SyntaxProfile: - text = self.advanced_stc_editor.GetText() - - syntax_id = detect_syntax_id(text) - return self.app.syntax_registry.get(syntax_id) - - def _get_current_syntax_profile(self) -> SyntaxProfile: - label = self.syntax_choice.GetStringSelection() - # text = self.advanced_stc_editor.GetText() - # - # syntax_id = detect_syntax_id(text) - return self.app.syntax_registry.get(label) - - def on_syntax_changed(self, _evt): - label = self.syntax_choice.GetStringSelection() - self.do_apply_syntax(label) - - def do_apply_syntax(self, do_format: bool = True): - label = self.syntax_choice.GetStringSelection() - syntax_profile = self.app.syntax_registry.by_label(label) - - apply_stc_theme(self.advanced_stc_editor, syntax_profile) - - if do_format and syntax_profile.formatter: - old = self.advanced_stc_editor.GetText() - try: - formatted = syntax_profile.formatter(old) - except Exception: - return - - if formatted != old: - self._replace_text_undo_friendly(formatted) - - def _replace_text_undo_friendly(self, new_text: str): - self.advanced_stc_editor.BeginUndoAction() - try: - self.advanced_stc_editor.SetText(new_text) - finally: - self.advanced_stc_editor.EndUndoAction() diff --git a/windows/main/table.py b/windows/main/tabs/table.py similarity index 94% rename from windows/main/table.py rename to windows/main/tabs/table.py index 883ac4e..e53116f 100644 --- a/windows/main/table.py +++ b/windows/main/tabs/table.py @@ -1,11 +1,9 @@ from helpers.bindings import AbstractModel from helpers.observables import Observable, debounce, ObservableList -from windows.main import CURRENT_TABLE, CURRENT_DATABASE - from structures.engines.database import SQLTable -NEW_TABLE: Observable[SQLTable] = Observable() +from windows.state import CURRENT_TABLE, CURRENT_DATABASE, NEW_TABLE class EditTableModel(AbstractModel): diff --git a/windows/main/tabs/view.py b/windows/main/tabs/view.py new file mode 100644 index 0000000..5c1cb44 --- /dev/null +++ b/windows/main/tabs/view.py @@ -0,0 +1,451 @@ +from typing import Optional + +import wx +import wx.stc + +from gettext import gettext as _ + +from helpers.sql import format_sql +from helpers.bindings import AbstractModel, wx_call_after_debounce +from helpers.observables import Observable + +from structures.connection import ConnectionEngine +from structures.engines.database import SQLView + +from windows.main import CURRENT_SESSION, CURRENT_DATABASE, CURRENT_VIEW + + +class EditViewModel(AbstractModel): + def __init__(self): + self.name = Observable() + self.schema = Observable() + self.definer = Observable() + self.sql_security = Observable() + self.algorithm = Observable() + self.constraint = Observable() + self.security_barrier = Observable() + self.force = Observable() + self.select_statement = Observable() + + wx_call_after_debounce( + self.name, self.schema, self.definer, self.sql_security, + self.algorithm, self.constraint, self.security_barrier, + self.force, self.select_statement, + callback=self.update_view + ) + + CURRENT_VIEW.subscribe(self._load_view) + + def _load_view(self, view: Optional[SQLView]): + if view is None: + return + + self.name.set_initial(view.name) + + if session := CURRENT_SESSION.get_value() : + dialect = session.engine.value.dialect + formatted_sql = format_sql(view.statement, dialect) + self.select_statement.set_initial(formatted_sql) + else: + self.select_statement.set_initial(view.statement) + + if not session: + return + + engine = session.engine + + if engine in (ConnectionEngine.MYSQL, ConnectionEngine.MARIADB): + self._load_mysql_fields(view) + elif engine == ConnectionEngine.POSTGRESQL: + self._load_postgresql_fields(view) + elif engine == ConnectionEngine.ORACLE: + self._load_oracle_fields(view) + + def update_view(self, *args): + if not any(args): + return + + view = CURRENT_VIEW.get_value() + if not view: + return + + view.name = self.name.get_value() + view.statement = self.select_statement.get_value() + + session = CURRENT_SESSION.get_value() + if not session: + return + + engine = session.engine + + if engine in (ConnectionEngine.MYSQL, ConnectionEngine.MARIADB): + self._update_mysql_fields(view) + elif engine == ConnectionEngine.POSTGRESQL: + self._update_postgresql_fields(view) + elif engine == ConnectionEngine.ORACLE: + self._update_oracle_fields(view) + + def _load_mysql_fields(self, view: SQLView): + if hasattr(view, "definer"): + self.definer.set_initial(view.definer) + if hasattr(view, "sql_security"): + self.sql_security.set_initial(view.sql_security) + if hasattr(view, "algorithm"): + self.algorithm.set_initial(view.algorithm) + if hasattr(view, "constraint"): + self.constraint.set_initial(view.constraint) + + def _load_postgresql_fields(self, view: SQLView): + if hasattr(view, "schema"): + self.schema.set_initial(view.schema) + if hasattr(view, "constraint"): + self.constraint.set_initial(view.constraint) + if hasattr(view, "security_barrier"): + self.security_barrier.set_initial(view.security_barrier) + + def _load_oracle_fields(self, view: SQLView): + if hasattr(view, "schema"): + self.schema.set_initial(view.schema) + if hasattr(view, "constraint"): + self.constraint.set_initial(view.constraint) + if hasattr(view, "force"): + self.force.set_initial(view.force) + + def _update_mysql_fields(self, view: SQLView): + if hasattr(view, "definer"): + view.definer = self.definer.get_value() + if hasattr(view, "sql_security"): + view.sql_security = self.sql_security.get_value() + if hasattr(view, "algorithm"): + view.algorithm = self.algorithm.get_value() + if hasattr(view, "constraint"): + view.constraint = self.constraint.get_value() + + def _update_postgresql_fields(self, view: SQLView): + if hasattr(view, "schema"): + view.schema = self.schema.get_value() + if hasattr(view, "constraint"): + view.constraint = self.constraint.get_value() + if hasattr(view, "security_barrier"): + view.security_barrier = self.security_barrier.get_value() + + def _update_oracle_fields(self, view: SQLView): + if hasattr(view, "schema"): + view.schema = self.schema.get_value() + if hasattr(view, "constraint"): + view.constraint = self.constraint.get_value() + if hasattr(view, "force"): + view.force = self.force.get_value() + + +class ViewEditorController: + def __init__(self, parent): + self.parent = parent + self.model = EditViewModel() + + self._bind_controls() + self._bind_buttons() + + wx_call_after_debounce( + self.model.name, + self.model.schema, + self.model.definer, + self.model.sql_security, + self.model.algorithm, + self.model.constraint, + self.model.security_barrier, + self.model.force, + self.model.select_statement, + callback=self.update_button_states + ) + + CURRENT_VIEW.subscribe(self.on_current_view_changed) + + + def _bind_controls(self): + algorithm_radios = [ + self.parent.rad_view_algorithm_undefined, + self.parent.rad_view_algorithm_merge, + self.parent.rad_view_algorithm_temptable, + ] + + constraint_radios = [ + self.parent.rad_view_constraint_none, + self.parent.rad_view_constraint_local, + self.parent.rad_view_constraint_cascaded, + self.parent.rad_view_constraint_check_only, + self.parent.rad_view_constraint_read_only, + ] + + self.model.bind_controls( + name=self.parent.txt_view_name, + schema=self.parent.cho_view_schema, + definer=self.parent.cmb_view_definer, + sql_security=self.parent.cho_view_sql_security, + algorithm=algorithm_radios, + constraint=constraint_radios, + security_barrier=self.parent.chk_view_security_barrier, + force=self.parent.chk_view_force, + select_statement=self.parent.stc_view_select, + ) + + def _bind_buttons(self): + self.parent.btn_save_view.Bind(wx.EVT_BUTTON, self.on_save_view) + self.parent.btn_delete_view.Bind(wx.EVT_BUTTON, self.on_delete_view) + self.parent.btn_cancel_view.Bind(wx.EVT_BUTTON, self.on_cancel_view) + + def _get_original_view(self, view: SQLView) -> Optional[SQLView]: + if view.is_new: + return None + + session = CURRENT_SESSION.get_value() + database = CURRENT_DATABASE.get_value() + if not session or not database: + return None + + return next((v for v in database.views if v.id == view.id), None) + + def _has_changes(self, view: SQLView) -> bool: + if view.is_new: + return True + + original = self._get_original_view(view) + if original is None: + return True + + self.model.update_view(view) + return view != original + + def update_button_states(self): + view = CURRENT_VIEW.get_value() + + if view is None: + self.parent.btn_save_view.Enable(False) + self.parent.btn_cancel_view.Enable(False) + self.parent.btn_delete_view.Enable(False) + else: + has_changes = self._has_changes(view) + self.parent.btn_save_view.Enable(has_changes) + self.parent.btn_cancel_view.Enable(has_changes) + self.parent.btn_delete_view.Enable(not view.is_new) + + def on_save_view(self, event): + self.do_save_view() + + def on_delete_view(self, event): + self.do_delete_view() + + def on_cancel_view(self, event): + self.do_cancel_view() + + def do_save_view(self): + view = CURRENT_VIEW.get_value() + if not view: + return + + session = CURRENT_SESSION.get_value() + if not session: + return + + try: + view.save() + message = _("View created successfully") if view.is_new else _("View updated successfully") + wx.MessageBox(message, _("Success"), wx.OK | wx.ICON_INFORMATION) + self.update_button_states() + except Exception as e: + wx.MessageBox(_("Error saving view: {}").format(str(e)), _("Error"), wx.OK | wx.ICON_ERROR) + + + def do_delete_view(self): + view = CURRENT_VIEW.get_value() + if not view: + return + + session = CURRENT_SESSION.get_value() + if not session: + return + + result = wx.MessageBox( + _("Are you sure you want to delete view '{}'?").format(view.name), + _("Confirm Delete"), + wx.YES_NO | wx.ICON_QUESTION + ) + + if result != wx.YES: + return + + try: + view.drop() + wx.MessageBox(_("View deleted successfully"), _("Success"), wx.OK | wx.ICON_INFORMATION) + CURRENT_VIEW.set_value(None) + except Exception as e: + wx.MessageBox(_("Error deleting view: {}").format(str(e)), _("Error"), wx.OK | wx.ICON_ERROR) + + def do_cancel_view(self): + view = CURRENT_VIEW.get_value() + if not view: + return + + CURRENT_VIEW.set_value(None) + CURRENT_VIEW.set_value(view) + self.update_button_states() + + def on_current_view_changed(self, view: Optional[SQLView]): + self.update_button_states() + + if view is None: + return + + session = CURRENT_SESSION.get_value() + if session: + engine = session.engine + self.apply_engine_visibility(engine) + self._populate_definers(engine, session) + + def _populate_definers(self, engine: ConnectionEngine, session): + if engine not in (ConnectionEngine.MYSQL, ConnectionEngine.MARIADB): + return + + try: + definers = session.context.get_definers() + self.parent.cmb_view_definer.Clear() + for definer in definers: + self.parent.cmb_view_definer.Append(definer) + except Exception: + pass + + def apply_engine_visibility(self, engine: ConnectionEngine): + if engine in (ConnectionEngine.MYSQL, ConnectionEngine.MARIADB): + self._apply_mysql_visibility() + elif engine == ConnectionEngine.POSTGRESQL: + self._apply_postgresql_visibility() + elif engine == ConnectionEngine.ORACLE: + self._apply_oracle_visibility() + elif engine == ConnectionEngine.SQLITE: + self._apply_sqlite_visibility() + + self.parent.pnl_view_editor_root.GetSizer().Layout() + self.parent.m_notebook7.SetMinSize(wx.Size(-1, -1)) + self.parent.m_notebook7.Fit() + self.parent.panel_views.Layout() + + def _apply_mysql_visibility(self): + panels_to_show = [ + self.parent.pnl_row_definer, + self.parent.pnl_row_sql_security, + self.parent.pnl_row_algorithm, + self.parent.pnl_row_constraint, + ] + panels_to_hide = [ + self.parent.pnl_row_schema, + self.parent.pnl_row_security_barrier, + self.parent.pnl_row_force, + ] + self._batch_show_hide(panels_to_show, panels_to_hide) + + self.parent.rad_view_constraint_none.Show(True) + self.parent.rad_view_constraint_local.Show(True) + self.parent.rad_view_constraint_cascaded.Show(True) + self.parent.rad_view_constraint_check_only.Show(False) + self.parent.rad_view_constraint_read_only.Show(False) + + self._normalize_radio_selection_algorithm() + self._normalize_radio_selection_constraint() + + def _apply_postgresql_visibility(self): + panels_to_show = [ + self.parent.pnl_row_schema, + self.parent.pnl_row_constraint, + self.parent.pnl_row_force, + ] + panels_to_hide = [ + self.parent.pnl_row_definer, + self.parent.pnl_row_sql_security, + self.parent.pnl_row_algorithm, + self.parent.pnl_row_security_barrier, + ] + self._batch_show_hide(panels_to_show, panels_to_hide) + + self.parent.rad_view_constraint_none.Show(True) + self.parent.rad_view_constraint_local.Show(True) + self.parent.rad_view_constraint_cascaded.Show(True) + self.parent.rad_view_constraint_check_only.Show(False) + self.parent.rad_view_constraint_read_only.Show(False) + + self._normalize_radio_selection_constraint() + + def _apply_oracle_visibility(self): + panels_to_show = [ + self.parent.pnl_row_schema, + self.parent.pnl_row_constraint, + self.parent.pnl_row_security_barrier, + ] + panels_to_hide = [ + self.parent.pnl_row_definer, + self.parent.pnl_row_sql_security, + self.parent.pnl_row_algorithm, + self.parent.pnl_row_force, + ] + self._batch_show_hide(panels_to_show, panels_to_hide) + + self.parent.rad_view_constraint_none.Show(True) + self.parent.rad_view_constraint_local.Show(False) + self.parent.rad_view_constraint_cascaded.Show(False) + self.parent.rad_view_constraint_check_only.Show(True) + self.parent.rad_view_constraint_read_only.Show(True) + + self._normalize_radio_selection_constraint() + + def _apply_sqlite_visibility(self): + panels_to_hide = [ + self.parent.pnl_row_schema, + self.parent.pnl_row_definer, + self.parent.pnl_row_sql_security, + self.parent.pnl_row_algorithm, + self.parent.pnl_row_constraint, + self.parent.pnl_row_security_barrier, + self.parent.pnl_row_force, + ] + self._batch_show_hide([], panels_to_hide) + + def _batch_show_hide(self, show: list[wx.Window], hide: list[wx.Window]): + for widget in show: + widget.Show(True) + sizer = widget.GetContainingSizer() + if sizer: + sizer.Show(widget, True) + for widget in hide: + widget.Show(False) + sizer = widget.GetContainingSizer() + if sizer: + sizer.Show(widget, False) + + def _normalize_radio_selection_algorithm(self): + radios = [ + self.parent.rad_view_algorithm_undefined, + self.parent.rad_view_algorithm_merge, + self.parent.rad_view_algorithm_temptable, + ] + self._normalize_radio_group(radios) + + def _normalize_radio_selection_constraint(self): + radios = [ + self.parent.rad_view_constraint_none, + self.parent.rad_view_constraint_local, + self.parent.rad_view_constraint_cascaded, + self.parent.rad_view_constraint_check_only, + self.parent.rad_view_constraint_read_only, + ] + self._normalize_radio_group(radios) + + def _normalize_radio_group(self, radios: list[wx.RadioButton]): + visible = [r for r in radios if r.IsShown()] + + if not visible: + return + + selected = next((r for r in visible if r.GetValue()), None) + + if selected is None: + visible[0].SetValue(True) + diff --git a/windows/state.py b/windows/state.py new file mode 100644 index 0000000..19a861e --- /dev/null +++ b/windows/state.py @@ -0,0 +1,25 @@ +from helpers.observables import Observable, ObservableList + +from structures.session import Session +from structures.connection import Connection +from structures.engines.database import SQLColumn, SQLDatabase, SQLForeignKey, SQLIndex, SQLRecord, SQLTable, SQLTrigger, SQLView + +SESSIONS_LIST: ObservableList[Session] = ObservableList() + +CURRENT_SESSION: Observable[Session] = Observable() +CURRENT_CONNECTION: Observable[Connection] = Observable() +CURRENT_DATABASE: Observable[SQLDatabase] = Observable() +CURRENT_TABLE: Observable[SQLTable] = Observable() +CURRENT_VIEW: Observable[SQLView] = Observable() +CURRENT_TRIGGER: Observable[SQLTrigger] = Observable() +CURRENT_FUNCTION: Observable[SQLTrigger] = Observable() +CURRENT_PROCEDURE: Observable[SQLTrigger] = Observable() +CURRENT_EVENT: Observable[SQLTrigger] = Observable() +CURRENT_COLUMN: Observable[SQLColumn] = Observable() +CURRENT_INDEX: Observable[SQLIndex] = Observable() +CURRENT_FOREIGN_KEY: Observable[SQLForeignKey] = Observable() +CURRENT_RECORDS: ObservableList[SQLRecord] = ObservableList() + +NEW_TABLE: Observable[SQLTable] = Observable() + +AUTO_APPLY: Observable[bool] = Observable(True) diff --git a/windows/views.py b/windows/views.py new file mode 100755 index 0000000..3d49225 --- /dev/null +++ b/windows/views.py @@ -0,0 +1,3000 @@ +# -*- coding: utf-8 -*- + +########################################################################### +## Python code generated with wxFormBuilder (version 4.2.1-111-g5faebfea) +## http://www.wxformbuilder.org/ +## +## PLEASE DO *NOT* EDIT THIS FILE! +########################################################################### + +from .components.dataview import TableIndexesDataViewCtrl +from .components.dataview import TableForeignKeysDataViewCtrl +from .components.dataview import TableCheckDataViewCtrl +from .components.dataview import TableColumnsDataViewCtrl +from .components.dataview import TableRecordsDataViewCtrl +from wx.lib.agw.flatnotebook import FlatNotebook +import wx +import wx.xrc +import wx.dataview +import wx.stc +import wx.lib.agw.hypertreelist +import wx.aui + +import gettext +_ = gettext.gettext + +########################################################################### +## Class ConnectionsDialog +########################################################################### + +class ConnectionsDialog ( wx.Dialog ): + + def __init__( self, parent ): + wx.Dialog.__init__ ( self, parent, id = wx.ID_ANY, title = _(u"Connection"), pos = wx.DefaultPosition, size = wx.Size( 800,600 ), style = wx.DEFAULT_DIALOG_STYLE|wx.DIALOG_NO_PARENT|wx.RESIZE_BORDER ) + + self.SetSizeHints( wx.Size( -1,-1 ), wx.DefaultSize ) + + bSizer34 = wx.BoxSizer( wx.VERTICAL ) + + self.m_splitter3 = wx.SplitterWindow( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.SP_LIVE_UPDATE ) + self.m_splitter3.Bind( wx.EVT_IDLE, self.m_splitter3OnIdle ) + self.m_splitter3.SetMinimumPaneSize( 250 ) + + self.m_panel16 = wx.Panel( self.m_splitter3, wx.ID_ANY, wx.DefaultPosition, wx.Size( -1,-1 ), wx.TAB_TRAVERSAL ) + bSizer35 = wx.BoxSizer( wx.VERTICAL ) + + self.connections_tree_ctrl = wx.dataview.DataViewCtrl( self.m_panel16, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.dataview.DV_ROW_LINES ) + self.connection_name = self.connections_tree_ctrl.AppendIconTextColumn( _(u"Name"), 0, wx.dataview.DATAVIEW_CELL_EDITABLE, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) + self.connection_last_connection = self.connections_tree_ctrl.AppendTextColumn( _(u"Last connection"), 1, wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) + bSizer35.Add( self.connections_tree_ctrl, 1, wx.ALL|wx.EXPAND|wx.TOP, 5 ) + + self.search_connection = wx.SearchCtrl( self.m_panel16, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) + self.search_connection.ShowSearchButton( True ) + self.search_connection.ShowCancelButton( True ) + bSizer35.Add( self.search_connection, 0, wx.BOTTOM|wx.EXPAND|wx.LEFT|wx.RIGHT, 5 ) + + + self.m_panel16.SetSizer( bSizer35 ) + self.m_panel16.Layout() + bSizer35.Fit( self.m_panel16 ) + self.connection_tree_menu = wx.Menu() + self.m_menuItem4 = wx.MenuItem( self.connection_tree_menu, wx.ID_ANY, _(u"New directory"), wx.EmptyString, wx.ITEM_NORMAL ) + self.m_menuItem4.SetBitmap( wx.Bitmap( u"icons/16x16/folder.png", wx.BITMAP_TYPE_ANY ) ) + self.connection_tree_menu.Append( self.m_menuItem4 ) + + self.m_menuItem5 = wx.MenuItem( self.connection_tree_menu, wx.ID_ANY, _(u"New connection"), wx.EmptyString, wx.ITEM_NORMAL ) + self.m_menuItem5.SetBitmap( wx.Bitmap( u"icons/16x16/server.png", wx.BITMAP_TYPE_ANY ) ) + self.connection_tree_menu.Append( self.m_menuItem5 ) + + self.connection_tree_menu.AppendSeparator() + + self.m_menuItem18 = wx.MenuItem( self.connection_tree_menu, wx.ID_ANY, _(u"Rename"), wx.EmptyString, wx.ITEM_NORMAL ) + self.m_menuItem18.SetBitmap( wx.Bitmap( u"icons/16x16/edit_marker.png", wx.BITMAP_TYPE_ANY ) ) + self.connection_tree_menu.Append( self.m_menuItem18 ) + self.m_menuItem18.Enable( False ) + + self.m_menuItem19 = wx.MenuItem( self.connection_tree_menu, wx.ID_ANY, _(u"Clone connection"), wx.EmptyString, wx.ITEM_NORMAL ) + self.m_menuItem19.SetBitmap( wx.Bitmap( u"icons/16x16/page_copy_columns.png", wx.BITMAP_TYPE_ANY ) ) + self.connection_tree_menu.Append( self.m_menuItem19 ) + self.m_menuItem19.Enable( False ) + + self.m_menuItem21 = wx.MenuItem( self.connection_tree_menu, wx.ID_ANY, _(u"Delete"), wx.EmptyString, wx.ITEM_NORMAL ) + self.m_menuItem21.SetBitmap( wx.Bitmap( u"icons/16x16/delete.png", wx.BITMAP_TYPE_ANY ) ) + self.connection_tree_menu.Append( self.m_menuItem21 ) + + + self.m_panel17 = wx.Panel( self.m_splitter3, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + bSizer36 = wx.BoxSizer( wx.VERTICAL ) + + self.m_notebook4 = wx.Notebook( self.m_panel17, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.NB_FIXEDWIDTH ) + self.panel_connection = wx.Panel( self.m_notebook4, wx.ID_ANY, wx.DefaultPosition, wx.Size( 600,-1 ), wx.BORDER_NONE|wx.TAB_TRAVERSAL ) + self.panel_connection.SetMinSize( wx.Size( 600,-1 ) ) + + bSizer12 = wx.BoxSizer( wx.VERTICAL ) + + bSizer1211 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText211 = wx.StaticText( self.panel_connection, wx.ID_ANY, _(u"Name"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) + self.m_staticText211.Wrap( -1 ) + + bSizer1211.Add( self.m_staticText211, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + self.name = wx.TextCtrl( self.panel_connection, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer1211.Add( self.name, 1, wx.ALIGN_CENTER|wx.ALL, 5 ) + + + bSizer12.Add( bSizer1211, 0, wx.EXPAND, 5 ) + + bSizer13 = wx.BoxSizer( wx.HORIZONTAL ) + + bSizer13.SetMinSize( wx.Size( -1,0 ) ) + self.m_staticText2 = wx.StaticText( self.panel_connection, wx.ID_ANY, _(u"Engine"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) + self.m_staticText2.Wrap( -1 ) + + bSizer13.Add( self.m_staticText2, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + engineChoices = [] + self.engine = wx.Choice( self.panel_connection, wx.ID_ANY, wx.DefaultPosition, wx.Size( 400,-1 ), engineChoices, 0 ) + self.engine.SetSelection( 0 ) + bSizer13.Add( self.engine, 1, wx.ALIGN_CENTER|wx.ALL, 5 ) + + + bSizer12.Add( bSizer13, 0, wx.EXPAND, 5 ) + + self.m_staticline41 = wx.StaticLine( self.panel_connection, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL ) + bSizer12.Add( self.m_staticline41, 0, wx.EXPAND | wx.ALL, 5 ) + + self.panel_credentials = wx.Panel( self.panel_connection, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + bSizer103 = wx.BoxSizer( wx.VERTICAL ) + + bSizer121 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText21 = wx.StaticText( self.panel_credentials, wx.ID_ANY, _(u"Host + port"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) + self.m_staticText21.Wrap( -1 ) + + bSizer121.Add( self.m_staticText21, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + self.hostname = wx.TextCtrl( self.panel_credentials, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer121.Add( self.hostname, 1, wx.ALIGN_CENTER|wx.ALL, 5 ) + + self.port = wx.SpinCtrl( self.panel_credentials, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.SP_ARROW_KEYS, 0, 65536, 3306 ) + bSizer121.Add( self.port, 0, wx.ALL, 5 ) + + + bSizer103.Add( bSizer121, 0, wx.EXPAND, 5 ) + + bSizer122 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText22 = wx.StaticText( self.panel_credentials, wx.ID_ANY, _(u"Username"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) + self.m_staticText22.Wrap( -1 ) + + bSizer122.Add( self.m_staticText22, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + self.username = wx.TextCtrl( self.panel_credentials, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer122.Add( self.username, 1, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + + bSizer103.Add( bSizer122, 0, wx.EXPAND, 5 ) + + bSizer1221 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText221 = wx.StaticText( self.panel_credentials, wx.ID_ANY, _(u"Password"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) + self.m_staticText221.Wrap( -1 ) + + bSizer1221.Add( self.m_staticText221, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + self.password = wx.TextCtrl( self.panel_credentials, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_PASSWORD ) + bSizer1221.Add( self.password, 1, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + + bSizer103.Add( bSizer1221, 0, wx.EXPAND, 5 ) + + bSizer116 = wx.BoxSizer( wx.HORIZONTAL ) + + + bSizer116.Add( ( 156, 0), 0, wx.EXPAND, 5 ) + + self.use_tls_enabled = wx.CheckBox( self.panel_credentials, wx.ID_ANY, _(u"Use TLS"), wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer116.Add( self.use_tls_enabled, 0, wx.ALL, 5 ) + + self.ssh_tunnel_enabled = wx.CheckBox( self.panel_credentials, wx.ID_ANY, _(u"Use SSH tunnel"), wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer116.Add( self.ssh_tunnel_enabled, 0, wx.ALL, 5 ) + + + bSizer103.Add( bSizer116, 0, wx.EXPAND, 5 ) + + self.m_staticline5 = wx.StaticLine( self.panel_credentials, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL ) + bSizer103.Add( self.m_staticline5, 0, wx.EXPAND | wx.ALL, 5 ) + + + self.panel_credentials.SetSizer( bSizer103 ) + self.panel_credentials.Layout() + bSizer103.Fit( self.panel_credentials ) + bSizer12.Add( self.panel_credentials, 0, wx.EXPAND | wx.ALL, 0 ) + + self.panel_source = wx.Panel( self.panel_connection, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + self.panel_source.Hide() + + bSizer105 = wx.BoxSizer( wx.VERTICAL ) + + bSizer106 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText50 = wx.StaticText( self.panel_source, wx.ID_ANY, _(u"Filename"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) + self.m_staticText50.Wrap( -1 ) + + bSizer106.Add( self.m_staticText50, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + self.filename = wx.FilePickerCtrl( self.panel_source, wx.ID_ANY, wx.EmptyString, _(u"Select a file"), _(u"*.*"), wx.DefaultPosition, wx.DefaultSize, wx.FLP_CHANGE_DIR|wx.FLP_DEFAULT_STYLE|wx.FLP_FILE_MUST_EXIST ) + bSizer106.Add( self.filename, 1, wx.ALIGN_CENTER|wx.ALL, 5 ) + + + bSizer105.Add( bSizer106, 1, wx.EXPAND, 5 ) + + + self.panel_source.SetSizer( bSizer105 ) + self.panel_source.Layout() + bSizer105.Fit( self.panel_source ) + bSizer12.Add( self.panel_source, 0, wx.EXPAND | wx.ALL, 0 ) + + bSizer122111 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText22111 = wx.StaticText( self.panel_connection, wx.ID_ANY, _(u"Comments"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) + self.m_staticText22111.Wrap( -1 ) + + bSizer122111.Add( self.m_staticText22111, 0, wx.ALL, 5 ) + + self.comments = wx.TextCtrl( self.panel_connection, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.Size( -1,200 ), wx.TE_MULTILINE ) + bSizer122111.Add( self.comments, 1, wx.ALL|wx.EXPAND, 5 ) + + + bSizer12.Add( bSizer122111, 0, wx.EXPAND, 5 ) + + + self.panel_connection.SetSizer( bSizer12 ) + self.panel_connection.Layout() + self.m_notebook4.AddPage( self.panel_connection, _(u"Settings"), True ) + self.panel_ssh_tunnel = wx.Panel( self.m_notebook4, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + self.panel_ssh_tunnel.Enable( False ) + self.panel_ssh_tunnel.Hide() + + bSizer102 = wx.BoxSizer( wx.VERTICAL ) + + bSizer1213 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText213 = wx.StaticText( self.panel_ssh_tunnel, wx.ID_ANY, _(u"SSH executable"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) + self.m_staticText213.Wrap( -1 ) + + bSizer1213.Add( self.m_staticText213, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + self.ssh_tunnel_executable = wx.TextCtrl( self.panel_ssh_tunnel, wx.ID_ANY, _(u"ssh"), wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer1213.Add( self.ssh_tunnel_executable, 1, wx.ALIGN_CENTER|wx.ALL, 5 ) + + + bSizer102.Add( bSizer1213, 0, wx.EXPAND, 5 ) + + bSizer12131 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText2131 = wx.StaticText( self.panel_ssh_tunnel, wx.ID_ANY, _(u"SSH host + port"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) + self.m_staticText2131.Wrap( -1 ) + + bSizer12131.Add( self.m_staticText2131, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + self.ssh_tunnel_hostname = wx.TextCtrl( self.panel_ssh_tunnel, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer12131.Add( self.ssh_tunnel_hostname, 1, wx.ALIGN_CENTER|wx.ALL, 5 ) + + self.ssh_tunnel_port = wx.SpinCtrl( self.panel_ssh_tunnel, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.SP_ARROW_KEYS, 0, 65536, 22 ) + bSizer12131.Add( self.ssh_tunnel_port, 0, wx.ALL, 5 ) + + self.m_bitmap11 = wx.StaticBitmap( self.panel_ssh_tunnel, wx.ID_ANY, wx.Bitmap( u"icons/16x16/information.png", wx.BITMAP_TYPE_ANY ), wx.DefaultPosition, wx.DefaultSize, 0 ) + self.m_bitmap11.SetToolTip( _(u"SSH host + port (the SSH server that forwards traffic to the DB)") ) + + bSizer12131.Add( self.m_bitmap11, 0, wx.ALL|wx.EXPAND, 5 ) + + + bSizer102.Add( bSizer12131, 0, wx.EXPAND, 5 ) + + bSizer12132 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText2132 = wx.StaticText( self.panel_ssh_tunnel, wx.ID_ANY, _(u"SSH username"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) + self.m_staticText2132.Wrap( -1 ) + + bSizer12132.Add( self.m_staticText2132, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + self.ssh_tunnel_username = wx.TextCtrl( self.panel_ssh_tunnel, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer12132.Add( self.ssh_tunnel_username, 1, wx.ALIGN_CENTER|wx.ALL, 5 ) + + + bSizer102.Add( bSizer12132, 0, wx.EXPAND, 5 ) + + bSizer121321 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText21321 = wx.StaticText( self.panel_ssh_tunnel, wx.ID_ANY, _(u"SSH password"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) + self.m_staticText21321.Wrap( -1 ) + + bSizer121321.Add( self.m_staticText21321, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + self.ssh_tunnel_password = wx.TextCtrl( self.panel_ssh_tunnel, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_PASSWORD ) + bSizer121321.Add( self.ssh_tunnel_password, 1, wx.ALIGN_CENTER|wx.ALL, 5 ) + + + bSizer102.Add( bSizer121321, 0, wx.EXPAND, 5 ) + + bSizer1213211 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText213211 = wx.StaticText( self.panel_ssh_tunnel, wx.ID_ANY, _(u"Local port"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) + self.m_staticText213211.Wrap( -1 ) + + bSizer1213211.Add( self.m_staticText213211, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + self.ssh_tunnel_local_port = wx.SpinCtrl( self.panel_ssh_tunnel, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.SP_ARROW_KEYS, 0, 65536, 0 ) + self.ssh_tunnel_local_port.SetToolTip( _(u"if the value is set to 0, the first available port will be used") ) + + bSizer1213211.Add( self.ssh_tunnel_local_port, 1, wx.ALL, 5 ) + + + bSizer102.Add( bSizer1213211, 0, wx.EXPAND, 5 ) + + bSizer1213212 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText213212 = wx.StaticText( self.panel_ssh_tunnel, wx.ID_ANY, _(u"Identity file"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) + self.m_staticText213212.Wrap( -1 ) + + bSizer1213212.Add( self.m_staticText213212, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + self.identity_file = wx.FilePickerCtrl( self.panel_ssh_tunnel, wx.ID_ANY, wx.EmptyString, _(u"Select a file"), _(u"*.*"), wx.DefaultPosition, wx.DefaultSize, wx.FLP_CHANGE_DIR|wx.FLP_DEFAULT_STYLE|wx.FLP_FILE_MUST_EXIST ) + bSizer1213212.Add( self.identity_file, 1, wx.ALIGN_CENTER|wx.ALL, 5 ) + + + bSizer102.Add( bSizer1213212, 0, wx.EXPAND, 5 ) + + self.m_staticline6 = wx.StaticLine( self.panel_ssh_tunnel, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL ) + bSizer102.Add( self.m_staticline6, 0, wx.EXPAND | wx.ALL, 5 ) + + bSizer121311 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText21311 = wx.StaticText( self.panel_ssh_tunnel, wx.ID_ANY, _(u"Remote host + port"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) + self.m_staticText21311.Wrap( -1 ) + + bSizer121311.Add( self.m_staticText21311, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + self.remote_hostname = wx.TextCtrl( self.panel_ssh_tunnel, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer121311.Add( self.remote_hostname, 1, wx.ALIGN_CENTER|wx.ALL, 5 ) + + self.remote_port = wx.SpinCtrl( self.panel_ssh_tunnel, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.SP_ARROW_KEYS, 0, 65536, 3306 ) + bSizer121311.Add( self.remote_port, 0, wx.ALL, 5 ) + + self.m_bitmap1 = wx.StaticBitmap( self.panel_ssh_tunnel, wx.ID_ANY, wx.Bitmap( u"icons/16x16/information.png", wx.BITMAP_TYPE_ANY ), wx.DefaultPosition, wx.DefaultSize, 0 ) + self.m_bitmap1.SetToolTip( _(u"Remote host/port is the real DB target (defaults to DB Host/Port).") ) + + bSizer121311.Add( self.m_bitmap1, 0, wx.ALL|wx.EXPAND, 5 ) + + + bSizer102.Add( bSizer121311, 0, wx.EXPAND, 5 ) + + + self.panel_ssh_tunnel.SetSizer( bSizer102 ) + self.panel_ssh_tunnel.Layout() + bSizer102.Fit( self.panel_ssh_tunnel ) + self.m_notebook4.AddPage( self.panel_ssh_tunnel, _(u"SSH Tunnel"), False ) + self.panel_statistics = wx.Panel( self.m_notebook4, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + bSizer361 = wx.BoxSizer( wx.VERTICAL ) + + bSizer37 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText15 = wx.StaticText( self.panel_statistics, wx.ID_ANY, _(u"Created at"), wx.DefaultPosition, wx.DefaultSize, 0 ) + self.m_staticText15.Wrap( -1 ) + + self.m_staticText15.SetMinSize( wx.Size( 200,-1 ) ) + + bSizer37.Add( self.m_staticText15, 0, wx.ALL, 5 ) + + self.created_at = wx.StaticText( self.panel_statistics, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) + self.created_at.Wrap( -1 ) + + bSizer37.Add( self.created_at, 0, wx.ALL, 5 ) + + + bSizer361.Add( bSizer37, 0, wx.EXPAND, 5 ) + + bSizer371 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText151 = wx.StaticText( self.panel_statistics, wx.ID_ANY, _(u"Last connection"), wx.DefaultPosition, wx.DefaultSize, 0 ) + self.m_staticText151.Wrap( -1 ) + + self.m_staticText151.SetMinSize( wx.Size( 200,-1 ) ) + + bSizer371.Add( self.m_staticText151, 0, wx.ALL, 5 ) + + self.last_connection_at = wx.StaticText( self.panel_statistics, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) + self.last_connection_at.Wrap( -1 ) + + bSizer371.Add( self.last_connection_at, 0, wx.ALL, 5 ) + + + bSizer361.Add( bSizer371, 0, wx.EXPAND, 5 ) + + bSizer3711 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText1511 = wx.StaticText( self.panel_statistics, wx.ID_ANY, _(u"Successful connections"), wx.DefaultPosition, wx.DefaultSize, 0 ) + self.m_staticText1511.Wrap( -1 ) + + self.m_staticText1511.SetMinSize( wx.Size( 200,-1 ) ) + + bSizer3711.Add( self.m_staticText1511, 0, wx.ALL, 5 ) + + self.successful_connected = wx.StaticText( self.panel_statistics, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) + self.successful_connected.Wrap( -1 ) + + bSizer3711.Add( self.successful_connected, 0, wx.ALL, 5 ) + + + bSizer361.Add( bSizer3711, 0, wx.EXPAND, 5 ) + + bSizer371111 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText151111 = wx.StaticText( self.panel_statistics, wx.ID_ANY, _(u"Last successful connection"), wx.DefaultPosition, wx.DefaultSize, 0 ) + self.m_staticText151111.Wrap( -1 ) + + self.m_staticText151111.SetMinSize( wx.Size( 200,-1 ) ) + + bSizer371111.Add( self.m_staticText151111, 0, wx.ALL, 5 ) + + self.last_successful_connection = wx.StaticText( self.panel_statistics, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) + self.last_successful_connection.Wrap( -1 ) + + bSizer371111.Add( self.last_successful_connection, 1, wx.ALL, 5 ) + + + bSizer361.Add( bSizer371111, 0, wx.EXPAND, 5 ) + + bSizer37111 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText15111 = wx.StaticText( self.panel_statistics, wx.ID_ANY, _(u"Unsuccessful connections"), wx.DefaultPosition, wx.DefaultSize, 0 ) + self.m_staticText15111.Wrap( -1 ) + + self.m_staticText15111.SetMinSize( wx.Size( 200,-1 ) ) + + bSizer37111.Add( self.m_staticText15111, 0, wx.ALL, 5 ) + + self.unsuccessful_connections = wx.StaticText( self.panel_statistics, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) + self.unsuccessful_connections.Wrap( -1 ) + + bSizer37111.Add( self.unsuccessful_connections, 0, wx.ALL, 5 ) + + + bSizer361.Add( bSizer37111, 0, wx.EXPAND, 5 ) + + bSizer371112 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText151112 = wx.StaticText( self.panel_statistics, wx.ID_ANY, _(u"Last failure reason"), wx.DefaultPosition, wx.DefaultSize, 0 ) + self.m_staticText151112.Wrap( -1 ) + + self.m_staticText151112.SetMinSize( wx.Size( 200,-1 ) ) + + bSizer371112.Add( self.m_staticText151112, 0, wx.ALL, 5 ) + + self.last_failure_raison = wx.StaticText( self.panel_statistics, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) + self.last_failure_raison.Wrap( -1 ) + + bSizer371112.Add( self.last_failure_raison, 1, wx.ALL, 5 ) + + + bSizer361.Add( bSizer371112, 0, wx.EXPAND, 5 ) + + bSizer3711121 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText1511121 = wx.StaticText( self.panel_statistics, wx.ID_ANY, _(u"Total connection attempts"), wx.DefaultPosition, wx.DefaultSize, 0 ) + self.m_staticText1511121.Wrap( -1 ) + + self.m_staticText1511121.SetMinSize( wx.Size( 200,-1 ) ) + + bSizer3711121.Add( self.m_staticText1511121, 0, wx.ALL, 5 ) + + self.total_connection_attempts = wx.StaticText( self.panel_statistics, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) + self.total_connection_attempts.Wrap( -1 ) + + bSizer3711121.Add( self.total_connection_attempts, 1, wx.ALL, 5 ) + + + bSizer361.Add( bSizer3711121, 0, wx.EXPAND, 5 ) + + bSizer37111211 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText15111211 = wx.StaticText( self.panel_statistics, wx.ID_ANY, _(u" Average connection time (ms)"), wx.DefaultPosition, wx.DefaultSize, 0 ) + self.m_staticText15111211.Wrap( -1 ) + + self.m_staticText15111211.SetMinSize( wx.Size( 200,-1 ) ) + + bSizer37111211.Add( self.m_staticText15111211, 0, wx.ALL, 5 ) + + self.average_connection_time = wx.StaticText( self.panel_statistics, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) + self.average_connection_time.Wrap( -1 ) + + bSizer37111211.Add( self.average_connection_time, 1, wx.ALL, 5 ) + + + bSizer361.Add( bSizer37111211, 0, wx.EXPAND, 5 ) + + bSizer371112111 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText151112111 = wx.StaticText( self.panel_statistics, wx.ID_ANY, _(u" Most recent connection duration"), wx.DefaultPosition, wx.DefaultSize, 0 ) + self.m_staticText151112111.Wrap( -1 ) + + self.m_staticText151112111.SetMinSize( wx.Size( 200,-1 ) ) + + bSizer371112111.Add( self.m_staticText151112111, 0, wx.ALL, 5 ) + + self.most_recent_connection_duration = wx.StaticText( self.panel_statistics, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) + self.most_recent_connection_duration.Wrap( -1 ) + + bSizer371112111.Add( self.most_recent_connection_duration, 1, wx.ALL, 5 ) + + + bSizer361.Add( bSizer371112111, 0, wx.EXPAND, 5 ) + + + self.panel_statistics.SetSizer( bSizer361 ) + self.panel_statistics.Layout() + bSizer361.Fit( self.panel_statistics ) + self.m_notebook4.AddPage( self.panel_statistics, _(u"Statistics"), False ) + + bSizer36.Add( self.m_notebook4, 1, wx.ALL|wx.EXPAND, 5 ) + + + self.m_panel17.SetSizer( bSizer36 ) + self.m_panel17.Layout() + bSizer36.Fit( self.m_panel17 ) + self.m_splitter3.SplitVertically( self.m_panel16, self.m_panel17, 250 ) + bSizer34.Add( self.m_splitter3, 1, wx.EXPAND, 5 ) + + self.m_staticline4 = wx.StaticLine( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL ) + bSizer34.Add( self.m_staticline4, 0, wx.EXPAND | wx.ALL, 5 ) + + bSizer28 = wx.BoxSizer( wx.HORIZONTAL ) + + bSizer301 = wx.BoxSizer( wx.HORIZONTAL ) + + self.btn_create = wx.Button( self, wx.ID_ANY, _(u"Create"), wx.DefaultPosition, wx.DefaultSize, 0 ) + + self.btn_create.SetBitmap( wx.Bitmap( u"icons/16x16/add.png", wx.BITMAP_TYPE_ANY ) ) + self.m_menu12 = wx.Menu() + self.m_menuItem16 = wx.MenuItem( self.m_menu12, wx.ID_ANY, _(u"Create connection"), wx.EmptyString, wx.ITEM_NORMAL ) + self.m_menu12.Append( self.m_menuItem16 ) + + self.m_menuItem17 = wx.MenuItem( self.m_menu12, wx.ID_ANY, _(u"Create directory"), wx.EmptyString, wx.ITEM_NORMAL ) + self.m_menu12.Append( self.m_menuItem17 ) + + self.btn_create.Bind( wx.EVT_RIGHT_DOWN, self.btn_createOnContextMenu ) + + bSizer301.Add( self.btn_create, 0, wx.ALL|wx.BOTTOM, 5 ) + + self.btn_create_directory = wx.Button( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.BU_EXACTFIT|wx.BU_NOTEXT ) + + self.btn_create_directory.SetBitmap( wx.Bitmap( u"icons/16x16/folder.png", wx.BITMAP_TYPE_ANY ) ) + bSizer301.Add( self.btn_create_directory, 0, wx.ALL, 5 ) + + self.btn_delete = wx.Button( self, wx.ID_ANY, _(u"Delete"), wx.DefaultPosition, wx.DefaultSize, 0 ) + + self.btn_delete.SetBitmap( wx.Bitmap( u"icons/16x16/delete.png", wx.BITMAP_TYPE_ANY ) ) + self.btn_delete.Enable( False ) + + bSizer301.Add( self.btn_delete, 0, wx.ALL, 5 ) + + + bSizer28.Add( bSizer301, 1, wx.EXPAND, 5 ) + + bSizer110 = wx.BoxSizer( wx.HORIZONTAL ) + + + bSizer28.Add( bSizer110, 1, wx.EXPAND, 5 ) + + bSizer29 = wx.BoxSizer( wx.HORIZONTAL ) + + self.btn_cancel = wx.Button( self, wx.ID_ANY, _(u"Cancel"), wx.DefaultPosition, wx.DefaultSize, 0 ) + self.btn_cancel.Hide() + + bSizer29.Add( self.btn_cancel, 0, wx.ALL, 5 ) + + self.btn_save = wx.Button( self, wx.ID_ANY, _(u"Save"), wx.DefaultPosition, wx.DefaultSize, 0 ) + + self.btn_save.SetBitmap( wx.Bitmap( u"icons/16x16/disk.png", wx.BITMAP_TYPE_ANY ) ) + self.btn_save.Enable( False ) + + bSizer29.Add( self.btn_save, 0, wx.ALL, 5 ) + + self.btn_test = wx.Button( self, wx.ID_ANY, _(u"Test"), wx.DefaultPosition, wx.DefaultSize, 0 ) + + self.btn_test.SetBitmap( wx.Bitmap( u"icons/16x16/world_go.png", wx.BITMAP_TYPE_ANY ) ) + self.btn_test.Enable( False ) + + bSizer29.Add( self.btn_test, 0, wx.ALL, 5 ) + + self.btn_open = wx.Button( self, wx.ID_ANY, _(u"Connect"), wx.DefaultPosition, wx.DefaultSize, 0 ) + + self.btn_open.SetBitmap( wx.Bitmap( u"icons/16x16/server_go.png", wx.BITMAP_TYPE_ANY ) ) + self.btn_open.Enable( False ) + + bSizer29.Add( self.btn_open, 0, wx.ALL, 5 ) + + + bSizer28.Add( bSizer29, 0, wx.EXPAND, 5 ) + + + bSizer34.Add( bSizer28, 0, wx.EXPAND, 0 ) + + + self.SetSizer( bSizer34 ) + self.Layout() + + self.Centre( wx.BOTH ) + + # Connect Events + self.Bind( wx.EVT_CLOSE, self.on_close ) + self.Bind( wx.EVT_MENU, self.on_new_directory, id = self.m_menuItem4.GetId() ) + self.Bind( wx.EVT_MENU, self.on_new_connection, id = self.m_menuItem5.GetId() ) + self.Bind( wx.EVT_MENU, self.on_rename, id = self.m_menuItem18.GetId() ) + self.Bind( wx.EVT_MENU, self.on_clone_connection, id = self.m_menuItem19.GetId() ) + self.Bind( wx.EVT_MENU, self.on_delete, id = self.m_menuItem21.GetId() ) + self.engine.Bind( wx.EVT_CHOICE, self.on_choice_engine ) + self.btn_create.Bind( wx.EVT_BUTTON, self.on_create ) + self.btn_create_directory.Bind( wx.EVT_BUTTON, self.on_create_directory ) + self.btn_delete.Bind( wx.EVT_BUTTON, self.on_delete ) + self.btn_save.Bind( wx.EVT_BUTTON, self.on_save ) + self.btn_test.Bind( wx.EVT_BUTTON, self.on_test_session ) + self.btn_open.Bind( wx.EVT_BUTTON, self.on_connect ) + + def __del__( self ): + pass + + + # Virtual event handlers, override them in your derived class + def on_close( self, event ): + event.Skip() + + def on_new_directory( self, event ): + event.Skip() + + def on_new_connection( self, event ): + event.Skip() + + def on_rename( self, event ): + event.Skip() + + def on_clone_connection( self, event ): + event.Skip() + + def on_delete( self, event ): + event.Skip() + + def on_choice_engine( self, event ): + event.Skip() + + def on_create( self, event ): + event.Skip() + + def on_create_directory( self, event ): + event.Skip() + + + def on_save( self, event ): + event.Skip() + + def on_test_session( self, event ): + event.Skip() + + def on_connect( self, event ): + event.Skip() + + def m_splitter3OnIdle( self, event ): + self.m_splitter3.SetSashPosition( 250 ) + self.m_splitter3.Unbind( wx.EVT_IDLE ) + + def btn_createOnContextMenu( self, event ): + self.btn_create.PopupMenu( self.m_menu12, event.GetPosition() ) + + +########################################################################### +## Class SettingsDialog +########################################################################### + +class SettingsDialog ( wx.Dialog ): + + def __init__( self, parent ): + wx.Dialog.__init__ ( self, parent, id = wx.ID_ANY, title = _(u"Settings"), pos = wx.DefaultPosition, size = wx.Size( 800,600 ), style = wx.DEFAULT_DIALOG_STYLE ) + + self.SetSizeHints( wx.Size( 800,600 ), wx.DefaultSize ) + + bSizer63 = wx.BoxSizer( wx.VERTICAL ) + + self.m_notebook4 = wx.Notebook( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0 ) + self.locales = wx.Panel( self.m_notebook4, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + bSizer65 = wx.BoxSizer( wx.VERTICAL ) + + bSizer64 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText27 = wx.StaticText( self.locales, wx.ID_ANY, _(u"Language"), wx.DefaultPosition, wx.DefaultSize, 0 ) + self.m_staticText27.Wrap( -1 ) + + bSizer64.Add( self.m_staticText27, 0, wx.ALL, 5 ) + + m_choice5Choices = [ _(u"English"), _(u"Italian"), _(u"French") ] + self.m_choice5 = wx.Choice( self.locales, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, m_choice5Choices, 0|wx.BORDER_NONE ) + self.m_choice5.SetSelection( 0 ) + bSizer64.Add( self.m_choice5, 1, wx.ALL, 5 ) + + + bSizer65.Add( bSizer64, 1, wx.EXPAND, 5 ) + + + self.locales.SetSizer( bSizer65 ) + self.locales.Layout() + bSizer65.Fit( self.locales ) + self.m_notebook4.AddPage( self.locales, _(u"Locale"), False ) + + bSizer63.Add( self.m_notebook4, 1, wx.EXPAND | wx.ALL, 5 ) + + + self.SetSizer( bSizer63 ) + self.Layout() + + self.Centre( wx.BOTH ) + + def __del__( self ): + pass + + +########################################################################### +## Class AdvancedCellEditorDialog +########################################################################### + +class AdvancedCellEditorDialog ( wx.Dialog ): + + def __init__( self, parent ): + wx.Dialog.__init__ ( self, parent, id = wx.ID_ANY, title = _(u"Edit Value"), pos = wx.DefaultPosition, size = wx.Size( 900,550 ), style = wx.DEFAULT_DIALOG_STYLE ) + + self.SetSizeHints( wx.Size( 640,480 ), wx.DefaultSize ) + + bSizer111 = wx.BoxSizer( wx.VERTICAL ) + + bSizer112 = wx.BoxSizer( wx.VERTICAL ) + + bSizer113 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText51 = wx.StaticText( self, wx.ID_ANY, _(u"Syntax"), wx.DefaultPosition, wx.Size( -1,-1 ), 0 ) + self.m_staticText51.Wrap( -1 ) + + bSizer113.Add( self.m_staticText51, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + syntax_choiceChoices = [] + self.syntax_choice = wx.Choice( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, syntax_choiceChoices, 0 ) + self.syntax_choice.SetSelection( 0 ) + bSizer113.Add( self.syntax_choice, 0, wx.ALL, 5 ) + + + bSizer112.Add( bSizer113, 1, wx.EXPAND, 5 ) + + + bSizer111.Add( bSizer112, 0, wx.EXPAND, 5 ) + + self.advanced_stc_editor = wx.stc.StyledTextCtrl( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0) + self.advanced_stc_editor.SetUseTabs ( False ) + self.advanced_stc_editor.SetTabWidth ( 4 ) + self.advanced_stc_editor.SetIndent ( 4 ) + self.advanced_stc_editor.SetTabIndents( True ) + self.advanced_stc_editor.SetBackSpaceUnIndents( True ) + self.advanced_stc_editor.SetViewEOL( False ) + self.advanced_stc_editor.SetViewWhiteSpace( False ) + self.advanced_stc_editor.SetMarginWidth( 2, 0 ) + self.advanced_stc_editor.SetIndentationGuides( True ) + self.advanced_stc_editor.SetReadOnly( False ) + self.advanced_stc_editor.SetMarginWidth( 1, 0 ) + self.advanced_stc_editor.SetMarginType( 0, wx.stc.STC_MARGIN_NUMBER ) + self.advanced_stc_editor.SetMarginWidth( 0, self.advanced_stc_editor.TextWidth( wx.stc.STC_STYLE_LINENUMBER, "_99999" ) ) + self.advanced_stc_editor.MarkerDefine( wx.stc.STC_MARKNUM_FOLDER, wx.stc.STC_MARK_BOXPLUS ) + self.advanced_stc_editor.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDER, wx.BLACK) + self.advanced_stc_editor.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDER, wx.WHITE) + self.advanced_stc_editor.MarkerDefine( wx.stc.STC_MARKNUM_FOLDEROPEN, wx.stc.STC_MARK_BOXMINUS ) + self.advanced_stc_editor.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDEROPEN, wx.BLACK ) + self.advanced_stc_editor.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDEROPEN, wx.WHITE ) + self.advanced_stc_editor.MarkerDefine( wx.stc.STC_MARKNUM_FOLDERSUB, wx.stc.STC_MARK_EMPTY ) + self.advanced_stc_editor.MarkerDefine( wx.stc.STC_MARKNUM_FOLDEREND, wx.stc.STC_MARK_BOXPLUS ) + self.advanced_stc_editor.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDEREND, wx.BLACK ) + self.advanced_stc_editor.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDEREND, wx.WHITE ) + self.advanced_stc_editor.MarkerDefine( wx.stc.STC_MARKNUM_FOLDEROPENMID, wx.stc.STC_MARK_BOXMINUS ) + self.advanced_stc_editor.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDEROPENMID, wx.BLACK) + self.advanced_stc_editor.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDEROPENMID, wx.WHITE) + self.advanced_stc_editor.MarkerDefine( wx.stc.STC_MARKNUM_FOLDERMIDTAIL, wx.stc.STC_MARK_EMPTY ) + self.advanced_stc_editor.MarkerDefine( wx.stc.STC_MARKNUM_FOLDERTAIL, wx.stc.STC_MARK_EMPTY ) + self.advanced_stc_editor.SetSelBackground( True, wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT ) ) + self.advanced_stc_editor.SetSelForeground( True, wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHTTEXT ) ) + bSizer111.Add( self.advanced_stc_editor, 1, wx.EXPAND | wx.ALL, 5 ) + + bSizer114 = wx.BoxSizer( wx.HORIZONTAL ) + + + bSizer114.Add( ( 0, 0), 1, wx.EXPAND, 5 ) + + self.m_button49 = wx.Button( self, wx.ID_ANY, _(u"Cancel"), wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer114.Add( self.m_button49, 0, wx.ALL, 5 ) + + self.m_button48 = wx.Button( self, wx.ID_ANY, _(u"Ok"), wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer114.Add( self.m_button48, 0, wx.ALL, 5 ) + + + bSizer111.Add( bSizer114, 0, wx.EXPAND, 5 ) + + + self.SetSizer( bSizer111 ) + self.Layout() + + self.Centre( wx.BOTH ) + + # Connect Events + self.syntax_choice.Bind( wx.EVT_CHOICE, self.on_syntax_changed ) + + def __del__( self ): + pass + + + # Virtual event handlers, override them in your derived class + def on_syntax_changed( self, event ): + event.Skip() + + +########################################################################### +## Class MainFrameView +########################################################################### + +class MainFrameView ( wx.Frame ): + + def __init__( self, parent ): + wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = _(u"PeterSQL"), pos = wx.DefaultPosition, size = wx.Size( 1280,1024 ), style = wx.DEFAULT_FRAME_STYLE|wx.MAXIMIZE_BOX|wx.TAB_TRAVERSAL ) + + self.SetSizeHints( wx.Size( 800,600 ), wx.DefaultSize ) + + self.m_menubar2 = wx.MenuBar( 0 ) + self.m_menu2 = wx.Menu() + self.m_menubar2.Append( self.m_menu2, _(u"File") ) + + self.m_menu4 = wx.Menu() + self.m_menuItem15 = wx.MenuItem( self.m_menu4, wx.ID_ANY, _(u"About"), wx.EmptyString, wx.ITEM_NORMAL ) + self.m_menu4.Append( self.m_menuItem15 ) + + self.m_menubar2.Append( self.m_menu4, _(u"Help") ) + + self.SetMenuBar( self.m_menubar2 ) + + self.m_toolBar1 = self.CreateToolBar( wx.TB_HORIZONTAL, wx.ID_ANY ) + self.m_tool5 = self.m_toolBar1.AddTool( wx.ID_ANY, _(u"Open connection manager"), wx.Bitmap( u"icons/16x16/server_connect.png", wx.BITMAP_TYPE_ANY ), wx.NullBitmap, wx.ITEM_NORMAL, wx.EmptyString, wx.EmptyString, None ) + + self.m_tool4 = self.m_toolBar1.AddTool( wx.ID_ANY, _(u"Disconnect from server"), wx.Bitmap( u"icons/16x16/disconnect.png", wx.BITMAP_TYPE_ANY ), wx.NullBitmap, wx.ITEM_NORMAL, wx.EmptyString, wx.EmptyString, None ) + + self.m_toolBar1.AddSeparator() + + self.database_refresh = self.m_toolBar1.AddTool( wx.ID_ANY, _(u"tool"), wx.Bitmap( u"icons/16x16/database_refresh.png", wx.BITMAP_TYPE_ANY ), wx.NullBitmap, wx.ITEM_NORMAL, _(u"Refresh"), _(u"Refresh"), None ) + + self.m_toolBar1.AddSeparator() + + self.database_add = self.m_toolBar1.AddTool( wx.ID_ANY, _(u"Add"), wx.Bitmap( u"icons/16x16/database_add.png", wx.BITMAP_TYPE_ANY ), wx.NullBitmap, wx.ITEM_NORMAL, wx.EmptyString, wx.EmptyString, None ) + + self.database_delete = self.m_toolBar1.AddTool( wx.ID_ANY, _(u"Add"), wx.Bitmap( u"icons/16x16/database_delete.png", wx.BITMAP_TYPE_ANY ), wx.NullBitmap, wx.ITEM_NORMAL, wx.EmptyString, wx.EmptyString, None ) + + self.m_toolBar1.Realize() + + bSizer19 = wx.BoxSizer( wx.VERTICAL ) + + self.m_panel13 = wx.Panel( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + bSizer21 = wx.BoxSizer( wx.VERTICAL ) + + self.m_splitter51 = wx.SplitterWindow( self.m_panel13, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.SP_3D|wx.SP_LIVE_UPDATE ) + self.m_splitter51.SetSashGravity( 1 ) + self.m_splitter51.Bind( wx.EVT_IDLE, self.m_splitter51OnIdle ) + + self.m_panel22 = wx.Panel( self.m_splitter51, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + bSizer72 = wx.BoxSizer( wx.VERTICAL ) + + self.m_splitter4 = wx.SplitterWindow( self.m_panel22, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.SP_LIVE_UPDATE ) + self.m_splitter4.Bind( wx.EVT_IDLE, self.m_splitter4OnIdle ) + self.m_splitter4.SetMinimumPaneSize( 100 ) + + self.m_panel14 = wx.Panel( self.m_splitter4, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + bSizer24 = wx.BoxSizer( wx.HORIZONTAL ) + + self.tree_ctrl_explorer = wx.lib.agw.hypertreelist.HyperTreeList( + self.m_panel14, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, + agwStyle=wx.TR_DEFAULT_STYLE|wx.TR_SINGLE|wx.TR_FULL_ROW_HIGHLIGHT|wx.TR_HIDE_ROOT|wx.TR_LINES_AT_ROOT + ) + bSizer24.Add( self.tree_ctrl_explorer, 1, wx.ALL|wx.EXPAND, 5 ) + + + self.m_panel14.SetSizer( bSizer24 ) + self.m_panel14.Layout() + bSizer24.Fit( self.m_panel14 ) + self.m_menu5 = wx.Menu() + self.m_menuItem4 = wx.MenuItem( self.m_menu5, wx.ID_ANY, _(u"MyMenuItem"), wx.EmptyString, wx.ITEM_NORMAL ) + self.m_menu5.Append( self.m_menuItem4 ) + + self.m_menu1 = wx.Menu() + self.m_menuItem5 = wx.MenuItem( self.m_menu1, wx.ID_ANY, _(u"MyMenuItem"), wx.EmptyString, wx.ITEM_NORMAL ) + self.m_menu1.Append( self.m_menuItem5 ) + + self.m_menu5.AppendSubMenu( self.m_menu1, _(u"MyMenu") ) + + self.m_panel14.Bind( wx.EVT_RIGHT_DOWN, self.m_panel14OnContextMenu ) + + self.m_panel15 = wx.Panel( self.m_splitter4, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + bSizer25 = wx.BoxSizer( wx.VERTICAL ) + + self.MainFrameNotebook = wx.Notebook( self.m_panel15, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.NB_FIXEDWIDTH ) + MainFrameNotebookImageSize = wx.Size( 16,16 ) + MainFrameNotebookIndex = 0 + MainFrameNotebookImages = wx.ImageList( MainFrameNotebookImageSize.GetWidth(), MainFrameNotebookImageSize.GetHeight() ) + self.MainFrameNotebook.AssignImageList( MainFrameNotebookImages ) + self.panel_system = wx.Panel( self.MainFrameNotebook, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + bSizer272 = wx.BoxSizer( wx.VERTICAL ) + + self.m_staticText291 = wx.StaticText( self.panel_system, wx.ID_ANY, _(u"MyLabel"), wx.DefaultPosition, wx.DefaultSize, 0 ) + self.m_staticText291.Wrap( -1 ) + + bSizer272.Add( self.m_staticText291, 0, wx.ALL, 5 ) + + self.system_databases = wx.dataview.DataViewListCtrl( self.panel_system, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0 ) + self.m_dataViewListColumn1 = self.system_databases.AppendTextColumn( _(u"Databases"), wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) + self.m_dataViewListColumn2 = self.system_databases.AppendTextColumn( _(u"Size"), wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) + self.m_dataViewListColumn3 = self.system_databases.AppendTextColumn( _(u"Elements"), wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) + self.m_dataViewListColumn4 = self.system_databases.AppendTextColumn( _(u"Modified at"), wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) + self.m_dataViewListColumn5 = self.system_databases.AppendTextColumn( _(u"Tables"), wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) + bSizer272.Add( self.system_databases, 1, wx.ALL|wx.EXPAND, 5 ) + + + self.panel_system.SetSizer( bSizer272 ) + self.panel_system.Layout() + bSizer272.Fit( self.panel_system ) + self.MainFrameNotebook.AddPage( self.panel_system, _(u"System"), False ) + MainFrameNotebookBitmap = wx.Bitmap( u"icons/16x16/server.png", wx.BITMAP_TYPE_ANY ) + if ( MainFrameNotebookBitmap.IsOk() ): + MainFrameNotebookImages.Add( MainFrameNotebookBitmap ) + self.MainFrameNotebook.SetPageImage( MainFrameNotebookIndex, MainFrameNotebookIndex ) + MainFrameNotebookIndex += 1 + + self.panel_database = wx.Panel( self.MainFrameNotebook, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + bSizer27 = wx.BoxSizer( wx.VERTICAL ) + + self.m_notebook6 = wx.Notebook( self.panel_database, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0 ) + self.m_panel30 = wx.Panel( self.m_notebook6, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + bSizer80 = wx.BoxSizer( wx.VERTICAL ) + + bSizer531 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText391 = wx.StaticText( self.m_panel30, wx.ID_ANY, _(u"Table:"), wx.DefaultPosition, wx.DefaultSize, 0 ) + self.m_staticText391.Wrap( -1 ) + + bSizer531.Add( self.m_staticText391, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + + bSizer531.Add( ( 100, 0), 0, wx.EXPAND, 5 ) + + self.btn_insert_table = wx.Button( self.m_panel30, wx.ID_ANY, _(u"Insert"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) + + self.btn_insert_table.SetBitmap( wx.Bitmap( u"icons/16x16/add.png", wx.BITMAP_TYPE_ANY ) ) + bSizer531.Add( self.btn_insert_table, 0, wx.ALL|wx.EXPAND, 2 ) + + self.btn_clone_table = wx.Button( self.m_panel30, wx.ID_ANY, _(u"Clone"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) + + self.btn_clone_table.SetBitmap( wx.Bitmap( u"icons/16x16/table_multiple.png", wx.BITMAP_TYPE_ANY ) ) + self.btn_clone_table.Enable( False ) + + bSizer531.Add( self.btn_clone_table, 0, wx.ALL|wx.EXPAND, 5 ) + + self.btn_delete_table = wx.Button( self.m_panel30, wx.ID_ANY, _(u"Delete"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) + + self.btn_delete_table.SetBitmap( wx.Bitmap( u"icons/16x16/delete.png", wx.BITMAP_TYPE_ANY ) ) + self.btn_delete_table.Enable( False ) + + bSizer531.Add( self.btn_delete_table, 0, wx.ALL|wx.EXPAND, 2 ) + + + bSizer531.Add( ( 0, 0), 1, wx.EXPAND, 5 ) + + + bSizer80.Add( bSizer531, 0, wx.EXPAND, 5 ) + + self.list_ctrl_database_tables = wx.dataview.DataViewCtrl( self.m_panel30, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0 ) + self.m_dataViewColumn12 = self.list_ctrl_database_tables.AppendTextColumn( _(u"Name"), 0, wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE|wx.dataview.DATAVIEW_COL_SORTABLE ) + self.m_dataViewColumn13 = self.list_ctrl_database_tables.AppendTextColumn( _(u"Rows"), 1, wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_RIGHT, wx.dataview.DATAVIEW_COL_RESIZABLE|wx.dataview.DATAVIEW_COL_SORTABLE ) + self.m_dataViewColumn14 = self.list_ctrl_database_tables.AppendTextColumn( _(u"Size"), 2, wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_RIGHT, wx.dataview.DATAVIEW_COL_RESIZABLE|wx.dataview.DATAVIEW_COL_SORTABLE ) + self.m_dataViewColumn15 = self.list_ctrl_database_tables.AppendDateColumn( _(u"Created at"), 3, wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE|wx.dataview.DATAVIEW_COL_SORTABLE ) + self.m_dataViewColumn16 = self.list_ctrl_database_tables.AppendDateColumn( _(u"Updated at"), 4, wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE|wx.dataview.DATAVIEW_COL_SORTABLE ) + self.m_dataViewColumn17 = self.list_ctrl_database_tables.AppendTextColumn( _(u"Engine"), 5, wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE|wx.dataview.DATAVIEW_COL_SORTABLE ) + self.m_dataViewColumn19 = self.list_ctrl_database_tables.AppendTextColumn( _(u"Collation"), 6, wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE|wx.dataview.DATAVIEW_COL_SORTABLE ) + self.m_dataViewColumn18 = self.list_ctrl_database_tables.AppendTextColumn( _(u"Comments"), 7, wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE|wx.dataview.DATAVIEW_COL_SORTABLE ) + bSizer80.Add( self.list_ctrl_database_tables, 1, wx.ALL|wx.EXPAND, 5 ) + + + self.m_panel30.SetSizer( bSizer80 ) + self.m_panel30.Layout() + bSizer80.Fit( self.m_panel30 ) + self.m_notebook6.AddPage( self.m_panel30, _(u"Tables"), False ) + self.m_panel31 = wx.Panel( self.m_notebook6, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + bSizer82 = wx.BoxSizer( wx.VERTICAL ) + + + self.m_panel31.SetSizer( bSizer82 ) + self.m_panel31.Layout() + bSizer82.Fit( self.m_panel31 ) + self.m_notebook6.AddPage( self.m_panel31, _(u"Diagram"), False ) + + bSizer27.Add( self.m_notebook6, 1, wx.EXPAND | wx.ALL, 5 ) + + + self.panel_database.SetSizer( bSizer27 ) + self.panel_database.Layout() + bSizer27.Fit( self.panel_database ) + self.m_menu15 = wx.Menu() + self.panel_database.Bind( wx.EVT_RIGHT_DOWN, self.panel_databaseOnContextMenu ) + + self.MainFrameNotebook.AddPage( self.panel_database, _(u"Database"), False ) + MainFrameNotebookBitmap = wx.Bitmap( u"icons/16x16/database.png", wx.BITMAP_TYPE_ANY ) + if ( MainFrameNotebookBitmap.IsOk() ): + MainFrameNotebookImages.Add( MainFrameNotebookBitmap ) + self.MainFrameNotebook.SetPageImage( MainFrameNotebookIndex, MainFrameNotebookIndex ) + MainFrameNotebookIndex += 1 + + self.panel_table = wx.Panel( self.MainFrameNotebook, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + bSizer251 = wx.BoxSizer( wx.VERTICAL ) + + self.m_splitter41 = wx.SplitterWindow( self.panel_table, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.SP_LIVE_UPDATE ) + self.m_splitter41.Bind( wx.EVT_IDLE, self.m_splitter41OnIdle ) + self.m_splitter41.SetMinimumPaneSize( 200 ) + + self.m_panel19 = wx.Panel( self.m_splitter41, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + bSizer55 = wx.BoxSizer( wx.VERTICAL ) + + self.m_notebook3 = wx.Notebook( self.m_panel19, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.NB_FIXEDWIDTH ) + m_notebook3ImageSize = wx.Size( 16,16 ) + m_notebook3Index = 0 + m_notebook3Images = wx.ImageList( m_notebook3ImageSize.GetWidth(), m_notebook3ImageSize.GetHeight() ) + self.m_notebook3.AssignImageList( m_notebook3Images ) + self.PanelTableBase = wx.Panel( self.m_notebook3, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + bSizer262 = wx.BoxSizer( wx.VERTICAL ) + + bSizer271 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText8 = wx.StaticText( self.PanelTableBase, wx.ID_ANY, _(u"Name"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) + self.m_staticText8.Wrap( -1 ) + + bSizer271.Add( self.m_staticText8, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + self.table_name = wx.TextCtrl( self.PanelTableBase, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer271.Add( self.table_name, 1, wx.ALL|wx.EXPAND, 5 ) + + + bSizer262.Add( bSizer271, 0, wx.EXPAND, 5 ) + + bSizer273 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText83 = wx.StaticText( self.PanelTableBase, wx.ID_ANY, _(u"Comments"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) + self.m_staticText83.Wrap( -1 ) + + bSizer273.Add( self.m_staticText83, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + self.table_comment = wx.TextCtrl( self.PanelTableBase, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_MULTILINE ) + bSizer273.Add( self.table_comment, 1, wx.ALL|wx.EXPAND, 5 ) + + + bSizer262.Add( bSizer273, 1, wx.EXPAND, 5 ) + + + self.PanelTableBase.SetSizer( bSizer262 ) + self.PanelTableBase.Layout() + bSizer262.Fit( self.PanelTableBase ) + self.m_notebook3.AddPage( self.PanelTableBase, _(u"Base"), True ) + m_notebook3Bitmap = wx.Bitmap( u"icons/16x16/table.png", wx.BITMAP_TYPE_ANY ) + if ( m_notebook3Bitmap.IsOk() ): + m_notebook3Images.Add( m_notebook3Bitmap ) + self.m_notebook3.SetPageImage( m_notebook3Index, m_notebook3Index ) + m_notebook3Index += 1 + + self.PanelTableOptions = wx.Panel( self.m_notebook3, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + bSizer261 = wx.BoxSizer( wx.VERTICAL ) + + gSizer11 = wx.GridSizer( 0, 2, 0, 0 ) + + bSizer27111 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText8111 = wx.StaticText( self.PanelTableOptions, wx.ID_ANY, _(u"Auto Increment"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) + self.m_staticText8111.Wrap( -1 ) + + bSizer27111.Add( self.m_staticText8111, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + self.table_auto_increment = wx.TextCtrl( self.PanelTableOptions, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer27111.Add( self.table_auto_increment, 1, wx.ALL|wx.EXPAND, 5 ) + + + gSizer11.Add( bSizer27111, 1, wx.EXPAND, 5 ) + + bSizer2712 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText812 = wx.StaticText( self.PanelTableOptions, wx.ID_ANY, _(u"Engine"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) + self.m_staticText812.Wrap( -1 ) + + bSizer2712.Add( self.m_staticText812, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + table_engineChoices = [ wx.EmptyString ] + self.table_engine = wx.Choice( self.PanelTableOptions, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, table_engineChoices, 0 ) + self.table_engine.SetSelection( 1 ) + bSizer2712.Add( self.table_engine, 1, wx.ALL|wx.EXPAND, 5 ) + + + gSizer11.Add( bSizer2712, 0, wx.EXPAND, 5 ) + + bSizer2721 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText821 = wx.StaticText( self.PanelTableOptions, wx.ID_ANY, _(u"Default Collation"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) + self.m_staticText821.Wrap( -1 ) + + bSizer2721.Add( self.m_staticText821, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + table_collationChoices = [] + self.table_collation = wx.Choice( self.PanelTableOptions, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, table_collationChoices, 0 ) + self.table_collation.SetSelection( 0 ) + bSizer2721.Add( self.table_collation, 1, wx.ALL, 5 ) + + + gSizer11.Add( bSizer2721, 0, wx.EXPAND, 5 ) + + + bSizer261.Add( gSizer11, 0, wx.EXPAND, 5 ) + + + self.PanelTableOptions.SetSizer( bSizer261 ) + self.PanelTableOptions.Layout() + bSizer261.Fit( self.PanelTableOptions ) + self.m_notebook3.AddPage( self.PanelTableOptions, _(u"Options"), False ) + m_notebook3Bitmap = wx.Bitmap( u"icons/16x16/wrench.png", wx.BITMAP_TYPE_ANY ) + if ( m_notebook3Bitmap.IsOk() ): + m_notebook3Images.Add( m_notebook3Bitmap ) + self.m_notebook3.SetPageImage( m_notebook3Index, m_notebook3Index ) + m_notebook3Index += 1 + + self.PanelTableIndex = wx.Panel( self.m_notebook3, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + bSizer28 = wx.BoxSizer( wx.HORIZONTAL ) + + bSizer791 = wx.BoxSizer( wx.VERTICAL ) + + self.btn_delete_index = wx.Button( self.PanelTableIndex, wx.ID_ANY, _(u"Remove"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) + + self.btn_delete_index.SetBitmap( wx.Bitmap( u"icons/16x16/delete.png", wx.BITMAP_TYPE_ANY ) ) + self.btn_delete_index.Enable( False ) + + bSizer791.Add( self.btn_delete_index, 0, wx.ALL|wx.EXPAND, 5 ) + + self.btn_clear_index = wx.Button( self.PanelTableIndex, wx.ID_ANY, _(u"Clear"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) + + self.btn_clear_index.SetBitmap( wx.Bitmap( u"icons/16x16/cross.png", wx.BITMAP_TYPE_ANY ) ) + bSizer791.Add( self.btn_clear_index, 0, wx.ALL|wx.EXPAND, 5 ) + + + bSizer28.Add( bSizer791, 0, wx.ALIGN_CENTER, 5 ) + + self.dv_table_indexes = TableIndexesDataViewCtrl( self.PanelTableIndex, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer28.Add( self.dv_table_indexes, 1, wx.ALL|wx.EXPAND, 0 ) + + + self.PanelTableIndex.SetSizer( bSizer28 ) + self.PanelTableIndex.Layout() + bSizer28.Fit( self.PanelTableIndex ) + self.m_notebook3.AddPage( self.PanelTableIndex, _(u"Indexes"), False ) + m_notebook3Bitmap = wx.Bitmap( u"icons/16x16/lightning.png", wx.BITMAP_TYPE_ANY ) + if ( m_notebook3Bitmap.IsOk() ): + m_notebook3Images.Add( m_notebook3Bitmap ) + self.m_notebook3.SetPageImage( m_notebook3Index, m_notebook3Index ) + m_notebook3Index += 1 + + self.PanelTableFK = wx.Panel( self.m_notebook3, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + bSizer77 = wx.BoxSizer( wx.VERTICAL ) + + bSizer78 = wx.BoxSizer( wx.HORIZONTAL ) + + bSizer79 = wx.BoxSizer( wx.VERTICAL ) + + self.btn_insert_foreign_key = wx.Button( self.PanelTableFK, wx.ID_ANY, _(u"Insert"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) + + self.btn_insert_foreign_key.SetBitmap( wx.Bitmap( u"icons/16x16/add.png", wx.BITMAP_TYPE_ANY ) ) + bSizer79.Add( self.btn_insert_foreign_key, 0, wx.ALL|wx.EXPAND, 5 ) + + self.btn_delete_foreign_key = wx.Button( self.PanelTableFK, wx.ID_ANY, _(u"Remove"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) + + self.btn_delete_foreign_key.SetBitmap( wx.Bitmap( u"icons/16x16/delete.png", wx.BITMAP_TYPE_ANY ) ) + self.btn_delete_foreign_key.Enable( False ) + + bSizer79.Add( self.btn_delete_foreign_key, 0, wx.ALL|wx.EXPAND, 5 ) + + self.btn_clear_foreign_key = wx.Button( self.PanelTableFK, wx.ID_ANY, _(u"Clear"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) + + self.btn_clear_foreign_key.SetBitmap( wx.Bitmap( u"icons/16x16/cross.png", wx.BITMAP_TYPE_ANY ) ) + bSizer79.Add( self.btn_clear_foreign_key, 0, wx.ALL|wx.EXPAND, 5 ) + + + bSizer78.Add( bSizer79, 0, wx.ALIGN_CENTER, 5 ) + + self.dv_table_foreign_keys = TableForeignKeysDataViewCtrl( self.PanelTableFK, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer78.Add( self.dv_table_foreign_keys, 1, wx.ALL|wx.EXPAND, 0 ) + + + bSizer77.Add( bSizer78, 1, wx.EXPAND, 5 ) + + + self.PanelTableFK.SetSizer( bSizer77 ) + self.PanelTableFK.Layout() + bSizer77.Fit( self.PanelTableFK ) + self.m_notebook3.AddPage( self.PanelTableFK, _(u"Foreign Keys"), False ) + m_notebook3Bitmap = wx.Bitmap( u"icons/16x16/table_relationship.png", wx.BITMAP_TYPE_ANY ) + if ( m_notebook3Bitmap.IsOk() ): + m_notebook3Images.Add( m_notebook3Bitmap ) + self.m_notebook3.SetPageImage( m_notebook3Index, m_notebook3Index ) + m_notebook3Index += 1 + + self.PanelTableCheck = wx.Panel( self.m_notebook3, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + bSizer771 = wx.BoxSizer( wx.VERTICAL ) + + bSizer781 = wx.BoxSizer( wx.HORIZONTAL ) + + bSizer792 = wx.BoxSizer( wx.VERTICAL ) + + self.btn_insert_check = wx.Button( self.PanelTableCheck, wx.ID_ANY, _(u"Insert"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) + + self.btn_insert_check.SetBitmap( wx.Bitmap( u"icons/16x16/add.png", wx.BITMAP_TYPE_ANY ) ) + bSizer792.Add( self.btn_insert_check, 0, wx.ALL|wx.EXPAND, 5 ) + + self.btn_delete_check = wx.Button( self.PanelTableCheck, wx.ID_ANY, _(u"Remove"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) + + self.btn_delete_check.SetBitmap( wx.Bitmap( u"icons/16x16/delete.png", wx.BITMAP_TYPE_ANY ) ) + self.btn_delete_check.Enable( False ) + + bSizer792.Add( self.btn_delete_check, 0, wx.ALL|wx.EXPAND, 5 ) + + self.btn_clear_check = wx.Button( self.PanelTableCheck, wx.ID_ANY, _(u"Clear"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) + + self.btn_clear_check.SetBitmap( wx.Bitmap( u"icons/16x16/cross.png", wx.BITMAP_TYPE_ANY ) ) + bSizer792.Add( self.btn_clear_check, 0, wx.ALL|wx.EXPAND, 5 ) + + + bSizer781.Add( bSizer792, 0, wx.ALIGN_CENTER, 5 ) + + self.dv_table_checks = TableCheckDataViewCtrl( self.PanelTableCheck, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer781.Add( self.dv_table_checks, 1, wx.ALL|wx.EXPAND, 0 ) + + + bSizer771.Add( bSizer781, 1, wx.EXPAND, 5 ) + + + self.PanelTableCheck.SetSizer( bSizer771 ) + self.PanelTableCheck.Layout() + bSizer771.Fit( self.PanelTableCheck ) + self.m_notebook3.AddPage( self.PanelTableCheck, _(u"Checks"), False ) + m_notebook3Bitmap = wx.Bitmap( u"icons/16x16/tick.png", wx.BITMAP_TYPE_ANY ) + if ( m_notebook3Bitmap.IsOk() ): + m_notebook3Images.Add( m_notebook3Bitmap ) + self.m_notebook3.SetPageImage( m_notebook3Index, m_notebook3Index ) + m_notebook3Index += 1 + + self.PanelTableCreate = wx.Panel( self.m_notebook3, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + bSizer109 = wx.BoxSizer( wx.VERTICAL ) + + self.sql_create_table = wx.stc.StyledTextCtrl( self.PanelTableCreate, wx.ID_ANY, wx.DefaultPosition, wx.Size( -1,200 ), 0) + self.sql_create_table.SetUseTabs ( True ) + self.sql_create_table.SetTabWidth ( 4 ) + self.sql_create_table.SetIndent ( 4 ) + self.sql_create_table.SetTabIndents( True ) + self.sql_create_table.SetBackSpaceUnIndents( True ) + self.sql_create_table.SetViewEOL( False ) + self.sql_create_table.SetViewWhiteSpace( False ) + self.sql_create_table.SetMarginWidth( 2, 0 ) + self.sql_create_table.SetIndentationGuides( True ) + self.sql_create_table.SetReadOnly( False ) + self.sql_create_table.SetMarginWidth( 1, 0 ) + self.sql_create_table.SetMarginType( 0, wx.stc.STC_MARGIN_NUMBER ) + self.sql_create_table.SetMarginWidth( 0, self.sql_create_table.TextWidth( wx.stc.STC_STYLE_LINENUMBER, "_99999" ) ) + self.sql_create_table.MarkerDefine( wx.stc.STC_MARKNUM_FOLDER, wx.stc.STC_MARK_BOXPLUS ) + self.sql_create_table.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDER, wx.BLACK) + self.sql_create_table.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDER, wx.WHITE) + self.sql_create_table.MarkerDefine( wx.stc.STC_MARKNUM_FOLDEROPEN, wx.stc.STC_MARK_BOXMINUS ) + self.sql_create_table.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDEROPEN, wx.BLACK ) + self.sql_create_table.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDEROPEN, wx.WHITE ) + self.sql_create_table.MarkerDefine( wx.stc.STC_MARKNUM_FOLDERSUB, wx.stc.STC_MARK_EMPTY ) + self.sql_create_table.MarkerDefine( wx.stc.STC_MARKNUM_FOLDEREND, wx.stc.STC_MARK_BOXPLUS ) + self.sql_create_table.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDEREND, wx.BLACK ) + self.sql_create_table.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDEREND, wx.WHITE ) + self.sql_create_table.MarkerDefine( wx.stc.STC_MARKNUM_FOLDEROPENMID, wx.stc.STC_MARK_BOXMINUS ) + self.sql_create_table.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDEROPENMID, wx.BLACK) + self.sql_create_table.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDEROPENMID, wx.WHITE) + self.sql_create_table.MarkerDefine( wx.stc.STC_MARKNUM_FOLDERMIDTAIL, wx.stc.STC_MARK_EMPTY ) + self.sql_create_table.MarkerDefine( wx.stc.STC_MARKNUM_FOLDERTAIL, wx.stc.STC_MARK_EMPTY ) + self.sql_create_table.SetSelBackground( True, wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT ) ) + self.sql_create_table.SetSelForeground( True, wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHTTEXT ) ) + bSizer109.Add( self.sql_create_table, 1, wx.EXPAND | wx.ALL, 5 ) + + + self.PanelTableCreate.SetSizer( bSizer109 ) + self.PanelTableCreate.Layout() + bSizer109.Fit( self.PanelTableCreate ) + self.m_notebook3.AddPage( self.PanelTableCreate, _(u"Create"), False ) + m_notebook3Bitmap = wx.Bitmap( u"icons/16x16/code-folding.png", wx.BITMAP_TYPE_ANY ) + if ( m_notebook3Bitmap.IsOk() ): + m_notebook3Images.Add( m_notebook3Bitmap ) + self.m_notebook3.SetPageImage( m_notebook3Index, m_notebook3Index ) + m_notebook3Index += 1 + + + bSizer55.Add( self.m_notebook3, 1, wx.EXPAND | wx.ALL, 5 ) + + + self.m_panel19.SetSizer( bSizer55 ) + self.m_panel19.Layout() + bSizer55.Fit( self.m_panel19 ) + self.panel_table_columns = wx.Panel( self.m_splitter41, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + self.panel_table_columns.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_WINDOW ) ) + + bSizer54 = wx.BoxSizer( wx.VERTICAL ) + + bSizer53 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText39 = wx.StaticText( self.panel_table_columns, wx.ID_ANY, _(u"Columns:"), wx.DefaultPosition, wx.DefaultSize, 0 ) + self.m_staticText39.Wrap( -1 ) + + bSizer53.Add( self.m_staticText39, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + + bSizer53.Add( ( 100, 0), 0, wx.EXPAND, 5 ) + + self.btn_insert_column = wx.Button( self.panel_table_columns, wx.ID_ANY, _(u"Insert"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) + + self.btn_insert_column.SetBitmap( wx.Bitmap( u"icons/16x16/add.png", wx.BITMAP_TYPE_ANY ) ) + bSizer53.Add( self.btn_insert_column, 0, wx.LEFT|wx.RIGHT, 2 ) + + self.btn_delete_column = wx.Button( self.panel_table_columns, wx.ID_ANY, _(u"Delete"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) + + self.btn_delete_column.SetBitmap( wx.Bitmap( u"icons/16x16/delete.png", wx.BITMAP_TYPE_ANY ) ) + self.btn_delete_column.Enable( False ) + + bSizer53.Add( self.btn_delete_column, 0, wx.LEFT|wx.RIGHT, 2 ) + + self.btn_move_up_column = wx.Button( self.panel_table_columns, wx.ID_ANY, _(u"Up"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) + + self.btn_move_up_column.SetBitmap( wx.Bitmap( u"icons/16x16/arrow_up.png", wx.BITMAP_TYPE_ANY ) ) + self.btn_move_up_column.Enable( False ) + + bSizer53.Add( self.btn_move_up_column, 0, wx.LEFT|wx.RIGHT, 2 ) + + self.btn_move_down_column = wx.Button( self.panel_table_columns, wx.ID_ANY, _(u"Down"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) + + self.btn_move_down_column.SetBitmap( wx.Bitmap( u"icons/16x16/arrow_down.png", wx.BITMAP_TYPE_ANY ) ) + self.btn_move_down_column.Enable( False ) + + bSizer53.Add( self.btn_move_down_column, 0, wx.LEFT|wx.RIGHT, 2 ) + + + bSizer53.Add( ( 0, 0), 1, wx.EXPAND, 5 ) + + + bSizer54.Add( bSizer53, 0, wx.ALL|wx.EXPAND, 5 ) + + self.list_ctrl_table_columns = TableColumnsDataViewCtrl( self.panel_table_columns, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer54.Add( self.list_ctrl_table_columns, 1, wx.ALL|wx.EXPAND, 5 ) + + bSizer52 = wx.BoxSizer( wx.HORIZONTAL ) + + self.btn_delete_table = wx.Button( self.panel_table_columns, wx.ID_ANY, _(u"Delete"), wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer52.Add( self.btn_delete_table, 0, wx.ALL, 5 ) + + self.btn_cancel_table = wx.Button( self.panel_table_columns, wx.ID_ANY, _(u"Cancel"), wx.DefaultPosition, wx.DefaultSize, 0 ) + self.btn_cancel_table.Enable( False ) + + bSizer52.Add( self.btn_cancel_table, 0, wx.ALL, 5 ) + + self.btn_apply_table = wx.Button( self.panel_table_columns, wx.ID_ANY, _(u"Apply"), wx.DefaultPosition, wx.DefaultSize, 0 ) + self.btn_apply_table.Enable( False ) + + bSizer52.Add( self.btn_apply_table, 0, wx.ALL, 5 ) + + + bSizer54.Add( bSizer52, 0, wx.EXPAND, 5 ) + + + self.panel_table_columns.SetSizer( bSizer54 ) + self.panel_table_columns.Layout() + bSizer54.Fit( self.panel_table_columns ) + self.menu_table_columns = wx.Menu() + self.add_index = wx.MenuItem( self.menu_table_columns, wx.ID_ANY, _(u"Add Index"), wx.EmptyString, wx.ITEM_NORMAL ) + self.menu_table_columns.Append( self.add_index ) + + self.m_menu21 = wx.Menu() + self.m_menuItem8 = wx.MenuItem( self.m_menu21, wx.ID_ANY, _(u"Add PrimaryKey"), wx.EmptyString, wx.ITEM_NORMAL ) + self.m_menu21.Append( self.m_menuItem8 ) + + self.m_menuItem9 = wx.MenuItem( self.m_menu21, wx.ID_ANY, _(u"Add Index"), wx.EmptyString, wx.ITEM_NORMAL ) + self.m_menu21.Append( self.m_menuItem9 ) + + self.menu_table_columns.AppendSubMenu( self.m_menu21, _(u"MyMenu") ) + + self.panel_table_columns.Bind( wx.EVT_RIGHT_DOWN, self.panel_table_columnsOnContextMenu ) + + self.m_splitter41.SplitHorizontally( self.m_panel19, self.panel_table_columns, 200 ) + bSizer251.Add( self.m_splitter41, 1, wx.EXPAND, 0 ) + + + self.panel_table.SetSizer( bSizer251 ) + self.panel_table.Layout() + bSizer251.Fit( self.panel_table ) + self.MainFrameNotebook.AddPage( self.panel_table, _(u"Table"), False ) + MainFrameNotebookBitmap = wx.Bitmap( u"icons/16x16/table.png", wx.BITMAP_TYPE_ANY ) + if ( MainFrameNotebookBitmap.IsOk() ): + MainFrameNotebookImages.Add( MainFrameNotebookBitmap ) + self.MainFrameNotebook.SetPageImage( MainFrameNotebookIndex, MainFrameNotebookIndex ) + MainFrameNotebookIndex += 1 + + self.panel_views = wx.Panel( self.MainFrameNotebook, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + bSizer84 = wx.BoxSizer( wx.VERTICAL ) + + self.m_notebook7 = wx.Notebook( self.panel_views, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0 ) + self.pnl_view_editor_root = wx.Panel( self.m_notebook7, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + bSizer85 = wx.BoxSizer( wx.VERTICAL ) + + bSizer87 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText40 = wx.StaticText( self.pnl_view_editor_root, wx.ID_ANY, _(u"Name"), wx.DefaultPosition, wx.DefaultSize, 0 ) + self.m_staticText40.Wrap( -1 ) + + self.m_staticText40.SetMinSize( wx.Size( 150,-1 ) ) + + bSizer87.Add( self.m_staticText40, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + self.txt_view_name = wx.TextCtrl( self.pnl_view_editor_root, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer87.Add( self.txt_view_name, 1, wx.ALIGN_CENTER|wx.ALL, 5 ) + + + bSizer85.Add( bSizer87, 0, wx.ALL|wx.EXPAND, 5 ) + + bSizer89 = wx.BoxSizer( wx.HORIZONTAL ) + + bSizer116 = wx.BoxSizer( wx.VERTICAL ) + + self.pnl_row_definer = wx.Panel( self.pnl_view_editor_root, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + szr_view_definer = wx.BoxSizer( wx.HORIZONTAL ) + + self.lbl_view_definer = wx.StaticText( self.pnl_row_definer, wx.ID_ANY, _(u"Definer"), wx.DefaultPosition, wx.DefaultSize, 0 ) + self.lbl_view_definer.Wrap( -1 ) + + self.lbl_view_definer.SetMinSize( wx.Size( 150,-1 ) ) + + szr_view_definer.Add( self.lbl_view_definer, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + cmb_view_definerChoices = [] + self.cmb_view_definer = wx.ComboBox( self.pnl_row_definer, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, cmb_view_definerChoices, 0 ) + szr_view_definer.Add( self.cmb_view_definer, 1, wx.ALIGN_CENTER|wx.ALL, 5 ) + + + self.pnl_row_definer.SetSizer( szr_view_definer ) + self.pnl_row_definer.Layout() + szr_view_definer.Fit( self.pnl_row_definer ) + bSizer116.Add( self.pnl_row_definer, 0, wx.EXPAND | wx.ALL, 5 ) + + self.pnl_row_schema = wx.Panel( self.pnl_view_editor_root, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + szr_view_schema = wx.BoxSizer( wx.HORIZONTAL ) + + self.lbl_view_schema = wx.StaticText( self.pnl_row_schema, wx.ID_ANY, _(u"Schema"), wx.DefaultPosition, wx.DefaultSize, 0 ) + self.lbl_view_schema.Wrap( -1 ) + + self.lbl_view_schema.SetMinSize( wx.Size( 150,-1 ) ) + + szr_view_schema.Add( self.lbl_view_schema, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + cho_view_schemaChoices = [] + self.cho_view_schema = wx.Choice( self.pnl_row_schema, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, cho_view_schemaChoices, 0 ) + self.cho_view_schema.SetSelection( 0 ) + szr_view_schema.Add( self.cho_view_schema, 1, wx.ALIGN_CENTER|wx.ALL, 5 ) + + + self.pnl_row_schema.SetSizer( szr_view_schema ) + self.pnl_row_schema.Layout() + szr_view_schema.Fit( self.pnl_row_schema ) + bSizer116.Add( self.pnl_row_schema, 0, wx.EXPAND | wx.ALL, 5 ) + + + bSizer89.Add( bSizer116, 1, wx.EXPAND, 5 ) + + bSizer8711 = wx.BoxSizer( wx.VERTICAL ) + + self.pnl_row_sql_security = wx.Panel( self.pnl_view_editor_root, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + szr_view_sql_security = wx.BoxSizer( wx.HORIZONTAL ) + + self.lbl_view_sql_security = wx.StaticText( self.pnl_row_sql_security, wx.ID_ANY, _(u"SQL security"), wx.DefaultPosition, wx.DefaultSize, 0 ) + self.lbl_view_sql_security.Wrap( -1 ) + + self.lbl_view_sql_security.SetMinSize( wx.Size( 150,-1 ) ) + + szr_view_sql_security.Add( self.lbl_view_sql_security, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + cho_view_sql_securityChoices = [ _(u"DEFINER"), _(u"INVOKER") ] + self.cho_view_sql_security = wx.Choice( self.pnl_row_sql_security, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, cho_view_sql_securityChoices, 0 ) + self.cho_view_sql_security.SetSelection( 0 ) + szr_view_sql_security.Add( self.cho_view_sql_security, 1, wx.ALIGN_CENTER|wx.ALL, 5 ) + + + self.pnl_row_sql_security.SetSizer( szr_view_sql_security ) + self.pnl_row_sql_security.Layout() + szr_view_sql_security.Fit( self.pnl_row_sql_security ) + bSizer8711.Add( self.pnl_row_sql_security, 0, wx.EXPAND | wx.ALL, 5 ) + + self.pnl_row_algorithm = wx.Panel( self.pnl_view_editor_root, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + szr_view_algorithm = wx.StaticBoxSizer( wx.VERTICAL, self.pnl_row_algorithm, _(u"Algorithm") ) + + self.rad_view_algorithm_undefined = wx.RadioButton( szr_view_algorithm.GetStaticBox(), wx.ID_ANY, _(u"UNDEFINED"), wx.DefaultPosition, wx.DefaultSize, wx.RB_GROUP ) + szr_view_algorithm.Add( self.rad_view_algorithm_undefined, 0, wx.ALL, 5 ) + + self.rad_view_algorithm_merge = wx.RadioButton( szr_view_algorithm.GetStaticBox(), wx.ID_ANY, _(u"MERGE"), wx.DefaultPosition, wx.DefaultSize, 0 ) + szr_view_algorithm.Add( self.rad_view_algorithm_merge, 0, wx.ALL, 5 ) + + self.rad_view_algorithm_temptable = wx.RadioButton( szr_view_algorithm.GetStaticBox(), wx.ID_ANY, _(u"TEMPTABLE"), wx.DefaultPosition, wx.DefaultSize, 0 ) + szr_view_algorithm.Add( self.rad_view_algorithm_temptable, 0, wx.ALL, 5 ) + + + self.pnl_row_algorithm.SetSizer( szr_view_algorithm ) + self.pnl_row_algorithm.Layout() + szr_view_algorithm.Fit( self.pnl_row_algorithm ) + bSizer8711.Add( self.pnl_row_algorithm, 0, wx.ALL|wx.EXPAND, 5 ) + + self.pnl_row_constraint = wx.Panel( self.pnl_view_editor_root, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + szr_view_constraint = wx.StaticBoxSizer( wx.VERTICAL, self.pnl_row_constraint, _(u"View constraint") ) + + self.rad_view_constraint_none = wx.RadioButton( szr_view_constraint.GetStaticBox(), wx.ID_ANY, _(u"None"), wx.DefaultPosition, wx.DefaultSize, wx.RB_GROUP ) + szr_view_constraint.Add( self.rad_view_constraint_none, 0, wx.ALL, 5 ) + + self.rad_view_constraint_local = wx.RadioButton( szr_view_constraint.GetStaticBox(), wx.ID_ANY, _(u"LOCAL"), wx.DefaultPosition, wx.DefaultSize, 0 ) + szr_view_constraint.Add( self.rad_view_constraint_local, 0, wx.ALL, 5 ) + + self.rad_view_constraint_cascaded = wx.RadioButton( szr_view_constraint.GetStaticBox(), wx.ID_ANY, _(u"CASCADE"), wx.DefaultPosition, wx.DefaultSize, 0 ) + szr_view_constraint.Add( self.rad_view_constraint_cascaded, 0, wx.ALL, 5 ) + + self.rad_view_constraint_check_only = wx.RadioButton( szr_view_constraint.GetStaticBox(), wx.ID_ANY, _(u"CHECK ONLY"), wx.DefaultPosition, wx.DefaultSize, 0 ) + szr_view_constraint.Add( self.rad_view_constraint_check_only, 0, wx.ALL, 5 ) + + self.rad_view_constraint_read_only = wx.RadioButton( szr_view_constraint.GetStaticBox(), wx.ID_ANY, _(u"READ ONLY"), wx.DefaultPosition, wx.DefaultSize, 0 ) + szr_view_constraint.Add( self.rad_view_constraint_read_only, 0, wx.ALL, 5 ) + + + self.pnl_row_constraint.SetSizer( szr_view_constraint ) + self.pnl_row_constraint.Layout() + szr_view_constraint.Fit( self.pnl_row_constraint ) + bSizer8711.Add( self.pnl_row_constraint, 0, wx.ALL|wx.EXPAND, 5 ) + + self.pnl_row_security_barrier = wx.Panel( self.pnl_view_editor_root, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + bSizer126 = wx.BoxSizer( wx.VERTICAL ) + + self.chk_view_force = wx.CheckBox( self.pnl_row_security_barrier, wx.ID_ANY, _(u"Force"), wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer126.Add( self.chk_view_force, 0, wx.ALL, 5 ) + + + self.pnl_row_security_barrier.SetSizer( bSizer126 ) + self.pnl_row_security_barrier.Layout() + bSizer126.Fit( self.pnl_row_security_barrier ) + bSizer8711.Add( self.pnl_row_security_barrier, 0, wx.EXPAND, 5 ) + + self.pnl_row_force = wx.Panel( self.pnl_view_editor_root, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + bSizer127 = wx.BoxSizer( wx.VERTICAL ) + + self.chk_view_security_barrier = wx.CheckBox( self.pnl_row_force, wx.ID_ANY, _(u"Security barrier"), wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer127.Add( self.chk_view_security_barrier, 0, wx.ALL, 5 ) + + + self.pnl_row_force.SetSizer( bSizer127 ) + self.pnl_row_force.Layout() + bSizer127.Fit( self.pnl_row_force ) + bSizer8711.Add( self.pnl_row_force, 0, wx.EXPAND, 5 ) + + + bSizer89.Add( bSizer8711, 1, wx.EXPAND, 5 ) + + + bSizer85.Add( bSizer89, 0, wx.EXPAND, 5 ) + + + self.pnl_view_editor_root.SetSizer( bSizer85 ) + self.pnl_view_editor_root.Layout() + bSizer85.Fit( self.pnl_view_editor_root ) + self.m_notebook7.AddPage( self.pnl_view_editor_root, _(u"Options"), False ) + + bSizer84.Add( self.m_notebook7, 0, wx.EXPAND | wx.ALL, 5 ) + + self.stc_view_select = wx.stc.StyledTextCtrl( self.panel_views, wx.ID_ANY, wx.DefaultPosition, wx.Size( -1,-1 ), 0) + self.stc_view_select.SetUseTabs ( True ) + self.stc_view_select.SetTabWidth ( 4 ) + self.stc_view_select.SetIndent ( 4 ) + self.stc_view_select.SetTabIndents( True ) + self.stc_view_select.SetBackSpaceUnIndents( True ) + self.stc_view_select.SetViewEOL( False ) + self.stc_view_select.SetViewWhiteSpace( False ) + self.stc_view_select.SetMarginWidth( 2, 0 ) + self.stc_view_select.SetIndentationGuides( True ) + self.stc_view_select.SetReadOnly( False ) + self.stc_view_select.SetMarginWidth( 1, 0 ) + self.stc_view_select.SetMarginType( 0, wx.stc.STC_MARGIN_NUMBER ) + self.stc_view_select.SetMarginWidth( 0, self.stc_view_select.TextWidth( wx.stc.STC_STYLE_LINENUMBER, "_99999" ) ) + self.stc_view_select.MarkerDefine( wx.stc.STC_MARKNUM_FOLDER, wx.stc.STC_MARK_BOXPLUS ) + self.stc_view_select.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDER, wx.BLACK) + self.stc_view_select.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDER, wx.WHITE) + self.stc_view_select.MarkerDefine( wx.stc.STC_MARKNUM_FOLDEROPEN, wx.stc.STC_MARK_BOXMINUS ) + self.stc_view_select.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDEROPEN, wx.BLACK ) + self.stc_view_select.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDEROPEN, wx.WHITE ) + self.stc_view_select.MarkerDefine( wx.stc.STC_MARKNUM_FOLDERSUB, wx.stc.STC_MARK_EMPTY ) + self.stc_view_select.MarkerDefine( wx.stc.STC_MARKNUM_FOLDEREND, wx.stc.STC_MARK_BOXPLUS ) + self.stc_view_select.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDEREND, wx.BLACK ) + self.stc_view_select.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDEREND, wx.WHITE ) + self.stc_view_select.MarkerDefine( wx.stc.STC_MARKNUM_FOLDEROPENMID, wx.stc.STC_MARK_BOXMINUS ) + self.stc_view_select.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDEROPENMID, wx.BLACK) + self.stc_view_select.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDEROPENMID, wx.WHITE) + self.stc_view_select.MarkerDefine( wx.stc.STC_MARKNUM_FOLDERMIDTAIL, wx.stc.STC_MARK_EMPTY ) + self.stc_view_select.MarkerDefine( wx.stc.STC_MARKNUM_FOLDERTAIL, wx.stc.STC_MARK_EMPTY ) + self.stc_view_select.SetSelBackground( True, wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT ) ) + self.stc_view_select.SetSelForeground( True, wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHTTEXT ) ) + self.stc_view_select.SetMinSize( wx.Size( -1,200 ) ) + + bSizer84.Add( self.stc_view_select, 1, wx.EXPAND | wx.ALL, 5 ) + + bSizer91 = wx.BoxSizer( wx.HORIZONTAL ) + + self.btn_delete_view = wx.Button( self.panel_views, wx.ID_ANY, _(u"Delete"), wx.DefaultPosition, wx.DefaultSize, 0 ) + self.btn_delete_view.Enable( False ) + + bSizer91.Add( self.btn_delete_view, 0, wx.ALL, 5 ) + + self.btn_cancel_view = wx.Button( self.panel_views, wx.ID_ANY, _(u"Cancel"), wx.DefaultPosition, wx.DefaultSize, 0 ) + self.btn_cancel_view.Enable( False ) + + bSizer91.Add( self.btn_cancel_view, 0, wx.ALL, 5 ) + + self.btn_save_view = wx.Button( self.panel_views, wx.ID_ANY, _(u"Save"), wx.DefaultPosition, wx.DefaultSize, 0 ) + self.btn_save_view.Enable( False ) + + bSizer91.Add( self.btn_save_view, 0, wx.ALL, 5 ) + + + bSizer84.Add( bSizer91, 0, wx.EXPAND, 5 ) + + + self.panel_views.SetSizer( bSizer84 ) + self.panel_views.Layout() + bSizer84.Fit( self.panel_views ) + self.MainFrameNotebook.AddPage( self.panel_views, _(u"Views"), False ) + MainFrameNotebookBitmap = wx.Bitmap( u"icons/16x16/view.png", wx.BITMAP_TYPE_ANY ) + if ( MainFrameNotebookBitmap.IsOk() ): + MainFrameNotebookImages.Add( MainFrameNotebookBitmap ) + self.MainFrameNotebook.SetPageImage( MainFrameNotebookIndex, MainFrameNotebookIndex ) + MainFrameNotebookIndex += 1 + + self.panel_triggers = wx.Panel( self.MainFrameNotebook, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + self.MainFrameNotebook.AddPage( self.panel_triggers, _(u"Triggers"), False ) + MainFrameNotebookBitmap = wx.Bitmap( u"icons/16x16/cog.png", wx.BITMAP_TYPE_ANY ) + if ( MainFrameNotebookBitmap.IsOk() ): + MainFrameNotebookImages.Add( MainFrameNotebookBitmap ) + self.MainFrameNotebook.SetPageImage( MainFrameNotebookIndex, MainFrameNotebookIndex ) + MainFrameNotebookIndex += 1 + + self.panel_records = wx.Panel( self.MainFrameNotebook, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + bSizer61 = wx.BoxSizer( wx.VERTICAL ) + + bSizer94 = wx.BoxSizer( wx.HORIZONTAL ) + + self.name_database_table = wx.StaticText( self.panel_records, wx.ID_ANY, _(u"Table `%(database_name)s`.`%(table_name)s`: %(total_rows) rows total"), wx.DefaultPosition, wx.DefaultSize, 0 ) + self.name_database_table.Wrap( -1 ) + + bSizer94.Add( self.name_database_table, 0, wx.ALL, 5 ) + + + bSizer61.Add( bSizer94, 0, wx.EXPAND, 5 ) + + bSizer83 = wx.BoxSizer( wx.HORIZONTAL ) + + self.btn_insert_record = wx.Button( self.panel_records, wx.ID_ANY, _(u"Insert record"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) + + self.btn_insert_record.SetBitmap( wx.Bitmap( u"icons/16x16/add.png", wx.BITMAP_TYPE_ANY ) ) + bSizer83.Add( self.btn_insert_record, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + self.btn_duplicate_record = wx.Button( self.panel_records, wx.ID_ANY, _(u"Duplicate record"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) + + self.btn_duplicate_record.SetBitmap( wx.Bitmap( u"icons/16x16/add.png", wx.BITMAP_TYPE_ANY ) ) + self.btn_duplicate_record.Enable( False ) + + bSizer83.Add( self.btn_duplicate_record, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + self.btn_delete_record = wx.Button( self.panel_records, wx.ID_ANY, _(u"Delete record"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) + + self.btn_delete_record.SetBitmap( wx.Bitmap( u"icons/16x16/delete.png", wx.BITMAP_TYPE_ANY ) ) + self.btn_delete_record.Enable( False ) + + bSizer83.Add( self.btn_delete_record, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + self.m_staticline3 = wx.StaticLine( self.panel_records, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_VERTICAL ) + bSizer83.Add( self.m_staticline3, 0, wx.EXPAND | wx.ALL, 5 ) + + self.chb_auto_apply = wx.CheckBox( self.panel_records, wx.ID_ANY, _(u"Apply changes automatically"), wx.DefaultPosition, wx.DefaultSize, 0 ) + self.chb_auto_apply.SetValue(True) + self.chb_auto_apply.SetToolTip( _(u"If enabled, table edits are applied immediately without pressing Apply or Cancel") ) + self.chb_auto_apply.SetHelpText( _(u"If enabled, table edits are applied immediately without pressing Apply or Cancel") ) + + bSizer83.Add( self.chb_auto_apply, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + self.btn_cancel_record = wx.Button( self.panel_records, wx.ID_ANY, _(u"Cancel"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) + + self.btn_cancel_record.SetBitmap( wx.Bitmap( u"icons/16x16/cancel.png", wx.BITMAP_TYPE_ANY ) ) + self.btn_cancel_record.Enable( False ) + + bSizer83.Add( self.btn_cancel_record, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + self.btn_apply_record = wx.Button( self.panel_records, wx.ID_ANY, _(u"Apply"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) + + self.btn_apply_record.SetBitmap( wx.Bitmap( u"icons/16x16/disk.png", wx.BITMAP_TYPE_ANY ) ) + self.btn_apply_record.Enable( False ) + + bSizer83.Add( self.btn_apply_record, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + + bSizer83.Add( ( 0, 0), 1, wx.EXPAND, 5 ) + + self.m_button40 = wx.Button( self.panel_records, wx.ID_ANY, _(u"Next"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) + + self.m_button40.SetBitmap( wx.Bitmap( u"icons/16x16/resultset_next.png", wx.BITMAP_TYPE_ANY ) ) + bSizer83.Add( self.m_button40, 0, wx.ALL, 5 ) + + + bSizer61.Add( bSizer83, 0, wx.EXPAND, 5 ) + + self.m_collapsiblePane1 = wx.CollapsiblePane( self.panel_records, wx.ID_ANY, _(u"Filters"), wx.DefaultPosition, wx.DefaultSize, wx.CP_DEFAULT_STYLE|wx.CP_NO_TLW_RESIZE|wx.FULL_REPAINT_ON_RESIZE ) + self.m_collapsiblePane1.Collapse( False ) + + bSizer831 = wx.BoxSizer( wx.VERTICAL ) + + self.sql_query_filters = wx.stc.StyledTextCtrl( self.m_collapsiblePane1.GetPane(), wx.ID_ANY, wx.DefaultPosition, wx.Size( -1,100 ), 0) + self.sql_query_filters.SetUseTabs ( True ) + self.sql_query_filters.SetTabWidth ( 4 ) + self.sql_query_filters.SetIndent ( 4 ) + self.sql_query_filters.SetTabIndents( True ) + self.sql_query_filters.SetBackSpaceUnIndents( True ) + self.sql_query_filters.SetViewEOL( False ) + self.sql_query_filters.SetViewWhiteSpace( False ) + self.sql_query_filters.SetMarginWidth( 2, 0 ) + self.sql_query_filters.SetIndentationGuides( True ) + self.sql_query_filters.SetReadOnly( False ) + self.sql_query_filters.SetMarginWidth( 1, 0 ) + self.sql_query_filters.SetMarginWidth ( 0, 0 ) + self.sql_query_filters.MarkerDefine( wx.stc.STC_MARKNUM_FOLDER, wx.stc.STC_MARK_BOXPLUS ) + self.sql_query_filters.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDER, wx.BLACK) + self.sql_query_filters.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDER, wx.WHITE) + self.sql_query_filters.MarkerDefine( wx.stc.STC_MARKNUM_FOLDEROPEN, wx.stc.STC_MARK_BOXMINUS ) + self.sql_query_filters.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDEROPEN, wx.BLACK ) + self.sql_query_filters.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDEROPEN, wx.WHITE ) + self.sql_query_filters.MarkerDefine( wx.stc.STC_MARKNUM_FOLDERSUB, wx.stc.STC_MARK_EMPTY ) + self.sql_query_filters.MarkerDefine( wx.stc.STC_MARKNUM_FOLDEREND, wx.stc.STC_MARK_BOXPLUS ) + self.sql_query_filters.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDEREND, wx.BLACK ) + self.sql_query_filters.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDEREND, wx.WHITE ) + self.sql_query_filters.MarkerDefine( wx.stc.STC_MARKNUM_FOLDEROPENMID, wx.stc.STC_MARK_BOXMINUS ) + self.sql_query_filters.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDEROPENMID, wx.BLACK) + self.sql_query_filters.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDEROPENMID, wx.WHITE) + self.sql_query_filters.MarkerDefine( wx.stc.STC_MARKNUM_FOLDERMIDTAIL, wx.stc.STC_MARK_EMPTY ) + self.sql_query_filters.MarkerDefine( wx.stc.STC_MARKNUM_FOLDERTAIL, wx.stc.STC_MARK_EMPTY ) + self.sql_query_filters.SetSelBackground( True, wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT ) ) + self.sql_query_filters.SetSelForeground( True, wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHTTEXT ) ) + bSizer831.Add( self.sql_query_filters, 1, wx.EXPAND | wx.ALL, 5 ) + + self.m_button41 = wx.Button( self.m_collapsiblePane1.GetPane(), wx.ID_ANY, _(u"Apply"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) + + self.m_button41.SetBitmap( wx.Bitmap( u"icons/16x16/tick.png", wx.BITMAP_TYPE_ANY ) ) + self.m_button41.SetHelpText( _(u"CTRL+ENTER") ) + + bSizer831.Add( self.m_button41, 0, wx.ALL, 5 ) + + + self.m_collapsiblePane1.GetPane().SetSizer( bSizer831 ) + self.m_collapsiblePane1.GetPane().Layout() + bSizer831.Fit( self.m_collapsiblePane1.GetPane() ) + bSizer61.Add( self.m_collapsiblePane1, 0, wx.ALL|wx.EXPAND, 5 ) + + self.list_ctrl_table_records = TableRecordsDataViewCtrl( self.panel_records, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.dataview.DV_MULTIPLE ) + self.list_ctrl_table_records.SetFont( wx.Font( 10, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, wx.EmptyString ) ) + + bSizer61.Add( self.list_ctrl_table_records, 1, wx.ALL|wx.EXPAND, 5 ) + + + self.panel_records.SetSizer( bSizer61 ) + self.panel_records.Layout() + bSizer61.Fit( self.panel_records ) + self.m_menu10 = wx.Menu() + self.m_menuItem13 = wx.MenuItem( self.m_menu10, wx.ID_ANY, _(u"Insert row")+ u"\t" + u"Ins", wx.EmptyString, wx.ITEM_NORMAL ) + self.m_menu10.Append( self.m_menuItem13 ) + + self.m_menuItem14 = wx.MenuItem( self.m_menu10, wx.ID_ANY, _(u"MyMenuItem"), wx.EmptyString, wx.ITEM_NORMAL ) + self.m_menu10.Append( self.m_menuItem14 ) + + self.panel_records.Bind( wx.EVT_RIGHT_DOWN, self.panel_recordsOnContextMenu ) + + self.MainFrameNotebook.AddPage( self.panel_records, _(u"Data"), True ) + MainFrameNotebookBitmap = wx.Bitmap( u"icons/16x16/text_columns.png", wx.BITMAP_TYPE_ANY ) + if ( MainFrameNotebookBitmap.IsOk() ): + MainFrameNotebookImages.Add( MainFrameNotebookBitmap ) + self.MainFrameNotebook.SetPageImage( MainFrameNotebookIndex, MainFrameNotebookIndex ) + MainFrameNotebookIndex += 1 + + self.panel_query = wx.Panel( self.MainFrameNotebook, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + bSizer26 = wx.BoxSizer( wx.VERTICAL ) + + self.m_splitter6 = wx.SplitterWindow( self.panel_query, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.SP_3D ) + self.m_splitter6.Bind( wx.EVT_IDLE, self.m_splitter6OnIdle ) + + self.m_panel52 = wx.Panel( self.m_splitter6, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + bSizer125 = wx.BoxSizer( wx.VERTICAL ) + + self.sql_query_editor = wx.stc.StyledTextCtrl( self.m_panel52, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0) + self.sql_query_editor.SetUseTabs ( True ) + self.sql_query_editor.SetTabWidth ( 4 ) + self.sql_query_editor.SetIndent ( 4 ) + self.sql_query_editor.SetTabIndents( True ) + self.sql_query_editor.SetBackSpaceUnIndents( True ) + self.sql_query_editor.SetViewEOL( False ) + self.sql_query_editor.SetViewWhiteSpace( False ) + self.sql_query_editor.SetMarginWidth( 2, 0 ) + self.sql_query_editor.SetIndentationGuides( True ) + self.sql_query_editor.SetReadOnly( False ) + self.sql_query_editor.SetMarginType ( 1, wx.stc.STC_MARGIN_SYMBOL ) + self.sql_query_editor.SetMarginMask ( 1, wx.stc.STC_MASK_FOLDERS ) + self.sql_query_editor.SetMarginWidth ( 1, 16) + self.sql_query_editor.SetMarginSensitive( 1, True ) + self.sql_query_editor.SetProperty ( "fold", "1" ) + self.sql_query_editor.SetFoldFlags ( wx.stc.STC_FOLDFLAG_LINEBEFORE_CONTRACTED | wx.stc.STC_FOLDFLAG_LINEAFTER_CONTRACTED ) + self.sql_query_editor.SetMarginType( 0, wx.stc.STC_MARGIN_NUMBER ) + self.sql_query_editor.SetMarginWidth( 0, self.sql_query_editor.TextWidth( wx.stc.STC_STYLE_LINENUMBER, "_99999" ) ) + self.sql_query_editor.MarkerDefine( wx.stc.STC_MARKNUM_FOLDER, wx.stc.STC_MARK_BOXPLUS ) + self.sql_query_editor.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDER, wx.BLACK) + self.sql_query_editor.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDER, wx.WHITE) + self.sql_query_editor.MarkerDefine( wx.stc.STC_MARKNUM_FOLDEROPEN, wx.stc.STC_MARK_BOXMINUS ) + self.sql_query_editor.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDEROPEN, wx.BLACK ) + self.sql_query_editor.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDEROPEN, wx.WHITE ) + self.sql_query_editor.MarkerDefine( wx.stc.STC_MARKNUM_FOLDERSUB, wx.stc.STC_MARK_EMPTY ) + self.sql_query_editor.MarkerDefine( wx.stc.STC_MARKNUM_FOLDEREND, wx.stc.STC_MARK_BOXPLUS ) + self.sql_query_editor.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDEREND, wx.BLACK ) + self.sql_query_editor.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDEREND, wx.WHITE ) + self.sql_query_editor.MarkerDefine( wx.stc.STC_MARKNUM_FOLDEROPENMID, wx.stc.STC_MARK_BOXMINUS ) + self.sql_query_editor.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDEROPENMID, wx.BLACK) + self.sql_query_editor.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDEROPENMID, wx.WHITE) + self.sql_query_editor.MarkerDefine( wx.stc.STC_MARKNUM_FOLDERMIDTAIL, wx.stc.STC_MARK_EMPTY ) + self.sql_query_editor.MarkerDefine( wx.stc.STC_MARKNUM_FOLDERTAIL, wx.stc.STC_MARK_EMPTY ) + self.sql_query_editor.SetSelBackground( True, wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT ) ) + self.sql_query_editor.SetSelForeground( True, wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHTTEXT ) ) + bSizer125.Add( self.sql_query_editor, 1, wx.EXPAND | wx.ALL, 5 ) + + self.m_button12 = wx.Button( self.m_panel52, wx.ID_ANY, _(u"New"), wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer125.Add( self.m_button12, 0, wx.ALIGN_RIGHT|wx.ALL, 5 ) + + + self.m_panel52.SetSizer( bSizer125 ) + self.m_panel52.Layout() + bSizer125.Fit( self.m_panel52 ) + self.m_panel53 = wx.Panel( self.m_splitter6, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + self.m_panel53.Hide() + + bSizer1261 = wx.BoxSizer( wx.VERTICAL ) + + self.notebook_sql_results = FlatNotebook( self.m_panel53, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0 ) + + bSizer1261.Add( self.notebook_sql_results, 1, wx.EXPAND | wx.ALL, 5 ) + + + self.m_panel53.SetSizer( bSizer1261 ) + self.m_panel53.Layout() + bSizer1261.Fit( self.m_panel53 ) + self.m_splitter6.SplitHorizontally( self.m_panel52, self.m_panel53, -300 ) + bSizer26.Add( self.m_splitter6, 1, wx.EXPAND, 5 ) + + + self.panel_query.SetSizer( bSizer26 ) + self.panel_query.Layout() + bSizer26.Fit( self.panel_query ) + self.MainFrameNotebook.AddPage( self.panel_query, _(u"Query"), False ) + MainFrameNotebookBitmap = wx.Bitmap( u"icons/16x16/arrow_right.png", wx.BITMAP_TYPE_ANY ) + if ( MainFrameNotebookBitmap.IsOk() ): + MainFrameNotebookImages.Add( MainFrameNotebookBitmap ) + self.MainFrameNotebook.SetPageImage( MainFrameNotebookIndex, MainFrameNotebookIndex ) + MainFrameNotebookIndex += 1 + + self.QueryPanelTpl = wx.Panel( self.MainFrameNotebook, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + self.QueryPanelTpl.Hide() + + bSizer263 = wx.BoxSizer( wx.VERTICAL ) + + self.m_textCtrl101 = wx.TextCtrl( self.QueryPanelTpl, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_MULTILINE|wx.TE_RICH|wx.TE_RICH2 ) + bSizer263.Add( self.m_textCtrl101, 1, wx.ALL|wx.EXPAND, 5 ) + + bSizer49 = wx.BoxSizer( wx.HORIZONTAL ) + + + bSizer49.Add( ( 0, 0), 1, wx.EXPAND, 5 ) + + self.m_button17 = wx.Button( self.QueryPanelTpl, wx.ID_ANY, _(u"Close"), wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer49.Add( self.m_button17, 0, wx.ALL, 5 ) + + self.m_button121 = wx.Button( self.QueryPanelTpl, wx.ID_ANY, _(u"New"), wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer49.Add( self.m_button121, 0, wx.ALL, 5 ) + + + bSizer263.Add( bSizer49, 0, wx.EXPAND, 5 ) + + + self.QueryPanelTpl.SetSizer( bSizer263 ) + self.QueryPanelTpl.Layout() + bSizer263.Fit( self.QueryPanelTpl ) + self.MainFrameNotebook.AddPage( self.QueryPanelTpl, _(u"Query #2"), False ) + + bSizer25.Add( self.MainFrameNotebook, 1, wx.ALL|wx.EXPAND, 5 ) + + + self.m_panel15.SetSizer( bSizer25 ) + self.m_panel15.Layout() + bSizer25.Fit( self.m_panel15 ) + self.m_menu3 = wx.Menu() + self.m_menuItem3 = wx.MenuItem( self.m_menu3, wx.ID_ANY, _(u"MyMenuItem"), wx.EmptyString, wx.ITEM_NORMAL ) + self.m_menu3.Append( self.m_menuItem3 ) + + self.m_panel15.Bind( wx.EVT_RIGHT_DOWN, self.m_panel15OnContextMenu ) + + self.m_splitter4.SplitVertically( self.m_panel14, self.m_panel15, 320 ) + bSizer72.Add( self.m_splitter4, 1, wx.EXPAND, 5 ) + + + self.m_panel22.SetSizer( bSizer72 ) + self.m_panel22.Layout() + bSizer72.Fit( self.m_panel22 ) + self.panel_sql_log = wx.Panel( self.m_splitter51, wx.ID_ANY, wx.DefaultPosition, wx.Size( -1,-1 ), wx.TAB_TRAVERSAL ) + sizer_log_sql = wx.BoxSizer( wx.VERTICAL ) + + self.sql_query_logs = wx.stc.StyledTextCtrl( self.panel_sql_log, wx.ID_ANY, wx.DefaultPosition, wx.Size( -1,200 ), 0) + self.sql_query_logs.SetUseTabs ( True ) + self.sql_query_logs.SetTabWidth ( 4 ) + self.sql_query_logs.SetIndent ( 4 ) + self.sql_query_logs.SetTabIndents( True ) + self.sql_query_logs.SetBackSpaceUnIndents( True ) + self.sql_query_logs.SetViewEOL( False ) + self.sql_query_logs.SetViewWhiteSpace( False ) + self.sql_query_logs.SetMarginWidth( 2, 0 ) + self.sql_query_logs.SetIndentationGuides( True ) + self.sql_query_logs.SetReadOnly( False ) + self.sql_query_logs.SetMarginWidth( 1, 0 ) + self.sql_query_logs.SetMarginType( 0, wx.stc.STC_MARGIN_NUMBER ) + self.sql_query_logs.SetMarginWidth( 0, self.sql_query_logs.TextWidth( wx.stc.STC_STYLE_LINENUMBER, "_99999" ) ) + self.sql_query_logs.MarkerDefine( wx.stc.STC_MARKNUM_FOLDER, wx.stc.STC_MARK_BOXPLUS ) + self.sql_query_logs.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDER, wx.BLACK) + self.sql_query_logs.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDER, wx.WHITE) + self.sql_query_logs.MarkerDefine( wx.stc.STC_MARKNUM_FOLDEROPEN, wx.stc.STC_MARK_BOXMINUS ) + self.sql_query_logs.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDEROPEN, wx.BLACK ) + self.sql_query_logs.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDEROPEN, wx.WHITE ) + self.sql_query_logs.MarkerDefine( wx.stc.STC_MARKNUM_FOLDERSUB, wx.stc.STC_MARK_EMPTY ) + self.sql_query_logs.MarkerDefine( wx.stc.STC_MARKNUM_FOLDEREND, wx.stc.STC_MARK_BOXPLUS ) + self.sql_query_logs.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDEREND, wx.BLACK ) + self.sql_query_logs.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDEREND, wx.WHITE ) + self.sql_query_logs.MarkerDefine( wx.stc.STC_MARKNUM_FOLDEROPENMID, wx.stc.STC_MARK_BOXMINUS ) + self.sql_query_logs.MarkerSetBackground( wx.stc.STC_MARKNUM_FOLDEROPENMID, wx.BLACK) + self.sql_query_logs.MarkerSetForeground( wx.stc.STC_MARKNUM_FOLDEROPENMID, wx.WHITE) + self.sql_query_logs.MarkerDefine( wx.stc.STC_MARKNUM_FOLDERMIDTAIL, wx.stc.STC_MARK_EMPTY ) + self.sql_query_logs.MarkerDefine( wx.stc.STC_MARKNUM_FOLDERTAIL, wx.stc.STC_MARK_EMPTY ) + self.sql_query_logs.SetSelBackground( True, wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT ) ) + self.sql_query_logs.SetSelForeground( True, wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHTTEXT ) ) + sizer_log_sql.Add( self.sql_query_logs, 1, wx.EXPAND | wx.ALL, 5 ) + + + self.panel_sql_log.SetSizer( sizer_log_sql ) + self.panel_sql_log.Layout() + sizer_log_sql.Fit( self.panel_sql_log ) + self.m_splitter51.SplitHorizontally( self.m_panel22, self.panel_sql_log, -150 ) + bSizer21.Add( self.m_splitter51, 1, wx.EXPAND, 5 ) + + + self.m_panel13.SetSizer( bSizer21 ) + self.m_panel13.Layout() + bSizer21.Fit( self.m_panel13 ) + bSizer19.Add( self.m_panel13, 1, wx.EXPAND | wx.ALL, 0 ) + + + self.SetSizer( bSizer19 ) + self.Layout() + self.status_bar = self.CreateStatusBar( 4, wx.STB_SIZEGRIP, wx.ID_ANY ) + + self.Centre( wx.BOTH ) + + # Connect Events + self.Bind( wx.EVT_CLOSE, self.do_close ) + self.Bind( wx.EVT_MENU, self.on_menu_about, id = self.m_menuItem15.GetId() ) + self.Bind( wx.EVT_TOOL, self.do_open_connection_manager, id = self.m_tool5.GetId() ) + self.Bind( wx.EVT_TOOL, self.do_disconnect, id = self.m_tool4.GetId() ) + self.MainFrameNotebook.Bind( wx.EVT_NOTEBOOK_PAGE_CHANGED, self.on_page_chaged ) + self.btn_insert_table.Bind( wx.EVT_BUTTON, self.on_insert_table ) + self.btn_clone_table.Bind( wx.EVT_BUTTON, self.on_clone_table ) + self.btn_delete_table.Bind( wx.EVT_BUTTON, self.on_delete_table ) + self.btn_delete_index.Bind( wx.EVT_BUTTON, self.on_delete_index ) + self.btn_clear_index.Bind( wx.EVT_BUTTON, self.on_clear_index ) + self.btn_insert_foreign_key.Bind( wx.EVT_BUTTON, self.on_insert_foreign_key ) + self.btn_delete_foreign_key.Bind( wx.EVT_BUTTON, self.on_delete_foreign_key ) + self.btn_clear_foreign_key.Bind( wx.EVT_BUTTON, self.on_clear_foreign_key ) + self.btn_insert_check.Bind( wx.EVT_BUTTON, self.on_insert_foreign_key ) + self.btn_delete_check.Bind( wx.EVT_BUTTON, self.on_delete_foreign_key ) + self.btn_clear_check.Bind( wx.EVT_BUTTON, self.on_clear_foreign_key ) + self.btn_insert_column.Bind( wx.EVT_BUTTON, self.on_insert_column ) + self.btn_delete_column.Bind( wx.EVT_BUTTON, self.on_delete_column ) + self.btn_move_up_column.Bind( wx.EVT_BUTTON, self.on_move_up_column ) + self.btn_move_down_column.Bind( wx.EVT_BUTTON, self.on_move_down_column ) + self.btn_delete_table.Bind( wx.EVT_BUTTON, self.on_delete_table ) + self.btn_cancel_table.Bind( wx.EVT_BUTTON, self.on_cancel_table ) + self.btn_apply_table.Bind( wx.EVT_BUTTON, self.do_apply_table ) + self.btn_insert_record.Bind( wx.EVT_BUTTON, self.on_insert_record ) + self.btn_duplicate_record.Bind( wx.EVT_BUTTON, self.on_duplicate_record ) + self.btn_delete_record.Bind( wx.EVT_BUTTON, self.on_delete_record ) + self.chb_auto_apply.Bind( wx.EVT_CHECKBOX, self.on_auto_apply ) + self.m_button40.Bind( wx.EVT_BUTTON, self.on_next_records ) + self.m_collapsiblePane1.Bind( wx.EVT_COLLAPSIBLEPANE_CHANGED, self.on_collapsible_pane_changed ) + self.m_button41.Bind( wx.EVT_BUTTON, self.on_apply_filters ) + + def __del__( self ): + pass + + + # Virtual event handlers, override them in your derived class + def do_close( self, event ): + event.Skip() + + def on_menu_about( self, event ): + event.Skip() + + def do_open_connection_manager( self, event ): + event.Skip() + + def do_disconnect( self, event ): + event.Skip() + + def on_page_chaged( self, event ): + event.Skip() + + def on_insert_table( self, event ): + event.Skip() + + def on_clone_table( self, event ): + event.Skip() + + def on_delete_table( self, event ): + event.Skip() + + def on_delete_index( self, event ): + event.Skip() + + def on_clear_index( self, event ): + event.Skip() + + def on_insert_foreign_key( self, event ): + event.Skip() + + def on_delete_foreign_key( self, event ): + event.Skip() + + def on_clear_foreign_key( self, event ): + event.Skip() + + + + + def on_insert_column( self, event ): + event.Skip() + + def on_delete_column( self, event ): + event.Skip() + + def on_move_up_column( self, event ): + event.Skip() + + def on_move_down_column( self, event ): + event.Skip() + + + def on_cancel_table( self, event ): + event.Skip() + + def do_apply_table( self, event ): + event.Skip() + + def on_insert_record( self, event ): + event.Skip() + + def on_duplicate_record( self, event ): + event.Skip() + + def on_delete_record( self, event ): + event.Skip() + + def on_auto_apply( self, event ): + event.Skip() + + def on_next_records( self, event ): + event.Skip() + + def on_collapsible_pane_changed( self, event ): + event.Skip() + + def on_apply_filters( self, event ): + event.Skip() + + def m_splitter51OnIdle( self, event ): + self.m_splitter51.SetSashPosition( -150 ) + self.m_splitter51.Unbind( wx.EVT_IDLE ) + + def m_splitter4OnIdle( self, event ): + self.m_splitter4.SetSashPosition( 320 ) + self.m_splitter4.Unbind( wx.EVT_IDLE ) + + def m_panel14OnContextMenu( self, event ): + self.m_panel14.PopupMenu( self.m_menu5, event.GetPosition() ) + + def panel_databaseOnContextMenu( self, event ): + self.panel_database.PopupMenu( self.m_menu15, event.GetPosition() ) + + def m_splitter41OnIdle( self, event ): + self.m_splitter41.SetSashPosition( 200 ) + self.m_splitter41.Unbind( wx.EVT_IDLE ) + + def panel_table_columnsOnContextMenu( self, event ): + self.panel_table_columns.PopupMenu( self.menu_table_columns, event.GetPosition() ) + + def panel_recordsOnContextMenu( self, event ): + self.panel_records.PopupMenu( self.m_menu10, event.GetPosition() ) + + def m_splitter6OnIdle( self, event ): + self.m_splitter6.SetSashPosition( -300 ) + self.m_splitter6.Unbind( wx.EVT_IDLE ) + + def m_panel15OnContextMenu( self, event ): + self.m_panel15.PopupMenu( self.m_menu3, event.GetPosition() ) + + +########################################################################### +## Class Trash +########################################################################### + +class Trash ( wx.Panel ): + + def __init__( self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition, size = wx.Size( 500,300 ), style = wx.TAB_TRAVERSAL, name = wx.EmptyString ): + wx.Panel.__init__ ( self, parent, id = id, pos = pos, size = size, style = style, name = name ) + + bSizer90 = wx.BoxSizer( wx.VERTICAL ) + + self.m_textCtrl221 = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer90.Add( self.m_textCtrl221, 1, wx.ALL|wx.EXPAND, 5 ) + + bSizer93 = wx.BoxSizer( wx.VERTICAL ) + + self.tree_ctrl_explorer____ = wx.dataview.TreeListCtrl( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.dataview.TL_DEFAULT_STYLE ) + self.tree_ctrl_explorer____.AppendColumn( _(u"Column5"), wx.COL_WIDTH_DEFAULT, wx.ALIGN_LEFT, wx.COL_RESIZABLE ) + + bSizer93.Add( self.tree_ctrl_explorer____, 1, wx.EXPAND | wx.ALL, 5 ) + + bSizer129 = wx.BoxSizer( wx.VERTICAL ) + + self.m_radioBtn11 = wx.RadioButton( self, wx.ID_ANY, _(u"UNDEFINED"), wx.DefaultPosition, wx.DefaultSize, wx.RB_GROUP ) + bSizer129.Add( self.m_radioBtn11, 1, wx.ALL|wx.EXPAND, 5 ) + + self.m_radioBtn21 = wx.RadioButton( self, wx.ID_ANY, _(u"MERGE"), wx.DefaultPosition, wx.DefaultSize, 0 ) + self.m_menu13 = wx.Menu() + self.m_menuItem10 = wx.MenuItem( self.m_menu13, wx.ID_ANY, _(u"Import"), wx.EmptyString, wx.ITEM_NORMAL ) + self.m_menu13.Append( self.m_menuItem10 ) + + self.m_radioBtn21.Bind( wx.EVT_RIGHT_DOWN, self.m_radioBtn21OnContextMenu ) + + bSizer129.Add( self.m_radioBtn21, 1, wx.ALL|wx.EXPAND, 5 ) + + self.m_radioBtn31 = wx.RadioButton( self, wx.ID_ANY, _(u"TEMPTABLE"), wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer129.Add( self.m_radioBtn31, 1, wx.ALL|wx.EXPAND, 5 ) + + self.m_staticText4011 = wx.StaticText( self, wx.ID_ANY, _(u"Algorithm"), wx.DefaultPosition, wx.DefaultSize, 0 ) + self.m_staticText4011.Wrap( -1 ) + + bSizer129.Add( self.m_staticText4011, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + fgSizer1 = wx.FlexGridSizer( 3, 2, 0, 0 ) + fgSizer1.SetFlexibleDirection( wx.BOTH ) + fgSizer1.SetNonFlexibleGrowMode( wx.FLEX_GROWMODE_NONE ) + + + fgSizer1.Add( ( 0, 0), 1, wx.EXPAND, 5 ) + + + bSizer129.Add( fgSizer1, 1, wx.ALL|wx.EXPAND, 5 ) + + self.m_checkBox7 = wx.CheckBox( self, wx.ID_ANY, _(u"Read only"), wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer129.Add( self.m_checkBox7, 0, wx.ALL, 5 ) + + rad_view_algorithmChoices = [ _(u"UNDEFINED"), _(u"MERGE"), _(u"TEMPTABLE") ] + self.rad_view_algorithm = wx.RadioBox( self, wx.ID_ANY, _(u"Algorithm"), wx.DefaultPosition, wx.DefaultSize, rad_view_algorithmChoices, 1, wx.RA_SPECIFY_COLS ) + self.rad_view_algorithm.SetSelection( 0 ) + bSizer129.Add( self.rad_view_algorithm, 0, wx.ALL|wx.EXPAND, 5 ) + + rad_view_constraintChoices = [ _(u"None"), _(u"LOCAL"), _(u"CASCADED"), _(u"CHECK OPTION"), _(u"READ ONLY") ] + self.rad_view_constraint = wx.RadioBox( self, wx.ID_ANY, _(u"View constraint"), wx.DefaultPosition, wx.DefaultSize, rad_view_constraintChoices, 1, wx.RA_SPECIFY_COLS ) + self.rad_view_constraint.SetSelection( 0 ) + self.m_menu15 = wx.Menu() + self.rad_view_constraint.Bind( wx.EVT_RIGHT_DOWN, self.rad_view_constraintOnContextMenu ) + + bSizer129.Add( self.rad_view_constraint, 0, wx.ALL|wx.EXPAND, 5 ) + + self.m_textCtrl10 = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_MULTILINE|wx.TE_RICH|wx.TE_RICH2 ) + bSizer129.Add( self.m_textCtrl10, 1, wx.ALL|wx.EXPAND, 5 ) + + + bSizer93.Add( bSizer129, 1, wx.EXPAND, 5 ) + + self.notebook_sql_results = wx.aui.AuiNotebook( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.aui.AUI_NB_DEFAULT_STYLE|wx.aui.AUI_NB_MIDDLE_CLICK_CLOSE ) + + bSizer93.Add( self.notebook_sql_results, 1, wx.EXPAND | wx.ALL, 5 ) + + self.ssh_tunnel_password1 = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_PASSWORD ) + bSizer93.Add( self.ssh_tunnel_password1, 1, wx.ALIGN_CENTER|wx.ALL, 5 ) + + + bSizer90.Add( bSizer93, 1, wx.EXPAND, 5 ) + + self.m_collapsiblePane2 = wx.CollapsiblePane( self, wx.ID_ANY, _(u"collapsible"), wx.DefaultPosition, wx.DefaultSize, wx.CP_DEFAULT_STYLE ) + self.m_collapsiblePane2.Collapse( False ) + + bSizer92 = wx.BoxSizer( wx.VERTICAL ) + + + self.m_collapsiblePane2.GetPane().SetSizer( bSizer92 ) + self.m_collapsiblePane2.GetPane().Layout() + bSizer92.Fit( self.m_collapsiblePane2.GetPane() ) + bSizer90.Add( self.m_collapsiblePane2, 1, wx.EXPAND | wx.ALL, 5 ) + + self.tree_ctrl_sessions = wx.TreeCtrl( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TR_DEFAULT_STYLE|wx.TR_FULL_ROW_HIGHLIGHT|wx.TR_HAS_BUTTONS|wx.TR_HIDE_ROOT|wx.TR_TWIST_BUTTONS ) + self.m_menu12 = wx.Menu() + self.tree_ctrl_sessions.Bind( wx.EVT_RIGHT_DOWN, self.tree_ctrl_sessionsOnContextMenu ) + + bSizer90.Add( self.tree_ctrl_sessions, 1, wx.ALL|wx.EXPAND, 5 ) + + self.m_treeListCtrl3 = wx.dataview.TreeListCtrl( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.dataview.TL_DEFAULT_STYLE ) + + bSizer90.Add( self.m_treeListCtrl3, 1, wx.EXPAND | wx.ALL, 5 ) + + self.tree_ctrl_sessions1 = wx.dataview.TreeListCtrl( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.dataview.TL_DEFAULT_STYLE ) + self.tree_ctrl_sessions1.AppendColumn( _(u"Column3"), wx.COL_WIDTH_DEFAULT, wx.ALIGN_LEFT, wx.COL_RESIZABLE ) + self.tree_ctrl_sessions1.AppendColumn( _(u"Column4"), wx.COL_WIDTH_DEFAULT, wx.ALIGN_LEFT, wx.COL_RESIZABLE ) + + bSizer90.Add( self.tree_ctrl_sessions1, 1, wx.EXPAND | wx.ALL, 5 ) + + self.table_collationdd = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer90.Add( self.table_collationdd, 1, wx.ALL|wx.EXPAND, 5 ) + + self.m_textCtrl21 = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_MULTILINE ) + bSizer90.Add( self.m_textCtrl21, 1, wx.ALL|wx.EXPAND, 5 ) + + bSizer51 = wx.BoxSizer( wx.VERTICAL ) + + self.panel_credentials = wx.Panel( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + bSizer48 = wx.BoxSizer( wx.VERTICAL ) + + self.m_notebook8 = wx.Notebook( self.panel_credentials, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0 ) + + bSizer48.Add( self.m_notebook8, 1, wx.EXPAND | wx.ALL, 5 ) + + + self.panel_credentials.SetSizer( bSizer48 ) + self.panel_credentials.Layout() + bSizer48.Fit( self.panel_credentials ) + bSizer51.Add( self.panel_credentials, 0, wx.EXPAND | wx.ALL, 0 ) + + self.panel_source = wx.Panel( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + self.panel_source.Hide() + + bSizer52 = wx.BoxSizer( wx.VERTICAL ) + + bSizer1212 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText212 = wx.StaticText( self.panel_source, wx.ID_ANY, _(u"Filename"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) + self.m_staticText212.Wrap( -1 ) + + bSizer1212.Add( self.m_staticText212, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + self.filename = wx.FilePickerCtrl( self.panel_source, wx.ID_ANY, wx.EmptyString, _(u"Select a file"), _(u"Database (*.db;*.db3;*.sdb;*.s3db;*.sqlite;*.sqlite3)|*.db;*.db3;*.sdb;*.s3db;*.sqlite;*.sqlite3"), wx.DefaultPosition, wx.DefaultSize, wx.FLP_CHANGE_DIR|wx.FLP_USE_TEXTCTRL ) + bSizer1212.Add( self.filename, 1, wx.ALL, 5 ) + + + bSizer52.Add( bSizer1212, 0, wx.EXPAND, 0 ) + + + self.panel_source.SetSizer( bSizer52 ) + self.panel_source.Layout() + bSizer52.Fit( self.panel_source ) + bSizer51.Add( self.panel_source, 0, wx.EXPAND | wx.ALL, 0 ) + + self.m_staticText2211 = wx.StaticText( self, wx.ID_ANY, _(u"Port"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) + self.m_staticText2211.Wrap( -1 ) + + bSizer51.Add( self.m_staticText2211, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + + bSizer90.Add( bSizer51, 0, wx.EXPAND, 0 ) + + self.m_panel35 = wx.Panel( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + bSizer96 = wx.BoxSizer( wx.VERTICAL ) + + + self.m_panel35.SetSizer( bSizer96 ) + self.m_panel35.Layout() + bSizer96.Fit( self.m_panel35 ) + bSizer90.Add( self.m_panel35, 1, wx.EXPAND | wx.ALL, 5 ) + + self.ssh_tunnel_port = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer90.Add( self.ssh_tunnel_port, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + self.ssh_tunnel_local_port = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer90.Add( self.ssh_tunnel_local_port, 1, wx.ALIGN_CENTER|wx.ALL, 5 ) + + self.tree_ctrl_sessions2 = wx.TreeCtrl( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TR_DEFAULT_STYLE ) + self.tree_ctrl_sessions2.Hide() + + bSizer90.Add( self.tree_ctrl_sessions2, 1, wx.ALL|wx.EXPAND, 5 ) + + self.tree_ctrl_sessions_bkp3 = wx.dataview.TreeListCtrl( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.dataview.TL_DEFAULT_STYLE|wx.dataview.TL_SINGLE ) + self.tree_ctrl_sessions_bkp3.Hide() + + self.tree_ctrl_sessions_bkp3.AppendColumn( _(u"Name"), wx.COL_WIDTH_DEFAULT, wx.ALIGN_LEFT, wx.COL_RESIZABLE ) + self.tree_ctrl_sessions_bkp3.AppendColumn( _(u"Usage"), wx.COL_WIDTH_DEFAULT, wx.ALIGN_LEFT, wx.COL_RESIZABLE ) + + bSizer90.Add( self.tree_ctrl_sessions_bkp3, 1, wx.EXPAND | wx.ALL, 5 ) + + self.tree_ctrl_sessions_bkp = wx.dataview.DataViewCtrl( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.dataview.DV_SINGLE ) + self.tree_ctrl_sessions_bkp.Hide() + + self.m_dataViewColumn1 = self.tree_ctrl_sessions_bkp.AppendIconTextColumn( _(u"Database"), 0, wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) + self.m_dataViewColumn3 = self.tree_ctrl_sessions_bkp.AppendProgressColumn( _(u"Size"), 1, wx.dataview.DATAVIEW_CELL_INERT, 50, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) + bSizer90.Add( self.tree_ctrl_sessions_bkp, 1, wx.ALL|wx.EXPAND, 5 ) + + self.rows_database_table = wx.StaticText( self, wx.ID_ANY, _(u"%(total_rows)s"), wx.DefaultPosition, wx.DefaultSize, 0 ) + self.rows_database_table.Wrap( -1 ) + + bSizer90.Add( self.rows_database_table, 0, wx.ALL, 5 ) + + self.m_staticText44 = wx.StaticText( self, wx.ID_ANY, _(u"rows total"), wx.DefaultPosition, wx.DefaultSize, 0 ) + self.m_staticText44.Wrap( -1 ) + + bSizer90.Add( self.m_staticText44, 0, wx.ALL, 5 ) + + self.____list_ctrl_database_tables = wx.dataview.DataViewCtrl( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0 ) + self.m_dataViewColumn5 = self.____list_ctrl_database_tables.AppendTextColumn( _(u"Name"), 0, wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) + self.m_dataViewColumn6 = self.____list_ctrl_database_tables.AppendTextColumn( _(u"Name"), 0, wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) + self.m_dataViewColumn7 = self.____list_ctrl_database_tables.AppendTextColumn( _(u"Name"), 0, wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) + self.m_dataViewColumn8 = self.____list_ctrl_database_tables.AppendTextColumn( _(u"Name"), 0, wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) + self.m_dataViewColumn9 = self.____list_ctrl_database_tables.AppendTextColumn( _(u"Name"), 0, wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) + self.m_dataViewColumn10 = self.____list_ctrl_database_tables.AppendTextColumn( _(u"Name"), 0, wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) + self.m_dataViewColumn11 = self.____list_ctrl_database_tables.AppendTextColumn( _(u"Name"), 0, wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) + self.m_dataViewColumn20 = self.____list_ctrl_database_tables.AppendTextColumn( _(u"Name"), 0, wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) + self.m_dataViewColumn21 = self.____list_ctrl_database_tables.AppendTextColumn( _(u"Name"), 0, wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) + bSizer90.Add( self.____list_ctrl_database_tables, 0, wx.ALL, 5 ) + + self.___list_ctrl_database_tables = wx.dataview.DataViewListCtrl( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0 ) + self.m_dataViewListColumn6 = self.___list_ctrl_database_tables.AppendTextColumn( _(u"Name"), wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) + self.m_dataViewListColumn7 = self.___list_ctrl_database_tables.AppendTextColumn( _(u"Lines"), wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) + self.m_dataViewListColumn8 = self.___list_ctrl_database_tables.AppendTextColumn( _(u"Size"), wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) + self.m_dataViewListColumn9 = self.___list_ctrl_database_tables.AppendTextColumn( _(u"Created at"), wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) + self.m_dataViewListColumn10 = self.___list_ctrl_database_tables.AppendTextColumn( _(u"Updated at"), wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) + self.m_dataViewListColumn11 = self.___list_ctrl_database_tables.AppendTextColumn( _(u"Engine"), wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) + self.m_dataViewListColumn12 = self.___list_ctrl_database_tables.AppendTextColumn( _(u"Comments"), wx.dataview.DATAVIEW_CELL_INERT, -1, wx.ALIGN_LEFT, wx.dataview.DATAVIEW_COL_RESIZABLE ) + bSizer90.Add( self.___list_ctrl_database_tables, 1, wx.ALL|wx.EXPAND, 5 ) + + self.m_gauge1 = wx.Gauge( self, wx.ID_ANY, 100, wx.DefaultPosition, wx.DefaultSize, 0 ) + self.m_gauge1.SetValue( 0 ) + bSizer90.Add( self.m_gauge1, 0, wx.ALL|wx.EXPAND, 5 ) + + + bSizer90.Add( ( 150, 0), 0, wx.EXPAND, 5 ) + + + bSizer90.Add( ( 0, 0), 1, wx.EXPAND, 5 ) + + self.tree_ctrl_explorer__ = wx.dataview.DataViewCtrl( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0 ) + self.tree_ctrl_explorer__.Hide() + + bSizer90.Add( self.tree_ctrl_explorer__, 1, wx.ALL|wx.EXPAND, 5 ) + + self.m_vlistBox1 = wx.VListBox( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer90.Add( self.m_vlistBox1, 0, wx.ALL, 5 ) + + m_listBox1Choices = [] + self.m_listBox1 = wx.ListBox( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, m_listBox1Choices, 0 ) + bSizer90.Add( self.m_listBox1, 0, wx.ALL, 5 ) + + bSizer871 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText401 = wx.StaticText( self, wx.ID_ANY, _(u"Temporary"), wx.DefaultPosition, wx.DefaultSize, 0 ) + self.m_staticText401.Wrap( -1 ) + + bSizer871.Add( self.m_staticText401, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + self.m_checkBox5 = wx.CheckBox( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer871.Add( self.m_checkBox5, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + + bSizer90.Add( bSizer871, 1, wx.EXPAND, 5 ) + + self.m_collapsiblePane3 = wx.CollapsiblePane( self, wx.ID_ANY, _(u"Engine options"), wx.DefaultPosition, wx.DefaultSize, wx.CP_DEFAULT_STYLE ) + self.m_collapsiblePane3.Collapse( False ) + + bSizer115 = wx.BoxSizer( wx.VERTICAL ) + + self.m_panel41 = wx.Panel( self.m_collapsiblePane3.GetPane(), wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + bSizer115.Add( self.m_panel41, 1, wx.EXPAND | wx.ALL, 5 ) + + self.m_panel42 = wx.Panel( self.m_collapsiblePane3.GetPane(), wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + bSizer115.Add( self.m_panel42, 1, wx.EXPAND | wx.ALL, 5 ) + + self.m_panel43 = wx.Panel( self.m_collapsiblePane3.GetPane(), wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + bSizer115.Add( self.m_panel43, 1, wx.EXPAND | wx.ALL, 5 ) + + + self.m_collapsiblePane3.GetPane().SetSizer( bSizer115 ) + self.m_collapsiblePane3.GetPane().Layout() + bSizer115.Fit( self.m_collapsiblePane3.GetPane() ) + bSizer90.Add( self.m_collapsiblePane3, 1, wx.EXPAND | wx.ALL, 5 ) + + self.m_textCtrl2211 = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer90.Add( self.m_textCtrl2211, 1, wx.ALL|wx.EXPAND, 5 ) + + self.m_textCtrl2212 = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer90.Add( self.m_textCtrl2212, 1, wx.ALL|wx.EXPAND, 5 ) + + m_comboBox11Choices = [] + self.m_comboBox11 = wx.ComboBox( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, m_comboBox11Choices, 0 ) + bSizer90.Add( self.m_comboBox11, 1, wx.ALL|wx.EXPAND, 5 ) + + gSizer3 = wx.GridSizer( 0, 2, 0, 0 ) + + bSizer8712 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText4012 = wx.StaticText( self, wx.ID_ANY, _(u"Algorithm"), wx.DefaultPosition, wx.DefaultSize, 0 ) + self.m_staticText4012.Wrap( -1 ) + + bSizer8712.Add( self.m_staticText4012, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + self.m_radioBtn1 = wx.RadioButton( self, wx.ID_ANY, _(u"UNDEFINED"), wx.DefaultPosition, wx.DefaultSize, wx.RB_GROUP ) + bSizer8712.Add( self.m_radioBtn1, 1, wx.ALL|wx.EXPAND, 5 ) + + self.m_radioBtn2 = wx.RadioButton( self, wx.ID_ANY, _(u"MERGE"), wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer8712.Add( self.m_radioBtn2, 1, wx.ALL|wx.EXPAND, 5 ) + + self.m_radioBtn3 = wx.RadioButton( self, wx.ID_ANY, _(u"TEMPTABLE"), wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer8712.Add( self.m_radioBtn3, 1, wx.ALL|wx.EXPAND, 5 ) + + + gSizer3.Add( bSizer8712, 1, wx.EXPAND, 5 ) + + bSizer12211 = wx.BoxSizer( wx.HORIZONTAL ) + + + gSizer3.Add( bSizer12211, 0, wx.EXPAND, 5 ) + + + bSizer90.Add( gSizer3, 1, wx.EXPAND, 5 ) + + self.m_radioBtn10 = wx.RadioButton( self, wx.ID_ANY, _(u"RadioBtn"), wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer90.Add( self.m_radioBtn10, 0, wx.ALL, 5 ) + + bSizer86 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_panel44 = wx.Panel( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + bSizer86.Add( self.m_panel44, 1, wx.EXPAND | wx.ALL, 5 ) + + + bSizer90.Add( bSizer86, 0, wx.EXPAND, 5 ) + + self.filename1 = wx.FilePickerCtrl( self, wx.ID_ANY, wx.EmptyString, _(u"Select a file"), _(u"*.*"), wx.DefaultPosition, wx.DefaultSize, wx.FLP_CHANGE_DIR|wx.FLP_DEFAULT_STYLE|wx.FLP_FILE_MUST_EXIST ) + bSizer90.Add( self.filename1, 0, wx.ALL, 5 ) + + + self.SetSizer( bSizer90 ) + self.Layout() + self.m_menu11 = wx.Menu() + self.Bind( wx.EVT_RIGHT_DOWN, self.TrashOnContextMenu ) + + + # Connect Events + self.Bind( wx.EVT_MENU, self.on_import, id = self.m_menuItem10.GetId() ) + self.tree_ctrl_sessions.Bind( wx.EVT_TREE_ITEM_RIGHT_CLICK, self.show_tree_ctrl_menu ) + + def __del__( self ): + pass + + + # Virtual event handlers, override them in your derived class + def on_import( self, event ): + event.Skip() + + def show_tree_ctrl_menu( self, event ): + event.Skip() + + def m_radioBtn21OnContextMenu( self, event ): + self.m_radioBtn21.PopupMenu( self.m_menu13, event.GetPosition() ) + + def rad_view_constraintOnContextMenu( self, event ): + self.rad_view_constraint.PopupMenu( self.m_menu15, event.GetPosition() ) + + def tree_ctrl_sessionsOnContextMenu( self, event ): + self.tree_ctrl_sessions.PopupMenu( self.m_menu12, event.GetPosition() ) + + def TrashOnContextMenu( self, event ): + self.PopupMenu( self.m_menu11, event.GetPosition() ) + + +########################################################################### +## Class MyPanel1 +########################################################################### + +class MyPanel1 ( wx.Panel ): + + def __init__( self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition, size = wx.Size( 500,300 ), style = wx.TAB_TRAVERSAL, name = wx.EmptyString ): + wx.Panel.__init__ ( self, parent, id = id, pos = pos, size = size, style = style, name = name ) + + + def __del__( self ): + pass + + +########################################################################### +## Class EditColumnView +########################################################################### + +class EditColumnView ( wx.Dialog ): + + def __init__( self, parent ): + wx.Dialog.__init__ ( self, parent, id = wx.ID_ANY, title = _(u"Edit Column"), pos = wx.DefaultPosition, size = wx.Size( 600,600 ), style = wx.DEFAULT_DIALOG_STYLE|wx.STAY_ON_TOP ) + + self.SetSizeHints( wx.DefaultSize, wx.DefaultSize ) + + bSizer98 = wx.BoxSizer( wx.VERTICAL ) + + bSizer52 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText26 = wx.StaticText( self, wx.ID_ANY, _(u"Name"), wx.DefaultPosition, wx.Size( 100,-1 ), wx.ST_NO_AUTORESIZE ) + self.m_staticText26.Wrap( -1 ) + + bSizer52.Add( self.m_staticText26, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + self.column_name = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer52.Add( self.column_name, 1, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + self.m_staticText261 = wx.StaticText( self, wx.ID_ANY, _(u"Datatype"), wx.DefaultPosition, wx.Size( 100,-1 ), 0 ) + self.m_staticText261.Wrap( -1 ) + + bSizer52.Add( self.m_staticText261, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + column_datatypeChoices = [] + self.column_datatype = wx.Choice( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, column_datatypeChoices, 0 ) + self.column_datatype.SetSelection( 0 ) + bSizer52.Add( self.column_datatype, 1, wx.ALL, 5 ) + + + bSizer98.Add( bSizer52, 0, wx.EXPAND, 5 ) + + bSizer5211 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText2611 = wx.StaticText( self, wx.ID_ANY, _(u"Length/Set"), wx.DefaultPosition, wx.Size( 100,-1 ), 0 ) + self.m_staticText2611.Wrap( -1 ) + + bSizer5211.Add( self.m_staticText2611, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + bSizer60 = wx.BoxSizer( wx.HORIZONTAL ) + + self.column_set = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer60.Add( self.column_set, 1, wx.ALIGN_CENTER|wx.ALL, 5 ) + + self.column_length = wx.SpinCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.SP_ARROW_KEYS, 0, 65536, 0 ) + bSizer60.Add( self.column_length, 1, wx.ALL, 5 ) + + self.column_scale = wx.SpinCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.SP_WRAP, 0, 65536, 0 ) + self.column_scale.Enable( False ) + + bSizer60.Add( self.column_scale, 1, wx.ALL, 5 ) + + + bSizer5211.Add( bSizer60, 1, wx.EXPAND, 5 ) + + self.m_staticText261111112 = wx.StaticText( self, wx.ID_ANY, _(u"Collation"), wx.DefaultPosition, wx.Size( 100,-1 ), 0 ) + self.m_staticText261111112.Wrap( -1 ) + + bSizer5211.Add( self.m_staticText261111112, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + column_collationChoices = [] + self.column_collation = wx.Choice( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, column_collationChoices, 0 ) + self.column_collation.SetSelection( 0 ) + bSizer5211.Add( self.column_collation, 1, wx.ALL, 5 ) + + + bSizer98.Add( bSizer5211, 0, wx.EXPAND, 5 ) + + bSizer52111 = wx.BoxSizer( wx.HORIZONTAL ) + + + bSizer52111.Add( ( 0, 0), 1, wx.EXPAND, 5 ) + + self.column_unsigned = wx.CheckBox( self, wx.ID_ANY, _(u"Unsigned"), wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer52111.Add( self.column_unsigned, 1, wx.ALL, 5 ) + + + bSizer52111.Add( ( 0, 0), 1, wx.EXPAND, 5 ) + + self.column_allow_null = wx.CheckBox( self, wx.ID_ANY, _(u"Allow NULL"), wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer52111.Add( self.column_allow_null, 1, wx.ALL, 5 ) + + + bSizer52111.Add( ( 0, 0), 1, wx.EXPAND, 5 ) + + self.column_zero_fill = wx.CheckBox( self, wx.ID_ANY, _(u"Zero Fill"), wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer52111.Add( self.column_zero_fill, 1, wx.ALL, 5 ) + + + bSizer52111.Add( ( 0, 0), 1, wx.EXPAND, 5 ) + + + bSizer98.Add( bSizer52111, 0, wx.EXPAND, 5 ) + + bSizer53 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText26111111 = wx.StaticText( self, wx.ID_ANY, _(u"Default"), wx.DefaultPosition, wx.Size( 100,-1 ), 0 ) + self.m_staticText26111111.Wrap( -1 ) + + bSizer53.Add( self.m_staticText26111111, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + self.column_default = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer53.Add( self.column_default, 1, wx.ALL, 5 ) + + + bSizer98.Add( bSizer53, 0, wx.EXPAND, 5 ) + + bSizer531 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText261111111 = wx.StaticText( self, wx.ID_ANY, _(u"Comments"), wx.DefaultPosition, wx.Size( 100,-1 ), 0 ) + self.m_staticText261111111.Wrap( -1 ) + + bSizer531.Add( self.m_staticText261111111, 0, wx.ALL, 5 ) + + self.column_comments = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.Size( -1,100 ), wx.TE_MULTILINE ) + bSizer531.Add( self.column_comments, 1, wx.ALL, 5 ) + + + bSizer98.Add( bSizer531, 0, wx.EXPAND, 5 ) + + bSizer532 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText261111113 = wx.StaticText( self, wx.ID_ANY, _(u"Virtuality"), wx.DefaultPosition, wx.Size( 100,-1 ), 0 ) + self.m_staticText261111113.Wrap( -1 ) + + bSizer532.Add( self.m_staticText261111113, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + column_virtualityChoices = [] + self.column_virtuality = wx.Choice( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, column_virtualityChoices, 0 ) + self.column_virtuality.SetSelection( 0 ) + bSizer532.Add( self.column_virtuality, 1, wx.ALL, 5 ) + + + bSizer98.Add( bSizer532, 0, wx.EXPAND, 5 ) + + bSizer5311 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText2611111111 = wx.StaticText( self, wx.ID_ANY, _(u"Expression"), wx.DefaultPosition, wx.Size( 100,-1 ), 0 ) + self.m_staticText2611111111.Wrap( -1 ) + + bSizer5311.Add( self.m_staticText2611111111, 0, wx.ALL, 5 ) + + self.column_expression = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.Size( -1,100 ), wx.TE_MULTILINE ) + bSizer5311.Add( self.column_expression, 1, wx.ALL, 5 ) + + + bSizer98.Add( bSizer5311, 0, wx.EXPAND, 5 ) + + + bSizer98.Add( ( 0, 0), 1, wx.EXPAND, 5 ) + + self.m_staticline2 = wx.StaticLine( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL ) + bSizer98.Add( self.m_staticline2, 0, wx.EXPAND | wx.ALL, 5 ) + + bSizer64 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_button16 = wx.Button( self, wx.ID_ANY, _(u"Cancel"), wx.DefaultPosition, wx.DefaultSize, 0 ) + + self.m_button16.SetDefault() + + self.m_button16.SetBitmap( wx.Bitmap( u"icons/16x16/cancel.png", wx.BITMAP_TYPE_ANY ) ) + bSizer64.Add( self.m_button16, 0, wx.ALL, 5 ) + + + bSizer64.Add( ( 0, 0), 1, wx.EXPAND, 5 ) + + self.m_button15 = wx.Button( self, wx.ID_ANY, _(u"Save"), wx.DefaultPosition, wx.DefaultSize, 0 ) + + self.m_button15.SetBitmap( wx.Bitmap( u"icons/16x16/disk.png", wx.BITMAP_TYPE_ANY ) ) + bSizer64.Add( self.m_button15, 0, wx.ALL, 5 ) + + + bSizer98.Add( bSizer64, 0, wx.EXPAND, 5 ) + + + self.SetSizer( bSizer98 ) + self.Layout() + + self.Centre( wx.BOTH ) + + def __del__( self ): + pass + + +########################################################################### +## Class TablePanel +########################################################################### + +class TablePanel ( wx.Panel ): + + def __init__( self, parent, id = wx.ID_ANY, pos = wx.DefaultPosition, size = wx.Size( 640,480 ), style = wx.TAB_TRAVERSAL, name = wx.EmptyString ): + wx.Panel.__init__ ( self, parent, id = id, pos = pos, size = size, style = style, name = name ) + + bSizer251 = wx.BoxSizer( wx.VERTICAL ) + + self.m_splitter41 = wx.SplitterWindow( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.SP_LIVE_UPDATE ) + self.m_splitter41.Bind( wx.EVT_IDLE, self.m_splitter41OnIdle ) + self.m_splitter41.SetMinimumPaneSize( 200 ) + + self.m_panel19 = wx.Panel( self.m_splitter41, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + bSizer55 = wx.BoxSizer( wx.VERTICAL ) + + self.m_notebook3 = wx.Notebook( self.m_panel19, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.NB_FIXEDWIDTH ) + m_notebook3ImageSize = wx.Size( 16,16 ) + m_notebook3Index = 0 + m_notebook3Images = wx.ImageList( m_notebook3ImageSize.GetWidth(), m_notebook3ImageSize.GetHeight() ) + self.m_notebook3.AssignImageList( m_notebook3Images ) + self.PanelTableBase = wx.Panel( self.m_notebook3, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + bSizer262 = wx.BoxSizer( wx.VERTICAL ) + + bSizer271 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText8 = wx.StaticText( self.PanelTableBase, wx.ID_ANY, _(u"Name"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) + self.m_staticText8.Wrap( -1 ) + + bSizer271.Add( self.m_staticText8, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + self.table_name = wx.TextCtrl( self.PanelTableBase, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer271.Add( self.table_name, 1, wx.ALL|wx.EXPAND, 5 ) + + + bSizer262.Add( bSizer271, 0, wx.EXPAND, 5 ) + + bSizer273 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText83 = wx.StaticText( self.PanelTableBase, wx.ID_ANY, _(u"Comments"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) + self.m_staticText83.Wrap( -1 ) + + bSizer273.Add( self.m_staticText83, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + self.table_comment = wx.TextCtrl( self.PanelTableBase, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_MULTILINE ) + bSizer273.Add( self.table_comment, 1, wx.ALL|wx.EXPAND, 5 ) + + + bSizer262.Add( bSizer273, 1, wx.EXPAND, 5 ) + + + self.PanelTableBase.SetSizer( bSizer262 ) + self.PanelTableBase.Layout() + bSizer262.Fit( self.PanelTableBase ) + self.m_notebook3.AddPage( self.PanelTableBase, _(u"Base"), True ) + m_notebook3Bitmap = wx.Bitmap( u"icons/16x16/table.png", wx.BITMAP_TYPE_ANY ) + if ( m_notebook3Bitmap.IsOk() ): + m_notebook3Images.Add( m_notebook3Bitmap ) + self.m_notebook3.SetPageImage( m_notebook3Index, m_notebook3Index ) + m_notebook3Index += 1 + + self.PanelTableOptions = wx.Panel( self.m_notebook3, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + bSizer261 = wx.BoxSizer( wx.VERTICAL ) + + gSizer11 = wx.GridSizer( 0, 2, 0, 0 ) + + bSizer27111 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText8111 = wx.StaticText( self.PanelTableOptions, wx.ID_ANY, _(u"Auto Increment"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) + self.m_staticText8111.Wrap( -1 ) + + bSizer27111.Add( self.m_staticText8111, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + self.table_auto_increment = wx.TextCtrl( self.PanelTableOptions, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer27111.Add( self.table_auto_increment, 1, wx.ALL|wx.EXPAND, 5 ) + + + gSizer11.Add( bSizer27111, 1, wx.EXPAND, 5 ) + + bSizer2712 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText812 = wx.StaticText( self.PanelTableOptions, wx.ID_ANY, _(u"Engine"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) + self.m_staticText812.Wrap( -1 ) + + bSizer2712.Add( self.m_staticText812, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + table_engineChoices = [ wx.EmptyString ] + self.table_engine = wx.Choice( self.PanelTableOptions, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, table_engineChoices, 0 ) + self.table_engine.SetSelection( 1 ) + bSizer2712.Add( self.table_engine, 1, wx.ALL|wx.EXPAND, 5 ) + + + gSizer11.Add( bSizer2712, 0, wx.EXPAND, 5 ) + + bSizer2721 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText821 = wx.StaticText( self.PanelTableOptions, wx.ID_ANY, _(u"Default Collation"), wx.DefaultPosition, wx.Size( 150,-1 ), 0 ) + self.m_staticText821.Wrap( -1 ) + + bSizer2721.Add( self.m_staticText821, 0, wx.ALIGN_CENTER|wx.ALL, 5 ) + + self.table_collation = wx.TextCtrl( self.PanelTableOptions, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer2721.Add( self.table_collation, 1, wx.ALL|wx.EXPAND, 5 ) + + + gSizer11.Add( bSizer2721, 0, wx.EXPAND, 5 ) + + + bSizer261.Add( gSizer11, 0, wx.EXPAND, 5 ) + + + self.PanelTableOptions.SetSizer( bSizer261 ) + self.PanelTableOptions.Layout() + bSizer261.Fit( self.PanelTableOptions ) + self.m_notebook3.AddPage( self.PanelTableOptions, _(u"Options"), False ) + m_notebook3Bitmap = wx.Bitmap( u"icons/16x16/wrench.png", wx.BITMAP_TYPE_ANY ) + if ( m_notebook3Bitmap.IsOk() ): + m_notebook3Images.Add( m_notebook3Bitmap ) + self.m_notebook3.SetPageImage( m_notebook3Index, m_notebook3Index ) + m_notebook3Index += 1 + + self.PanelTableIndex = wx.Panel( self.m_notebook3, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + bSizer28 = wx.BoxSizer( wx.HORIZONTAL ) + + + self.PanelTableIndex.SetSizer( bSizer28 ) + self.PanelTableIndex.Layout() + bSizer28.Fit( self.PanelTableIndex ) + self.m_notebook3.AddPage( self.PanelTableIndex, _(u"Indexes"), False ) + m_notebook3Bitmap = wx.Bitmap( u"icons/16x16/lightning.png", wx.BITMAP_TYPE_ANY ) + if ( m_notebook3Bitmap.IsOk() ): + m_notebook3Images.Add( m_notebook3Bitmap ) + self.m_notebook3.SetPageImage( m_notebook3Index, m_notebook3Index ) + m_notebook3Index += 1 + + + bSizer55.Add( self.m_notebook3, 1, wx.EXPAND | wx.ALL, 5 ) + + + self.m_panel19.SetSizer( bSizer55 ) + self.m_panel19.Layout() + bSizer55.Fit( self.m_panel19 ) + self.panel_table_columns = wx.Panel( self.m_splitter41, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL ) + self.panel_table_columns.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_WINDOW ) ) + + bSizer54 = wx.BoxSizer( wx.VERTICAL ) + + bSizer53 = wx.BoxSizer( wx.HORIZONTAL ) + + self.m_staticText39 = wx.StaticText( self.panel_table_columns, wx.ID_ANY, _(u"Columns:"), wx.DefaultPosition, wx.DefaultSize, 0 ) + self.m_staticText39.Wrap( -1 ) + + bSizer53.Add( self.m_staticText39, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5 ) + + + bSizer53.Add( ( 100, 0), 0, wx.EXPAND, 5 ) + + self.btn_insert_column = wx.Button( self.panel_table_columns, wx.ID_ANY, _(u"Insert"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) + + self.btn_insert_column.SetBitmap( wx.Bitmap( u"icons/16x16/add.png", wx.BITMAP_TYPE_ANY ) ) + bSizer53.Add( self.btn_insert_column, 0, wx.LEFT|wx.RIGHT, 2 ) + + self.btn_column_delete = wx.Button( self.panel_table_columns, wx.ID_ANY, _(u"Delete"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) + + self.btn_column_delete.SetBitmap( wx.Bitmap( u"icons/16x16/delete.png", wx.BITMAP_TYPE_ANY ) ) + self.btn_column_delete.Enable( False ) + + bSizer53.Add( self.btn_column_delete, 0, wx.LEFT|wx.RIGHT, 2 ) + + self.btn_column_move_up = wx.Button( self.panel_table_columns, wx.ID_ANY, _(u"Up"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) + + self.btn_column_move_up.SetBitmap( wx.Bitmap( u"icons/16x16/arrow_up.png", wx.BITMAP_TYPE_ANY ) ) + self.btn_column_move_up.Enable( False ) + + bSizer53.Add( self.btn_column_move_up, 0, wx.LEFT|wx.RIGHT, 2 ) + + self.btn_column_move_down = wx.Button( self.panel_table_columns, wx.ID_ANY, _(u"Down"), wx.DefaultPosition, wx.DefaultSize, wx.BORDER_NONE ) + + self.btn_column_move_down.SetBitmap( wx.Bitmap( u"icons/16x16/arrow_down.png", wx.BITMAP_TYPE_ANY ) ) + self.btn_column_move_down.Enable( False ) + + bSizer53.Add( self.btn_column_move_down, 0, wx.LEFT|wx.RIGHT, 2 ) + + + bSizer53.Add( ( 0, 0), 1, wx.EXPAND, 5 ) + + + bSizer54.Add( bSizer53, 0, wx.ALL|wx.EXPAND, 5 ) + + self.list_ctrl_table_columns = TableColumnsDataViewCtrl( self.panel_table_columns, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer54.Add( self.list_ctrl_table_columns, 1, wx.ALL|wx.EXPAND, 5 ) + + bSizer52 = wx.BoxSizer( wx.HORIZONTAL ) + + self.btn_table_delete = wx.Button( self.panel_table_columns, wx.ID_ANY, _(u"Delete"), wx.DefaultPosition, wx.DefaultSize, 0 ) + bSizer52.Add( self.btn_table_delete, 0, wx.ALL, 5 ) + + self.btn_table_cancel = wx.Button( self.panel_table_columns, wx.ID_ANY, _(u"Cancel"), wx.DefaultPosition, wx.DefaultSize, 0 ) + self.btn_table_cancel.Enable( False ) + + bSizer52.Add( self.btn_table_cancel, 0, wx.ALL, 5 ) + + self.btn_table_save = wx.Button( self.panel_table_columns, wx.ID_ANY, _(u"Save"), wx.DefaultPosition, wx.DefaultSize, 0 ) + self.btn_table_save.Enable( False ) + + bSizer52.Add( self.btn_table_save, 0, wx.ALL, 5 ) + + + bSizer54.Add( bSizer52, 0, wx.EXPAND, 5 ) + + + self.panel_table_columns.SetSizer( bSizer54 ) + self.panel_table_columns.Layout() + bSizer54.Fit( self.panel_table_columns ) + self.menu_table_columns = wx.Menu() + self.add_index = wx.MenuItem( self.menu_table_columns, wx.ID_ANY, _(u"Add Index"), wx.EmptyString, wx.ITEM_NORMAL ) + self.menu_table_columns.Append( self.add_index ) + + self.m_menu21 = wx.Menu() + self.m_menuItem8 = wx.MenuItem( self.m_menu21, wx.ID_ANY, _(u"Add PrimaryKey"), wx.EmptyString, wx.ITEM_NORMAL ) + self.m_menu21.Append( self.m_menuItem8 ) + + self.m_menuItem9 = wx.MenuItem( self.m_menu21, wx.ID_ANY, _(u"Add Index"), wx.EmptyString, wx.ITEM_NORMAL ) + self.m_menu21.Append( self.m_menuItem9 ) + + self.menu_table_columns.AppendSubMenu( self.m_menu21, _(u"MyMenu") ) + + self.panel_table_columns.Bind( wx.EVT_RIGHT_DOWN, self.panel_table_columnsOnContextMenu ) + + self.m_splitter41.SplitHorizontally( self.m_panel19, self.panel_table_columns, 200 ) + bSizer251.Add( self.m_splitter41, 1, wx.EXPAND, 0 ) + + + self.SetSizer( bSizer251 ) + self.Layout() + + # Connect Events + self.btn_insert_column.Bind( wx.EVT_BUTTON, self.on_column_insert ) + self.btn_column_delete.Bind( wx.EVT_BUTTON, self.on_column_delete ) + self.btn_column_move_up.Bind( wx.EVT_BUTTON, self.on_column_move_up ) + self.btn_column_move_down.Bind( wx.EVT_BUTTON, self.on_column_move_down ) + self.btn_table_delete.Bind( wx.EVT_BUTTON, self.on_delete_table ) + self.btn_table_cancel.Bind( wx.EVT_BUTTON, self.do_cancel_table ) + self.btn_table_save.Bind( wx.EVT_BUTTON, self.do_save_table ) + + def __del__( self ): + pass + + + # Virtual event handlers, override them in your derived class + def on_column_insert( self, event ): + event.Skip() + + def on_column_delete( self, event ): + event.Skip() + + def on_column_move_up( self, event ): + event.Skip() + + def on_column_move_down( self, event ): + event.Skip() + + def on_delete_table( self, event ): + event.Skip() + + def do_cancel_table( self, event ): + event.Skip() + + def do_save_table( self, event ): + event.Skip() + + def m_splitter41OnIdle( self, event ): + self.m_splitter41.SetSashPosition( 200 ) + self.m_splitter41.Unbind( wx.EVT_IDLE ) + + def panel_table_columnsOnContextMenu( self, event ): + self.panel_table_columns.PopupMenu( self.menu_table_columns, event.GetPosition() ) + +