Skip to main content
execsql logo

Multi-DBMS SQL script processor.

CI/CD codecov Docs PyPI Python License Downloads

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
VS Code extension Syntax highlighting for metacommands and variables No

format and lint take files or directories.

Quick start: no database required

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

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:

$ 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 page explains each one.

execsql format also runs as a pre-commit hook, so formatting is enforced without anyone remembering to run it.

Example

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

-- ==== 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

Installation

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 for the full list.

Usage

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:

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 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:

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:

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:

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.

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):

# 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 hooks. The format hook id is unchanged, so existing configs keep working:

repos:
  - repo: https://github.com/geocoug/execsql
    rev: v2.25.1
    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 for --check, --indent, and other options.

VS Code Syntax Highlighting

A VS Code extension for execsql syntax highlighting is included in extras/vscode-execsql. It injects a TextMate grammar into .sql files, adding highlighting for -- !x! metacommand markers, keywords (control flow, block, action, directive), variable substitutions (!!var!!, !{var}!), built-in functions, export formats, and config options, all layered on top of standard SQL highlighting.

To install, symlink the extension folder into your VS Code extensions directory:

ln -s /path/to/execsql/extras/vscode-execsql ~/.vscode/extensions/execsql-syntax

See the extension README for Windows instructions, 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.

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.

Metadata

Release files for execsql2 2.25.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for execsql2 2.25.1
File Size Uploaded
execsql2-2.25.1.tar.gz 2.5 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for execsql2 2.25.1
File Interpreter ABI Platform
execsql2-2.25.1-py3-none-any.whl Python 3 none any Details

Total release size: 3.0 MB

Release files / execsql2-2.25.1.tar.gz

Download URL execsql2-2.25.1.tar.gz
Size 2.5 MB
Tags Source
SHA-256 checksum
How to use checksums
6e0e787cb9a66d9e436ccfc8b4f419c848e8798e3ed3e6b106345305291d6a61
BLAKE2b-256 checksum
How to use checksums
1e63267b05dafb1b92e84c116ec13e4d668c4a7734ac03687419002dfebefa27
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.

Transparency log

Release files / execsql2-2.25.1-py3-none-any.whl

Download URL execsql2-2.25.1-py3-none-any.whl
Size 534.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
86e2327857622e257d269e9e99a03cd9d656d871ecf561ea37cbc742e01df833
BLAKE2b-256 checksum
How to use checksums
d2ae7e5cf60bdcf4f53ff3659f884f0d30cd228193531954515c9eb96874e149
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.

Transparency log

Release history Release notifications | RSS feed

2.33.0

2 release files

2.32.0

2 release files

2.31.2

2 release files

2.31.1

2 release files

2.30.0

2 release files

2.29.0

2 release files

2.28.0

2 release files

2.27.0

2 release files

This release

2.25.1 This release

2 release files

2.23.1

2 release files

2.23.0

2 release files

2.22.9

2 release files

2.22.8

2 release files

2.22.7

2 release files

2.22.6

2 release files

2.22.5

2 release files

2.22.4

2 release files

2.22.3

2 release files

2.22.2

2 release files

2.21.1

2 release files

2.21.0

2 release files

2.20.0

2 release files

2.18.1

2 release files

2.18.0

2 release files

2.17.3

2 release files

2.17.2

2 release files

2.16.8

2 release files

2.16.7

2 release files

2.16.6

2 release files

2.16.5

2 release files

2.16.4

2 release files

2.16.3

2 release files

2.16.2

2 release files

2.16.1

2 release files

2.16.0

2 release files

2.15.8

2 release files

2.15.7

2 release files

2.15.6

2 release files

2.15.5

2 release files

2.15.4

2 release files

2.15.2

2 release files

2.15.1

2 release files

2.9.0

2 release files

2.8.0

2 release files

2.7.1

2 release files

2.6.0

2 release files

2.5.0

2 release files

2.4.5

2 release files

2.4.4

2 release files

2.4.1

2 release files

2.4.0

2 release files

2.2.1

2 release files

2.1.2

2 release files

2.1.1

2 release files

2.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page