Metadata-Version: 2.5
Name: execsql2
Version: 2.28.0
Summary: Runs a SQL script against a PostgreSQL, SQLite, MariaDB/MySQL, DuckDB, Firebird, MS-Access, MS-SQL-Server, or Oracle database, or an ODBC DSN. Provides metacommands to import and export data, copy data between databases, conditionally execute SQL and metacommands, and dynamically alter SQL and metacommands with substitution variables.
Project-URL: Homepage, https://execsql2.readthedocs.io
Project-URL: Repository, https://github.com/geocoug/execsql
Project-URL: Issues, https://github.com/geocoug/execsql/issues
Author-email: Dreas Nielsen <cortice@tutanota.com>
Maintainer-email: Caleb Grant <grantcaleb22@gmail.com>
License: execsql2 — a fork of execsql.py
        Copyright (c) 2007-2025 R.Dreas Nielsen
        Copyright (c) 2026-present Caleb Grant
        
        This program is free software: you can redistribute it and/or modify it under
        the terms of the GNU General Public License as published by the Free Software
        Foundation, either version 3 of the License, or (at your option) any later
        version. This program is distributed in the hope that it will be useful, but
        WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
        FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
        details. The GNU General Public License is available at
        http://www.gnu.org/licenses/.
License-File: LICENSE.txt
License-File: NOTICE
Keywords: Access,CSV,DBMS,DuckDB,ETL,Firebird,HTML,JSON,LaTeX,MariaDB,MySQL,ODBC,OpenDocument,Oracle,PostgreSQL,Postgres,SQL,SQL Server,SQLite,TSV,XML,database,export,import,query,script,table
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console
Classifier: Intended Audience :: End Users/Desktop
Classifier: Intended Audience :: Information Technology
Classifier: Intended Audience :: System Administrators
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
Classifier: Natural Language :: English
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Database
Classifier: Topic :: Database :: Front-Ends
Requires-Python: >=3.10
Requires-Dist: python-dateutil>=2.8
Requires-Dist: rich>=13.0
Requires-Dist: textual>=1.0
Requires-Dist: typer>=0.26
Provides-Extra: all
Requires-Dist: defusedxml>=0.7; extra == 'all'
Requires-Dist: duckdb>=1.0; extra == 'all'
Requires-Dist: firebird-driver>=1.10; extra == 'all'
Requires-Dist: jinja2>=3.1; extra == 'all'
Requires-Dist: keyring>=25.0; extra == 'all'
Requires-Dist: lsprotocol>=2025.0.0; extra == 'all'
Requires-Dist: odfpy>=1.4; extra == 'all'
Requires-Dist: openpyxl>=3.1; extra == 'all'
Requires-Dist: oracledb>=3.0; extra == 'all'
Requires-Dist: pg-upsert>=1.25.0; extra == 'all'
Requires-Dist: polars>=1.0; extra == 'all'
Requires-Dist: psycopg[binary]<4,>=3.1; extra == 'all'
Requires-Dist: pygls<3,>=2.1; extra == 'all'
Requires-Dist: pymysql>=1.1; extra == 'all'
Requires-Dist: pyodbc>=5.0; extra == 'all'
Requires-Dist: pytz; extra == 'all'
Requires-Dist: pyyaml>=6.0; extra == 'all'
Requires-Dist: sqlglot<30.22,>=30.21.0; extra == 'all'
Requires-Dist: tables>=3.10; extra == 'all'
Requires-Dist: tkintermapview>=1.29; extra == 'all'
Requires-Dist: xlrd>=2.0; extra == 'all'
Provides-Extra: all-db
Requires-Dist: duckdb>=1.0; extra == 'all-db'
Requires-Dist: firebird-driver>=1.10; extra == 'all-db'
Requires-Dist: oracledb>=3.0; extra == 'all-db'
Requires-Dist: psycopg[binary]<4,>=3.1; extra == 'all-db'
Requires-Dist: pymysql>=1.1; extra == 'all-db'
Requires-Dist: pyodbc>=5.0; extra == 'all-db'
Requires-Dist: pytz; extra == 'all-db'
Provides-Extra: auth
Requires-Dist: keyring>=25.0; extra == 'auth'
Provides-Extra: auth-encrypted
Requires-Dist: keyring>=25.0; extra == 'auth-encrypted'
Requires-Dist: keyrings-alt>=5.0; extra == 'auth-encrypted'
Requires-Dist: pycryptodome>=3.20; extra == 'auth-encrypted'
Provides-Extra: auth-plaintext
Requires-Dist: keyring>=25.0; extra == 'auth-plaintext'
Requires-Dist: keyrings-alt>=5.0; extra == 'auth-plaintext'
Provides-Extra: dev
Requires-Dist: build>=1.2.2.post1; extra == 'dev'
Requires-Dist: bump-my-version>=1.2.7; extra == 'dev'
Requires-Dist: defusedxml>=0.7; extra == 'dev'
Requires-Dist: jinja2>=3.1; extra == 'dev'
Requires-Dist: lsprotocol>=2025.0.0; extra == 'dev'
Requires-Dist: mkdocstrings-python>=2.0.3; extra == 'dev'
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: odfpy>=1.4; extra == 'dev'
Requires-Dist: openpyxl>=3.1; extra == 'dev'
Requires-Dist: polars>=1.0; extra == 'dev'
Requires-Dist: pre-commit>=3.5.0; extra == 'dev'
Requires-Dist: pygls<3,>=2.1; extra == 'dev'
Requires-Dist: pytest-asyncio>=1.0; extra == 'dev'
Requires-Dist: pytest-cov>=5.0.0; extra == 'dev'
Requires-Dist: pytest-lsp>=1.0; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: pyyaml>=6.0; extra == 'dev'
Requires-Dist: ruff>=0.4; extra == 'dev'
Requires-Dist: sqlglot<30.22,>=30.21.0; extra == 'dev'
Requires-Dist: tables>=3.10; extra == 'dev'
Requires-Dist: tox-uv>=1.13.1; extra == 'dev'
Requires-Dist: twine>=6.1.0; extra == 'dev'
Requires-Dist: xlrd>=2.0; extra == 'dev'
Requires-Dist: zensical==0.0.67; extra == 'dev'
Provides-Extra: duckdb
Requires-Dist: duckdb>=1.0; extra == 'duckdb'
Requires-Dist: pytz; extra == 'duckdb'
Provides-Extra: firebird
Requires-Dist: firebird-driver>=1.10; extra == 'firebird'
Provides-Extra: formats
Requires-Dist: defusedxml>=0.7; extra == 'formats'
Requires-Dist: jinja2>=3.1; extra == 'formats'
Requires-Dist: odfpy>=1.4; extra == 'formats'
Requires-Dist: openpyxl>=3.1; extra == 'formats'
Requires-Dist: polars>=1.0; extra == 'formats'
Requires-Dist: pyyaml>=6.0; extra == 'formats'
Requires-Dist: tables>=3.10; extra == 'formats'
Requires-Dist: xlrd>=2.0; extra == 'formats'
Provides-Extra: formatter
Requires-Dist: sqlglot<30.22,>=30.21.0; extra == 'formatter'
Provides-Extra: lsp
Requires-Dist: lsprotocol>=2025.0.0; extra == 'lsp'
Requires-Dist: pygls<3,>=2.1; extra == 'lsp'
Requires-Dist: sqlglot<30.22,>=30.21.0; extra == 'lsp'
Provides-Extra: map
Requires-Dist: tkintermapview>=1.29; extra == 'map'
Provides-Extra: mssql
Requires-Dist: pyodbc>=5.0; extra == 'mssql'
Provides-Extra: mysql
Requires-Dist: pymysql>=1.1; extra == 'mysql'
Provides-Extra: odbc
Requires-Dist: pyodbc>=5.0; extra == 'odbc'
Provides-Extra: oracle
Requires-Dist: oracledb>=3.0; extra == 'oracle'
Provides-Extra: postgres
Requires-Dist: psycopg[binary]<4,>=3.1; extra == 'postgres'
Provides-Extra: upsert
Requires-Dist: pg-upsert>=1.25.0; extra == 'upsert'
Description-Content-Type: text/markdown

> [!NOTE]
> ***execsql2* is a maintained fork of [execsql](https://execsql.readthedocs.io/)**, originally authored by R.Dreas Nielsen and no longer actively maintained upstream. The monolith has been fully refactored into a modular package; the CLI and configuration remain backwards-compatible with upstream v1.130.1. Maintained by [Caleb Grant](https://github.com/geocoug) and distributed on PyPI as [`execsql2`](https://pypi.org/project/execsql2/). Report issues at [github.com/geocoug/execsql/issues](https://github.com/geocoug/execsql/issues).

<div align="center">

<img src="https://execsql2.readthedocs.io/en/latest/images/execsql_logo_01.png" alt="execsql logo">

*Multi-DBMS SQL script processor.*

</div>

<div align="center">

[![CI/CD](https://github.com/geocoug/execsql/actions/workflows/ci-cd.yml/badge.svg)](https://github.com/geocoug/execsql/actions/workflows/ci-cd.yml)
[![codecov](https://codecov.io/gh/geocoug/execsql/graph/badge.svg)](https://codecov.io/gh/geocoug/execsql)
[![Docs](https://readthedocs.org/projects/execsql2/badge/)](https://execsql2.readthedocs.io/)
[![PyPI](https://img.shields.io/pypi/v/execsql2)](https://pypi.org/project/execsql2/)
[![Python](https://img.shields.io/pypi/pyversions/execsql2)](https://pypi.org/project/execsql2/)
[![License](https://img.shields.io/pypi/l/execsql2)](https://pypi.org/project/execsql2/)
[![Downloads](https://pepy.tech/badge/execsql2/month)](https://pepy.tech/project/execsql2)

</div>

## Overview

*execsql* is a toolchain for SQL scripts: **write** them with editor support, **format** them consistently, **lint** them without a database, and **run** them against nine DBMSs.

Scripts are ordinary SQL plus metacommands embedded in comments (`-- !x!`), which add importing and exporting data, copying between databases, conditional execution, looping, substitution variables, and interactive prompts. Because the metacommands live in comments, the scripts stay valid SQL and other tools (`psql`, `sqlcmd`, your editor) ignore them.

| Command                                    | What it does                                                                                                                                                          | Needs a database? |
| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- |
| `execsql format`                           | Normalize keywords, indentation, and SQL layout                                                                                                                       | No                |
| `execsql lint`                             | Static analysis: structure, unknown metacommands, variables, targets                                                                                                  | No                |
| `execsql run`                              | Run the script against PostgreSQL, MySQL, SQLite, DuckDB, …                                                                                                           | Yes               |
| `execsql config`                           | Show every config option, its value, and the file that set it                                                                                                         | No                |
| `execsql list`                             | Metacommands, encodings, plugins, or the keyword vocabulary                                                                                                           | No                |
| `execsql init`                             | Set up a project: config file, a script with a header, pre-commit hooks                                                                                               | No                |
| `execsql lsp`                              | [Language server](https://execsql2.readthedocs.io/en/latest/guides/editors/): lint findings, completion, hover, navigation, quick fixes and formatting in your editor | No                |
| [VS Code extension](extras/vscode-execsql) | Highlighting plus everything `execsql lsp` provides                                                                                                                   | No                |

`format` and `lint` take files or directories.

> [!WARNING]
> The original positional form, `execsql script.sql myserver mydb`, still works
> but is deprecated: it prints a warning and stops working in execsql2 3.0. Use
> `execsql run script.sql myserver mydb`.

## Quick start: no database required

`format` and `lint` work on a bare `.sql` file, with no server and no config. Try them first:

```bash
uv tool install "execsql2[formatter]"     # or: pipx install "execsql2[formatter]"

execsql format --check scripts/   # is it formatted?
execsql format -i scripts/        # format it
execsql lint scripts/             # find problems before they cost you a run
```

`lint` reports unmatched blocks, misspelled or malformed metacommands and conditions, undefined and unused substitution variables, unreachable branches, and `INCLUDE`/`SCRIPT` targets that do not exist: the failures that otherwise surface halfway through a run against a live database:

```text
$ execsql lint load_data.sql
load_data.sql
  1  warning  V002  variable !!site_cd!! is never used
  2  warning  I001  INCLUDE file does not exist: common/setup.sql
  3  warning  V001  undefined variable !!site_code!!

Found 3 issues in 1 file: 3 warnings (1 file checked)
```

`V002` and `V001` together point at a typo: `site_cd` is defined and `site_code` is used. Every issue has a rule code for `--select` and `--ignore`; `--output-format concise` prints one `path:line` line per issue and `json` feeds CI tools. The [lint rules](https://execsql2.readthedocs.io/en/latest/reference/lint/) page explains each one.

`execsql format` also runs as a [pre-commit hook](#formatting-scripts), so formatting is enforced without anyone remembering to run it.

## Example

Lines prefixed with `-- !x!` are metacommands; identifiers wrapped in `!!` are substitution variables:

```sql
-- ==== Configuration ====
-- Put the (date-tagged) logfile name in the 'inputfile' substitution variable.
-- !x! SUB inputfile logs/errors_!!$date_tag!!
-- Ensure that the export directory will be created if necessary.
-- !x! CONFIG MAKE_EXPORT_DIRS Yes

-- ==== Display Fatal Errors ====
-- !x! IF(file_exists(!!inputfile!!))
    -- Import the data to a staging table.
    -- !x! IMPORT TO REPLACEMENT staging.errorlog FROM !!inputfile!!
    -- Create a view to display only fatal errors.
    create temporary view fatals as
        select user, run_time, process
        from   staging.errorlog
        where  severity = 'FATAL';
    -- !x! IF(HASROWS(fatals))
        -- Export the fatal errors to a dated report.
        -- !x! EXPORT fatals TO reports/error_report_!!$date_tag!! AS CSV
        -- Also display it to the user in a GUI.
        -- !x! PROMPT MESSAGE "Fatal errors in !!inputfile!!:" DISPLAY fatals
    -- !x! ELSE
        -- !x! WRITE "There are no fatal errors."
    -- !x! ENDIF
-- !x! ELSE
    -- !x! WRITE "There is no error log."
-- !x! ENDIF
drop table if exists staging.errorlog cascade;
```

The `PROMPT` metacommand produces a GUI display of the data:

![PROMPT display of 'fatals' view](https://execsql2.readthedocs.io/en/latest/images/fatals.png)

## Installation

```bash
uv tool install execsql2                  # core: SQLite works with no extras
uv tool install "execsql2[postgres]"      # with a driver: postgres, mysql, mssql, duckdb, firebird, oracle, odbc
uv tool install "execsql2[all]"           # everything: all drivers plus all feature extras
```

`pipx install` takes the same arguments. Quote the name when it has extras
(zsh reports `no matches found` otherwise). To call execsql from Python code,
add it to that project instead: `uv add "execsql2[postgres]"` or
`pip install "execsql2[postgres]"` in its virtual environment.

Feature extras cover spreadsheet and Parquet/Feather formats, keyring authentication, PostgreSQL upsert, and more. See the [installation guide](https://execsql2.readthedocs.io/en/latest/getting-started/installation/) for the full list.

## Usage

```text
execsql run    [OPTIONS] SQL_SCRIPT [SERVER DATABASE | DATABASE_FILE]
execsql format [--check | -i] [--indent N] FILE_OR_DIR...
execsql lint   [OPTIONS] FILE_OR_DIR...
execsql config [SQL_SCRIPT] [--init] [--config FILE]
execsql list   metacommands|encodings|plugins|keywords
execsql init   [DIR] [--script NAME] [--no-script] [--no-config] [--no-pre-commit]
```

Examples:

```bash
execsql run -tp script.sql myserver mydb        # PostgreSQL
execsql run -tm script.sql myserver mydb        # MySQL / MariaDB
execsql run -ts script.sql myserver mydb        # SQL Server
execsql run -tl script.sql mydb.sqlite          # SQLite
execsql run -tk script.sql mydb.duckdb          # DuckDB
execsql run -to script.sql myserver myservice   # Oracle
execsql run script.sql                          # read connection from config file
```

### Supported Databases

| Flag | Database        | Support                                         |
| ---- | --------------- | ----------------------------------------------- |
| `p`  | PostgreSQL      | Supported: verified against a live server in CI |
| `m`  | MySQL / MariaDB | Supported: verified against a live server in CI |
| `s`  | MS SQL Server   | Supported: verified against a live server in CI |
| `l`  | SQLite          | Supported: verified against real files in CI    |
| `k`  | DuckDB          | Supported: verified against real files in CI    |
| `a`  | MS Access       | Best effort: not verified in CI                 |
| `f`  | Firebird        | Best effort: not verified in CI                 |
| `o`  | Oracle          | Best effort: not verified in CI                 |
| `d`  | ODBC DSN        | Best effort: not verified in CI                 |

**Supported** adapters run against a live server or a real database file on
every CI run; a regression blocks the release and bugs get fixed. **Best
effort** adapters are carried forward from the upstream monolith and are not
exercised anywhere in CI. The code is there and may work, but nothing proves
it still does. Issues and pull requests are welcome for them; no guarantee is
made. Opening a best-effort connection prints one informational line, which
`support_tier_notice=No` in the `[interface]` section of `execsql.conf`
silences.

### Common options

| Flag                                              | Description                                                                |
| ------------------------------------------------- | -------------------------------------------------------------------------- |
| `-t {p,m,s,l,k,a,f,o,d}`                          | Database type                                                              |
| `-u USER`                                         | Database username                                                          |
| `-p PORT`                                         | Server port                                                                |
| `--dsn URL`                                       | Connection string (e.g. `postgresql://user:pass@host/db`)                  |
| `-n`                                              | Create a new SQLite or PostgreSQL database if it does not exist            |
| `-c SCRIPT`                                       | Execute inline SQL or metacommand string                                   |
| `-a VALUE`                                        | Set substitution variable `$ARG_x`                                         |
| `--var NAME=VALUE`                                | Set the substitution variable `!!NAME!!`                                   |
| `-v {0,1,2,3}`                                    | GUI level (0=none, 1=password/PAUSE, 2=+HALT and DB selection, 3=console)  |
| `--config FILE`                                   | Load an explicit config file                                               |
| `--dry-run`                                       | Parse the script and report commands without executing                     |
| `--manifest FILE`                                 | Write a JSON record of the run: files read and written, statements, errors |
| `--debug`                                         | Start in step-through debug mode (REPL pauses before each stmt)            |
| `--no-system-cmd` / `--no-rm-file` / `--no-serve` | Disable the `SYSTEM_CMD` / `RM_FILE` / `SERVE` metacommands                |

See the [full options reference](https://execsql2.readthedocs.io/en/latest/getting-started/syntax/#options) or run `execsql run --help` for the complete list, `execsql run --ping` to test a connection, `execsql config` to see which config files set what, and `execsql list metacommands` for all metacommands.

## Features

**Data movement**

- Import data from CSV, TSV, JSON, Excel, OpenDocument, Feather, or Parquet files into a database table.
- Export query results in 20+ formats including CSV, TSV, JSON, YAML, XML, HTML, Markdown, LaTeX, XLSX, OpenDocument, Feather, Parquet, HDF5, DuckDB, SQLite, plain text, and Jinja2 templates.
- Copy data between databases, including across different DBMS types.

**Script logic**

- Conditionally execute SQL and metacommands using `IF`/`ELSE`/`ENDIF` based on data values, DBMS type, or user input.
- Loop over blocks of SQL and metacommands using `LOOP`/`ENDLOOP`; include or chain scripts with `INCLUDE` and `SCRIPT`.
- Use substitution variables (`SUB`, `$ARG_x`, built-in variables like `$date_tag`) to parameterize scripts.
- Validate data with `ASSERT`: halt the script with a clear error message if a condition is false (ideal for CI pipelines).

**Interaction & observability**

- Display query results in a GUI dialog; optionally prompt the user to select a row, enter a value, or submit a form.
- Write status messages or tabular output to the console or a file during execution.
- Automatically log each run, recording databases used, scripts executed, and user responses.

**Extensibility**

- Extend with custom metacommands, exporters, and importers via the plugin system.

## Library API

execsql can be used as a Python library for programmatic script execution:

```python
from execsql import run

# Execute a script file
result = run(script="pipeline.sql", dsn="postgresql://user:pass@host/db")

# Execute inline SQL
result = run(
    sql="CREATE TABLE t (id INT);\nINSERT INTO t VALUES (1);",
    dsn="sqlite:///my.db",
    new_db=True,
)

# With substitution variables: each key becomes a $ variable,
# so the script refers to these as !!$SCHEMA!! and !!$DATE!!
result = run(
    script="etl.sql",
    dsn="sqlite:///data.db",
    variables={"SCHEMA": "public", "DATE": "2026-01-01"},
)

# Check results
print(result.success)       # True
print(result.commands_run)  # 2
print(result.elapsed)       # 0.003 (seconds)
print(result.variables)     # {"schema": "public", ...} (names lower-cased, no $)
```

Error handling:

```python
result = run(sql="SELECT * FROM nonexistent;", dsn="sqlite:///:memory:")
if not result.success:
    for err in result.errors:
        print(f"{err.source}:{err.line}: {err.message}")

# Or raise on failure
result.raise_on_error()  # raises ExecSqlError
```

Use a pre-existing database connection instead of a DSN. This is useful for reusing one connection across multiple `run()` calls, or when connection parameters come from your application's configuration rather than a URL:

```python
from execsql import run
from execsql.db.factory import db_Postgres, db_SQLite

# PostgreSQL connection object (psycopg3 under the hood)
conn = db_Postgres(
    "db.example.com",              # server
    "warehouse",                   # database
    user="etl",
    port=5432,                     # optional, default 5432
    password="s3cret",             # omit to use keyring / interactive prompt
)

# Reuse the same connection across multiple calls
staged = run(script="stage.sql", connection=conn)
loaded = run(
    script="load.sql",
    connection=conn,
    variables={"RUN_DATE": "2026-07-03"},
)

# run() does NOT close the connection; you manage its lifecycle
conn.close()

# SQLite works the same way
conn = db_SQLite("my.db", new_db=True)
result = run(sql="SELECT 1;", connection=conn)
conn.close()
```

Factory functions exist for every supported backend (`db_Postgres`, `db_MySQL`, `db_SQLite`, `db_DuckDB`, `db_SqlServer`, `db_Oracle`, `db_Firebird`, `db_Access`, `db_Dsn`). See [`execsql.db.factory`](https://execsql2.readthedocs.io/en/latest/api/db/).

Each call to `run()` uses an isolated `RuntimeContext`, so multiple calls do not share state.

## Formatting Scripts

The `execsql format` command normalizes execsql script files: it uppercases metacommand keywords, corrects block indentation, and optionally reformats SQL via `sqlglot`. The metacommand / indent / keyword reformatting is built into `execsql2`; SQL reformatting requires the `[formatter]` extra (or pass `--no-sql` to skip it):

```bash
# Install with the SQL-reformatting extra
uv tool install "execsql2[formatter]"

# Format files in place
execsql format --in-place scripts/

# Check formatting without writing (useful in CI)
execsql format --check scripts/

# Run the formatter without sqlglot: keyword/indent normalization only
execsql format --no-sql --in-place scripts/
```

`execsql format` and `execsql lint` are also available as [pre-commit](https://pre-commit.com/) hooks. The format hook id is unchanged, so existing configs keep working:

```yaml
repos:
  - repo: https://github.com/geocoug/execsql
    rev: v2.28.0
    hooks:
      - id: execsql-format
      - id: execsql-lint
```

The format hook rewrites `*.sql` files in place by default; the lint hook fails the commit when a script has a lint error. See the [formatter documentation](https://execsql2.readthedocs.io/en/latest/guides/formatter/) for `--check`, `--indent`, and other options.

## VS Code

The VS Code extension in [`extras/vscode-execsql`](extras/vscode-execsql) highlights execsql syntax in `.sql` files (metacommand markers, keywords, variable substitutions, export formats, config options) on top of standard SQL highlighting, and starts the execsql language server for lint findings as you type and completion. Each GitHub release has the extension attached as `execsql-syntax.vsix`:

```sh
uv tool install "execsql2[lsp]"                     # the language server
code --install-extension execsql-syntax.vsix        # the extension, from the release
```

If execsql is already installed with other extras, name them too, e.g. `uv tool install "execsql2[postgres,lsp]"` or `pipx install --force "execsql2[postgres,lsp]"`: the new list replaces the old one (see [Adding an extra later](https://execsql2.readthedocs.io/en/latest/getting-started/installation/#adding-an-extra-later)).

Other editors use the language server directly: see [Editor Support](https://execsql2.readthedocs.io/en/latest/guides/editors/). The [extension README](extras/vscode-execsql/README.md) covers settings, installing from a clone, color customization, and troubleshooting.

## Templates

The `templates/` directory in this repository includes ready-to-use execsql scripts:

- **Upsert scripts** (`pg_upsert.sql`, `md_upsert.sql`, `ss_upsert.sql`): Perform merge/upsert operations on multiple tables simultaneously, respecting foreign key order, for PostgreSQL, MySQL/MariaDB, and SQL Server.
- **Comparison scripts** (`pg_compare.sql`, `md_compare.sql`, `ss_compare.sql`): Compare staging and base tables across multiple dimensions.
- **Glossary scripts** (`pg_glossary.sql`, `md_glossary.sql`, `ss_glossary.sql`): Produce a glossary of column names and definitions to accompany a database export.
- **`script_template.sql`**: A framework for new scripts with sections for configuration, logging, and error reporting.
- **`execsql.conf`**: An annotated configuration file covering all available settings.

## Documentation

Full documentation, including a complete metacommand reference and 30+ examples, is at [execsql2.readthedocs.io](https://execsql2.readthedocs.io/).

## Copyright and License

Copyright (c) 2007-2025 R.Dreas Nielsen

Copyright (c) 2026-present Caleb Grant

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the [GNU General Public License](http://www.gnu.org/licenses/) for more details.
