execsql2 2.22.9__tar.gz → 2.23.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {execsql2-2.22.9 → execsql2-2.23.1}/.github/workflows/ci-cd.yml +22 -15
- {execsql2-2.22.9 → execsql2-2.23.1}/.gitignore +1 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/CHANGELOG.md +50 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/PKG-INFO +22 -13
- {execsql2-2.22.9 → execsql2-2.23.1}/README.md +21 -12
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/about/divergence.md +40 -9
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/api/index.md +17 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/getting-started/requirements.md +26 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/guides/encoding.md +1 -1
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/guides/formatter.md +1 -1
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/reference/configuration.md +4 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/reference/metacommands.md +15 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/reference/substitution_vars.md +1 -1
- {execsql2-2.22.9 → execsql2-2.23.1}/pyproject.toml +3 -3
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/api.py +71 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/config.py +2 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/data/execsql.conf.template +5 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/db/access.py +34 -10
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/db/base.py +120 -2
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/db/dsn.py +14 -7
- execsql2-2.23.1/src/execsql/db/duckdb.py +182 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/db/firebird.py +12 -7
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/db/mysql.py +55 -10
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/db/oracle.py +24 -11
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/db/postgres.py +17 -9
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/db/sqlite.py +23 -8
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/db/sqlserver.py +7 -7
- execsql2-2.23.1/src/execsql/db/tiers.py +79 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/exporters/delimited.py +15 -1
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/exporters/html.py +8 -2
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/exporters/latex.py +35 -2
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/exporters/markdown.py +9 -3
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/exporters/ods.py +22 -4
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/exporters/pretty.py +13 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/exporters/templates.py +18 -1
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/exporters/values.py +25 -4
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/exporters/xlsx.py +7 -1
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/state.py +5 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/state.pyi +3 -1
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/types.py +4 -1
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/utils/fileio.py +47 -2
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/db/test_postgres_inprocess.py +12 -0
- execsql2-2.23.1/tests/db/test_roundtrip_adapters.py +211 -0
- execsql2-2.23.1/tests/db/test_tiers.py +224 -0
- execsql2-2.23.1/tests/db/test_transactions.py +360 -0
- execsql2-2.23.1/tests/exporters/conftest.py +277 -0
- execsql2-2.23.1/tests/exporters/test_roundtrip_columnar.py +130 -0
- execsql2-2.23.1/tests/exporters/test_roundtrip_dbfile.py +207 -0
- execsql2-2.23.1/tests/exporters/test_roundtrip_delimited.py +147 -0
- execsql2-2.23.1/tests/exporters/test_roundtrip_json.py +81 -0
- execsql2-2.23.1/tests/exporters/test_roundtrip_latex_xml.py +133 -0
- execsql2-2.23.1/tests/exporters/test_roundtrip_markup.py +144 -0
- execsql2-2.23.1/tests/exporters/test_roundtrip_ods.py +131 -0
- execsql2-2.23.1/tests/exporters/test_roundtrip_pretty_raw.py +114 -0
- execsql2-2.23.1/tests/exporters/test_roundtrip_template.py +98 -0
- execsql2-2.23.1/tests/exporters/test_roundtrip_values.py +141 -0
- execsql2-2.23.1/tests/exporters/test_roundtrip_xlsx.py +103 -0
- execsql2-2.23.1/tests/importers/conftest.py +127 -0
- execsql2-2.23.1/tests/importers/test_roundtrip_csv.py +152 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/integration/test_postgres.py +69 -0
- execsql2-2.23.1/tests/live_db.py +108 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/metacommands/test_metacommands_data.py +23 -19
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/metacommands/test_metacommands_extended.py +20 -14
- execsql2-2.23.1/tests/test_api_filewriter.py +158 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/uv.lock +1 -1
- execsql2-2.22.9/src/execsql/db/duckdb.py +0 -90
- {execsql2-2.22.9 → execsql2-2.23.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/.github/dependabot.yml +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/.pre-commit-config.yaml +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/.pre-commit-hooks.yaml +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/.python-version +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/.readthedocs.yaml +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/CONTRIBUTING.md +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/LICENSE.txt +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/NOTICE +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/SECURITY.md +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/about/contributors.md +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/about/copyright.md +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/api/cli.md +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/api/db.md +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/api/exporters.md +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/api/importers.md +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/api/metacommands.md +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/dev/adding_db_adapters.md +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/dev/adding_exporters.md +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/dev/adding_importers.md +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/dev/adding_metacommands.md +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/dev/architecture.md +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/dev/releasing.md +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/getting-started/installation.md +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/getting-started/syntax.md +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/guides/debugging.md +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/guides/documentation.md +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/guides/examples.md +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/guides/logging.md +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/guides/sql_syntax.md +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/guides/usage.md +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/guides/using_scripts.md +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/images/Compare_planets.png +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/images/actions.png +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/images/actions2.png +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/images/checkboxes.png +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/images/connect.b64 +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/images/connect.png +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/images/create_conf.png +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/images/data_error1_screenshot.jpg +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/images/entry_form.png +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/images/execsql_console.png +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/images/execsql_logo_01.png +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/images/fatals.png +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/images/logo_small.png +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/images/pause_terminal.png +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/images/pause_terminal_sm.b64 +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/images/pause_terminal_sm.png +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/images/prompt_compare.png +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/images/set_build_commands.jpg +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/images/unit_conversions.b64 +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/images/unit_conversions_029.png +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/images/unmatched.png +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/images/vim_execsql_highlight.png +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/index.md +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/docs/reference/security.md +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/extras/plugin-template/README.md +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/extras/plugin-template/pyproject.toml +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/extras/plugin-template/src/execsql_plugin_YOURNAME/__init__.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/extras/plugin-template/tests/test_plugin.py.example +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/extras/vscode-execsql/README.md +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/extras/vscode-execsql/package.json +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/extras/vscode-execsql/syntaxes/execsql.tmLanguage.json +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/justfile +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/scripts/benchmarks/README.md +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/scripts/benchmarks/results/.gitignore +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/scripts/benchmarks/run_benchmarks.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/scripts/generate_vscode_grammar.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/__init__.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/__main__.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/cli/__init__.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/cli/dsn.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/cli/help.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/cli/lint.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/cli/run.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/data/__init__.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/db/__init__.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/db/factory.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/debug/__init__.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/debug/repl.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/exceptions.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/exporters/__init__.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/exporters/base.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/exporters/duckdb.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/exporters/feather.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/exporters/json.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/exporters/parquet.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/exporters/protocol.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/exporters/raw.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/exporters/sqlite.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/exporters/xls.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/exporters/xml.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/exporters/yaml.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/exporters/zip.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/format.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/gui/__init__.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/gui/base.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/gui/console.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/gui/desktop.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/gui/tui.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/importers/__init__.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/importers/base.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/importers/csv.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/importers/feather.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/importers/json.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/importers/ods.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/importers/xls.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/metacommands/__init__.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/metacommands/conditions.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/metacommands/connect.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/metacommands/control.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/metacommands/data.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/metacommands/debug.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/metacommands/dispatch.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/metacommands/io.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/metacommands/io_export.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/metacommands/io_fileops.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/metacommands/io_import.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/metacommands/io_write.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/metacommands/prompt.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/metacommands/script_ext.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/metacommands/system.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/metacommands/upsert.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/models.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/parser.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/plugins.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/py.typed +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/script/__init__.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/script/ast.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/script/control.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/script/engine.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/script/executor.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/script/parser.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/script/variables.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/utils/__init__.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/utils/auth.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/utils/crypto.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/utils/datetime.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/utils/errors.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/utils/gui.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/utils/mail.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/utils/numeric.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/utils/regex.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/utils/strings.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/src/execsql/utils/timer.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/templates/README.md +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/templates/config_settings.sqlite +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/templates/example_config_prompt.sql +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/templates/make_config_db.sql +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/templates/md_compare.sql +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/templates/md_glossary.sql +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/templates/md_upsert.sql +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/templates/pg_compare.sql +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/templates/pg_glossary.sql +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/templates/pg_upsert.sql +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/templates/script_template.sql +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/templates/ss_compare.sql +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/templates/ss_glossary.sql +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/templates/ss_upsert.sql +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/__init__.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/cli/__init__.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/cli/test_cli.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/cli/test_cli_e2e.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/cli/test_cli_run.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/cli/test_lint.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/cli/test_ping.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/cli/test_profile.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/conftest.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/data/formatter_shapes/comment_anchoring.sql +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/db/__init__.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/db/test_access_windows.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/db/test_base.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/db/test_db_adapters_mocked.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/db/test_db_adapters_mocked_extra.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/db/test_dsn.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/db/test_duckdb.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/db/test_factory.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/db/test_mysql_case_folding.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/db/test_mysql_inprocess.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/db/test_postgres.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/db/test_sqlite.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/db/test_sqlite_extra.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/db/test_sqlserver_inprocess.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/exporters/__init__.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/exporters/test_base.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/exporters/test_db.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/exporters/test_delimited.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/exporters/test_duckdb_exporter.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/exporters/test_exporters.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/exporters/test_feather.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/exporters/test_html_extended.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/exporters/test_html_latex.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/exporters/test_json.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/exporters/test_json_extended.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/exporters/test_latex_extended.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/exporters/test_markdown.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/exporters/test_ods.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/exporters/test_ods_export.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/exporters/test_parquet.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/exporters/test_pretty_extended.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/exporters/test_raw_extended.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/exporters/test_sqlite_exporter.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/exporters/test_templates.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/exporters/test_templates_extended.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/exporters/test_values_extended.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/exporters/test_xls_xlsx.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/exporters/test_xlsx.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/exporters/test_xml.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/exporters/test_yaml.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/exporters/test_zip.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/gui/__init__.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/gui/test_backends.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/gui/test_backends_extended.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/gui/test_compare_stats.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/gui/test_compute_row_diffs.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/gui/test_desktop_dialogs.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/gui/test_format_table.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/gui/test_tui_pilot.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/gui/test_tui_pilot_complex.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/gui/test_utils_gui_extended.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/importers/__init__.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/importers/test_base_extended.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/importers/test_csv_edge_cases.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/importers/test_csv_importer.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/importers/test_feather_importer.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/importers/test_json_importer.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/importers/test_ods_importer.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/importers/test_xls_importer.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/integration/__init__.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/integration/conftest.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/integration/test_dsn.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/integration/test_duckdb.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/integration/test_mysql.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/integration/test_sqlite.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/metacommands/__init__.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/metacommands/test_assert.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/metacommands/test_breakpoint.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/metacommands/test_conditions_extra.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/metacommands/test_connect.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/metacommands/test_io_export.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/metacommands/test_io_import.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/metacommands/test_metacommands.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/metacommands/test_metacommands_connect.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/metacommands/test_metacommands_fileops_extra.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/metacommands/test_metacommands_io.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/metacommands/test_metacommands_io_write_extra.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/metacommands/test_metacommands_script_ext.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/metacommands/test_metacommands_system.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/metacommands/test_metacommands_system_extra.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/metacommands/test_pg_upsert.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/metacommands/test_prompt.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/metacommands/test_row_count.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/metacommands/test_show_scripts.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/scripts/__init__.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/scripts/fixtures/audit_lint_bad.sql +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/scripts/fixtures/audit_smoke/sample.json +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/scripts/fixtures/audit_smoke/sample.jsonl +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/scripts/fixtures/audit_smoke.sql +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/scripts/fixtures/config_runtime.sql +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/scripts/fixtures/control_flow.sql +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/scripts/fixtures/counters_and_locals.sql +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/scripts/fixtures/error_handling.sql +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/scripts/fixtures/import_options/has_comments.csv +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/scripts/fixtures/import_options.sql +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/scripts/fixtures/includes/helper.sql +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/scripts/fixtures/includes.sql +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/scripts/fixtures/io_formats.sql +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/scripts/fixtures/io_roundtrip.sql +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/scripts/fixtures/multi_db.sql +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/scripts/fixtures/parquet_feather_roundtrip.sql +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/scripts/fixtures/parse_only/parse_tree.sql +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/scripts/fixtures/predicates.sql +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/scripts/fixtures/scripts.sql +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/scripts/fixtures/smoke.sql +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/scripts/fixtures/subroutine_loops.sql +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/scripts/fixtures/subvars_advanced.sql +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/scripts/fixtures/template_export/greeting.tmpl +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/scripts/fixtures/template_export.sql +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/scripts/fixtures/timer.sql +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/scripts/fixtures/transactions.sql +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/scripts/fixtures/write_create_table.sql +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/scripts/fixtures/xlsx_ods_roundtrip.sql +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/scripts/test_sql_scripts.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/security/__init__.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/security/test_env_detection.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/security/test_expansion_bomb.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/security/test_import_regex_hardening.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/security/test_log_redaction.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/security/test_odbc_injection.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/security/test_path_containment.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/security/test_substitution_injection.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/security/test_zip_bomb.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/test_api.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/test_api_extended.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/test_ast.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/test_ast_parser.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/test_bundled_templates.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/test_config.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/test_config_data.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/test_config_extended.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/test_debug_repl.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/test_engine.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/test_error_messages.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/test_exceptions.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/test_executor.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/test_executor_inprocess.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/test_format.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/test_mail.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/test_models.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/test_package.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/test_parser.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/test_parser_params.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/test_plugins.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/test_registry.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/test_script.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/test_state.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/test_types.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/utils/__init__.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/utils/test_auth.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/utils/test_auth_extra.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/utils/test_crypto.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/utils/test_datetime.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/utils/test_encodedfile_context.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/utils/test_errors.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/utils/test_errors_extra.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/utils/test_fileio.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/utils/test_fileio_extra.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/utils/test_numeric.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/utils/test_regex.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/utils/test_strings.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/utils/test_timer.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/tests/utils/test_timer_extra.py +0 -0
- {execsql2-2.22.9 → execsql2-2.23.1}/zensical.toml +0 -0
|
@@ -20,7 +20,7 @@ jobs:
|
|
|
20
20
|
permissions:
|
|
21
21
|
contents: read
|
|
22
22
|
steps:
|
|
23
|
-
- uses: actions/checkout@
|
|
23
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
24
24
|
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
25
25
|
with:
|
|
26
26
|
python-version: "3.13"
|
|
@@ -34,7 +34,7 @@ jobs:
|
|
|
34
34
|
permissions:
|
|
35
35
|
contents: read
|
|
36
36
|
steps:
|
|
37
|
-
- uses: actions/checkout@
|
|
37
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
38
38
|
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
39
39
|
with:
|
|
40
40
|
python-version: "3.13"
|
|
@@ -49,7 +49,7 @@ jobs:
|
|
|
49
49
|
permissions:
|
|
50
50
|
contents: read
|
|
51
51
|
steps:
|
|
52
|
-
- uses: actions/checkout@
|
|
52
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
53
53
|
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
54
54
|
with:
|
|
55
55
|
python-version: "3.13"
|
|
@@ -67,7 +67,7 @@ jobs:
|
|
|
67
67
|
permissions:
|
|
68
68
|
contents: read
|
|
69
69
|
steps:
|
|
70
|
-
- uses: actions/checkout@
|
|
70
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
71
71
|
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
72
72
|
with:
|
|
73
73
|
python-version: "3.13"
|
|
@@ -97,13 +97,13 @@ jobs:
|
|
|
97
97
|
contents: read
|
|
98
98
|
steps:
|
|
99
99
|
- name: Check out repository code
|
|
100
|
-
uses: actions/checkout@
|
|
100
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
101
101
|
- name: Setup Python ${{ matrix.python-version }}
|
|
102
102
|
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
103
103
|
with:
|
|
104
104
|
python-version: ${{ matrix.python-version }}
|
|
105
105
|
- name: Cache pip dependencies
|
|
106
|
-
uses: actions/cache@
|
|
106
|
+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
107
107
|
with:
|
|
108
108
|
path: ~/.cache/pip
|
|
109
109
|
key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{
|
|
@@ -135,13 +135,13 @@ jobs:
|
|
|
135
135
|
tox -e py
|
|
136
136
|
- name: Upload coverage to Codecov
|
|
137
137
|
if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.13'
|
|
138
|
-
uses: codecov/codecov-action@
|
|
138
|
+
uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1
|
|
139
139
|
with:
|
|
140
140
|
token: ${{ secrets.CODECOV_TOKEN }}
|
|
141
141
|
files: coverage.xml
|
|
142
142
|
|
|
143
143
|
integration-tests:
|
|
144
|
-
name:
|
|
144
|
+
name: tier1-live-db-tests-py${{ matrix.python-version }}
|
|
145
145
|
runs-on: ubuntu-latest
|
|
146
146
|
strategy:
|
|
147
147
|
fail-fast: false
|
|
@@ -188,7 +188,7 @@ jobs:
|
|
|
188
188
|
--health-interval 10s --health-timeout 5s --health-retries 20
|
|
189
189
|
steps:
|
|
190
190
|
- name: Check out repository code
|
|
191
|
-
uses: actions/checkout@
|
|
191
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
192
192
|
- name: Setup Python ${{ matrix.python-version }}
|
|
193
193
|
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
194
194
|
with:
|
|
@@ -203,6 +203,13 @@ jobs:
|
|
|
203
203
|
sudo ACCEPT_EULA=Y apt-get install -y msodbcsql18 unixodbc-dev
|
|
204
204
|
odbcinst -j
|
|
205
205
|
odbcinst -q -d
|
|
206
|
+
- name: Enable MySQL LOAD DATA LOCAL INFILE
|
|
207
|
+
# The MySQL importer uses LOAD DATA LOCAL INFILE, which MySQL 8
|
|
208
|
+
# disables server-side by default. A service container takes no
|
|
209
|
+
# command line, so it is switched on here; without it the import
|
|
210
|
+
# round-trip tests skip instead of running.
|
|
211
|
+
run: |
|
|
212
|
+
mysql -h 127.0.0.1 -P 3306 -u root -proot -e "SET GLOBAL local_infile = 1;"
|
|
206
213
|
- name: Install dependencies
|
|
207
214
|
run: |
|
|
208
215
|
python -m pip install --upgrade pip
|
|
@@ -216,13 +223,13 @@ jobs:
|
|
|
216
223
|
run: |
|
|
217
224
|
python -m pytest --cov=execsql --cov-branch --cov-report=xml --cov-fail-under=86
|
|
218
225
|
- name: Upload coverage to Codecov
|
|
219
|
-
uses: codecov/codecov-action@
|
|
226
|
+
uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1
|
|
220
227
|
with:
|
|
221
228
|
token: ${{ secrets.CODECOV_TOKEN }}
|
|
222
229
|
files: coverage.xml
|
|
223
230
|
|
|
224
231
|
access-tests-windows:
|
|
225
|
-
name: access-tests-windows
|
|
232
|
+
name: tier2-access-tests-windows
|
|
226
233
|
runs-on: windows-latest
|
|
227
234
|
# The Access engine install step is best-effort (the redistributable
|
|
228
235
|
# download from Microsoft can be flaky on hosted runners). The
|
|
@@ -236,7 +243,7 @@ jobs:
|
|
|
236
243
|
permissions:
|
|
237
244
|
contents: read
|
|
238
245
|
steps:
|
|
239
|
-
- uses: actions/checkout@
|
|
246
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
240
247
|
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
241
248
|
with:
|
|
242
249
|
python-version: ${{ matrix.python-version }}
|
|
@@ -288,7 +295,7 @@ jobs:
|
|
|
288
295
|
permissions:
|
|
289
296
|
contents: read
|
|
290
297
|
steps:
|
|
291
|
-
- uses: actions/checkout@
|
|
298
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
292
299
|
- name: Set up Python
|
|
293
300
|
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
294
301
|
with:
|
|
@@ -343,14 +350,14 @@ jobs:
|
|
|
343
350
|
contents: write
|
|
344
351
|
steps:
|
|
345
352
|
- name: Checkout
|
|
346
|
-
uses: actions/checkout@
|
|
353
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
347
354
|
- name: Download dist
|
|
348
355
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
349
356
|
with:
|
|
350
357
|
name: ${{ github.event.repository.name }}
|
|
351
358
|
path: dist/
|
|
352
359
|
- name: Create release
|
|
353
|
-
uses: softprops/action-gh-release@
|
|
360
|
+
uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3
|
|
354
361
|
with:
|
|
355
362
|
draft: false
|
|
356
363
|
prerelease: false
|
|
@@ -13,6 +13,56 @@ ______________________________________________________________________
|
|
|
13
13
|
|
|
14
14
|
______________________________________________________________________
|
|
15
15
|
|
|
16
|
+
## [2.23.1] - 2026-09-29
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- A warning when MySQL, MariaDB or Oracle commits a statement on its own while `AUTOCOMMIT` is `OFF` or a batch is open — `CREATE TABLE`, `DROP`, `TRUNCATE` and other DDL, which the server commits together with everything before it, so none of it can be rolled back.
|
|
21
|
+
- A warning when `AUTOCOMMIT OFF` or `BEGIN BATCH` cannot hold statements at all: an ODBC DSN whose driver has no transactions, or an MS Access `CREATE TEMPORARY QUERY`, which reopens the connection and rolls back what was not yet committed.
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- SQLite: `CREATE TABLE`, `DROP` and other DDL run while `AUTOCOMMIT` is `OFF` or inside `BEGIN BATCH` are held until commit. They were committed at once, so `AUTOCOMMIT ON WITH ROLLBACK` and `ROLLBACK BATCH` left them in place.
|
|
26
|
+
- DuckDB: `AUTOCOMMIT OFF` and `BEGIN BATCH` hold statements until commit. Every statement, rows included, was committed as it ran, so nothing could be rolled back. A script's own `BEGIN;` and `COMMIT;` now work too; `COMMIT;` failed with "no transaction is active".
|
|
27
|
+
- DuckDB: `$LAST_ROWCOUNT` holds the number of rows an `INSERT`, `UPDATE` or `DELETE` changed. It was always `-1`.
|
|
28
|
+
|
|
29
|
+
______________________________________________________________________
|
|
30
|
+
|
|
31
|
+
## [2.23.0] - 2026-09-29
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
- Database support tiers. PostgreSQL, MySQL/MariaDB, MS SQL Server, SQLite, and DuckDB are **supported** — verified against a live server or real database file on every CI run. MS Access, Firebird, Oracle, and ODBC DSN are **best effort** — present, unverified in CI, and may break. No adapter changes behavior or is scheduled for removal.
|
|
36
|
+
- `support_tier_notice` configuration option (`[interface]` section, default `Yes`). Opening a best-effort database connection writes one line to stderr naming the tier, once per DBMS per run. Set it to `No` to silence.
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
|
|
40
|
+
- MySQL and MariaDB connections now default to `utf8mb4` instead of `latin1`. A latin1 connection could not carry CJK, emoji, Greek, or Cyrillic at all — inserting such text raised `UnicodeEncodeError` before reaching the server — so scripts had to pass `-e utf8mb4` to move most of Unicode. Existing latin1 databases are unaffected: the connection charset governs only client/server transfer, and MySQL transcodes to and from each column's own charset. Pass `-e latin1` to restore the previous behavior.
|
|
41
|
+
|
|
42
|
+
### Fixed
|
|
43
|
+
|
|
44
|
+
- `execsql.api.run()` no longer discards every `WRITE ... TO <file>` and `TEE` to a file. Those metacommands hand their output to a FileWriter subprocess that only the CLI started, so under the library API the file was never created and the run still reported success ([#46](https://github.com/geocoug/execsql/issues/46)). `run()` now starts the writer, and flushes and closes every file before returning, so output is readable as soon as it hands back control. A writer the caller started themselves is left untouched.
|
|
45
|
+
- File output that cannot be written because no FileWriter is running now reports a warning instead of being dropped in silence. The write is still skipped — queueing to a subprocess that is not draining the queue deadlocks the caller — but a script's logfile no longer comes back empty with no indication anything was missed.
|
|
46
|
+
- `IMPORT` reads CSV files containing a quoted field with a newline. RFC 4180 allows it and execsql's own CSV export produces it, but format diagnosis scanned physical lines, so the delimiter looked inconsistent and was rejected: the header arrived as a single column and the record split into two rows with the quote characters still in the data. Exporting a multi-line value and importing it back now round-trips.
|
|
47
|
+
- `IMPORT` to MySQL writes an empty field as NULL instead of the column's zero value. `LOAD DATA` coerced an empty numeric to `0` and an empty date to `0000-00-00`, so the same file imported to MySQL differed from every other backend.
|
|
48
|
+
- `IMPORT ... WITH QUOTE NONE` to PostgreSQL keeps double quotes in the data. `Well "A" 12` was stored as `Well A 12`, and the same happened when no quote character was detected ([#54](https://github.com/geocoug/execsql/issues/54)).
|
|
49
|
+
- `IMPORT` to PostgreSQL honors a detected backslash escape. A quoted field containing `\"` was stored with the backslash and without the quote, so `"Well \"A\" 12"` became `Well \A\ 12`.
|
|
50
|
+
- `IMPORT` to MySQL reads CRLF files correctly. `LOAD DATA` defaults to `\n` as its line terminator, so every row's last field kept a trailing carriage return — enough to turn an empty date column into `0000-00-00`.
|
|
51
|
+
- `EXPORT ... AS CSV` (and the other delimited formats) no longer corrupts values containing a newline when run on Windows. Universal-newline translation rewrote every `\n` as `\r\n` on write, including newlines *inside* a quoted field, so an exported value did not match the one in the database. Delimited files now use `\n` row terminators on every platform, so the same query produces the same bytes everywhere.
|
|
52
|
+
- `EXPORT ... AS TAB`/console pretty-printing keeps every row on one line. A newline or tab inside a value split the row across physical lines and misaligned every column after it; both are now shown as `\\n` and `\\t`, so the table stays square and the break is still visible.
|
|
53
|
+
- `EXPORT ... AS VALUES` quotes dates, timestamps, and every other non-numeric value in the generated `INSERT`. Only strings were quoted, so a date was written bare as `2026-09-24` — which SQL reads as arithmetic, not a date. PostgreSQL accepted the statement and stored `1993` without complaint; a timestamp, carrying a space, was a syntax error instead. Numbers are still written bare, and `NULL` is unchanged.
|
|
54
|
+
- `EXPORT ... AS LATEX` escapes every LaTeX special character, not only `_`. An `&` in a value acted as a column separator and produced a table with the wrong number of columns, which LaTeX refuses to compile ("Extra alignment tab has been changed to \\cr"); a `%` commented out the rest of the row. `\\ & % $ # _ { } ~ ^` are now all escaped.
|
|
55
|
+
- `EXPORT ... AS LATEX` writes NULL as an empty cell instead of the literal text `None`.
|
|
56
|
+
- `EXPORT ... AS HTML` renders zero and other falsy values instead of blank cells. Cells were written with `str(v) if v else ''`, so `0`, `False`, and empty strings came out empty — a zero measurement was indistinguishable from a NULL in the output.
|
|
57
|
+
- `EXPORT ... AS MARKDOWN` no longer breaks the table when a value contains a newline. A GitHub-flavoured Markdown row is a single line, so a literal newline ended the row mid-cell and left the remaining values on a stray line outside the table. Line breaks are now written as `<br>`.
|
|
58
|
+
- `EXPORT ... AS ODS` writes `numeric`/`decimal` columns as typed numbers. PostgreSQL, MySQL, and DuckDB return `Decimal` for those columns, which was written with an `office:value` but no `office:value-type` — not valid ODF, leaving a reader free to treat the number as text or ignore it.
|
|
59
|
+
- `EXPORT ... AS ODS` keeps newlines inside a value instead of replacing them with spaces. Multi-line text is now written as one line per `<text:p>`, which is how ODF represents it; previously the exported cell did not match the value in the database.
|
|
60
|
+
- `EXPORT ... AS XLSX` writes `numeric`/`decimal` columns as numbers instead of text. PostgreSQL, MySQL, and DuckDB return `Decimal` for those columns, which fell through to a string cell — a measurement column arrived in Excel as text and could not be summed, sorted, or charted. Integer and float columns were unaffected.
|
|
61
|
+
- Exports and copies to DuckDB no longer lose floating-point precision. `DT_Float` is IEEE double precision and a Python float always is one, but DuckDB columns were declared `REAL` — single precision — so `1234.567` came back as `1234.5670166015625`. DuckDB columns are now `DOUBLE`, matching every other backend. Affects `EXPORT ... AS DUCKDB` and `COPY` into a DuckDB database.
|
|
62
|
+
- Jinja2 report templates with an `.html`, `.htm`, or `.xml` extension now escape the values rendered into them. Autoescaping was off, so a database value containing markup — `<script>`, `&`, a quote — became live markup in the generated report. Templates with any other extension (`.csv`, `.tex`, `.txt`, `.sql`, `.md`) are unchanged, since escaping those would corrupt them. A template that means to emit markup uses `{{ value|safe }}`.
|
|
63
|
+
|
|
64
|
+
______________________________________________________________________
|
|
65
|
+
|
|
16
66
|
## [2.22.9] - 2026-09-24
|
|
17
67
|
|
|
18
68
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: execsql2
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.23.1
|
|
4
4
|
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.
|
|
5
5
|
Project-URL: Homepage, https://execsql2.readthedocs.io
|
|
6
6
|
Project-URL: Repository, https://github.com/geocoug/execsql
|
|
@@ -224,17 +224,26 @@ execsql script.sql # read connection from config file
|
|
|
224
224
|
|
|
225
225
|
### Supported Databases
|
|
226
226
|
|
|
227
|
-
| Flag | Database |
|
|
228
|
-
| ---- | --------------- |
|
|
229
|
-
| `p` | PostgreSQL |
|
|
230
|
-
| `m` | MySQL / MariaDB |
|
|
231
|
-
| `s` | MS SQL Server |
|
|
232
|
-
| `l` | SQLite |
|
|
233
|
-
| `k` | DuckDB |
|
|
234
|
-
| `a` | MS Access |
|
|
235
|
-
| `f` | Firebird |
|
|
236
|
-
| `o` | Oracle |
|
|
237
|
-
| `d` | ODBC DSN |
|
|
227
|
+
| Flag | Database | Support |
|
|
228
|
+
| ---- | --------------- | ------------------------------------------------ |
|
|
229
|
+
| `p` | PostgreSQL | Supported — verified against a live server in CI |
|
|
230
|
+
| `m` | MySQL / MariaDB | Supported — verified against a live server in CI |
|
|
231
|
+
| `s` | MS SQL Server | Supported — verified against a live server in CI |
|
|
232
|
+
| `l` | SQLite | Supported — verified against real files in CI |
|
|
233
|
+
| `k` | DuckDB | Supported — verified against real files in CI |
|
|
234
|
+
| `a` | MS Access | Best effort — not verified in CI |
|
|
235
|
+
| `f` | Firebird | Best effort — not verified in CI |
|
|
236
|
+
| `o` | Oracle | Best effort — not verified in CI |
|
|
237
|
+
| `d` | ODBC DSN | Best effort — not verified in CI |
|
|
238
|
+
|
|
239
|
+
**Supported** adapters run against a live server or a real database file on
|
|
240
|
+
every CI run; a regression blocks the release and bugs get fixed. **Best
|
|
241
|
+
effort** adapters are carried forward from the upstream monolith and are not
|
|
242
|
+
exercised anywhere in CI — the code is there and may work, but nothing proves
|
|
243
|
+
it still does. Issues and pull requests are welcome for them; no guarantee is
|
|
244
|
+
made. Opening a best-effort connection prints one informational line, which
|
|
245
|
+
`support_tier_notice=No` in the `[interface]` section of `execsql.conf`
|
|
246
|
+
silences.
|
|
238
247
|
|
|
239
248
|
### Common options
|
|
240
249
|
|
|
@@ -384,7 +393,7 @@ execsql-format --no-sql --in-place scripts/
|
|
|
384
393
|
```yaml
|
|
385
394
|
repos:
|
|
386
395
|
- repo: https://github.com/geocoug/execsql
|
|
387
|
-
rev: v2.
|
|
396
|
+
rev: v2.23.1
|
|
388
397
|
hooks:
|
|
389
398
|
- id: execsql-format
|
|
390
399
|
```
|
|
@@ -93,17 +93,26 @@ execsql script.sql # read connection from config file
|
|
|
93
93
|
|
|
94
94
|
### Supported Databases
|
|
95
95
|
|
|
96
|
-
| Flag | Database |
|
|
97
|
-
| ---- | --------------- |
|
|
98
|
-
| `p` | PostgreSQL |
|
|
99
|
-
| `m` | MySQL / MariaDB |
|
|
100
|
-
| `s` | MS SQL Server |
|
|
101
|
-
| `l` | SQLite |
|
|
102
|
-
| `k` | DuckDB |
|
|
103
|
-
| `a` | MS Access |
|
|
104
|
-
| `f` | Firebird |
|
|
105
|
-
| `o` | Oracle |
|
|
106
|
-
| `d` | ODBC DSN |
|
|
96
|
+
| Flag | Database | Support |
|
|
97
|
+
| ---- | --------------- | ------------------------------------------------ |
|
|
98
|
+
| `p` | PostgreSQL | Supported — verified against a live server in CI |
|
|
99
|
+
| `m` | MySQL / MariaDB | Supported — verified against a live server in CI |
|
|
100
|
+
| `s` | MS SQL Server | Supported — verified against a live server in CI |
|
|
101
|
+
| `l` | SQLite | Supported — verified against real files in CI |
|
|
102
|
+
| `k` | DuckDB | Supported — verified against real files in CI |
|
|
103
|
+
| `a` | MS Access | Best effort — not verified in CI |
|
|
104
|
+
| `f` | Firebird | Best effort — not verified in CI |
|
|
105
|
+
| `o` | Oracle | Best effort — not verified in CI |
|
|
106
|
+
| `d` | ODBC DSN | Best effort — not verified in CI |
|
|
107
|
+
|
|
108
|
+
**Supported** adapters run against a live server or a real database file on
|
|
109
|
+
every CI run; a regression blocks the release and bugs get fixed. **Best
|
|
110
|
+
effort** adapters are carried forward from the upstream monolith and are not
|
|
111
|
+
exercised anywhere in CI — the code is there and may work, but nothing proves
|
|
112
|
+
it still does. Issues and pull requests are welcome for them; no guarantee is
|
|
113
|
+
made. Opening a best-effort connection prints one informational line, which
|
|
114
|
+
`support_tier_notice=No` in the `[interface]` section of `execsql.conf`
|
|
115
|
+
silences.
|
|
107
116
|
|
|
108
117
|
### Common options
|
|
109
118
|
|
|
@@ -253,7 +262,7 @@ execsql-format --no-sql --in-place scripts/
|
|
|
253
262
|
```yaml
|
|
254
263
|
repos:
|
|
255
264
|
- repo: https://github.com/geocoug/execsql
|
|
256
|
-
rev: v2.
|
|
265
|
+
rev: v2.23.1
|
|
257
266
|
hooks:
|
|
258
267
|
- id: execsql-format
|
|
259
268
|
```
|
|
@@ -184,6 +184,18 @@ The CLI framework changed from `optparse` to [Typer](https://typer.tiangolo.com/
|
|
|
184
184
|
|
|
185
185
|
Seven upstream long-form flags were renamed underscore → hyphen and the underscore forms are **not** accepted: `--database-encoding`, `--script-encoding`, `--output-encoding`, `--import-encoding`, `--import-buffer`, `--user-logfile`, `--visible-prompts` (upstream wrote these with underscores). Scripts and CI pipelines that invoke the long-form flags must update the spelling; the short letters (`-e`, `-f`, `-g`, `-i`, `-l`, `-v`, `-z`) are unchanged.
|
|
186
186
|
|
|
187
|
+
### MySQL Connection Encoding
|
|
188
|
+
|
|
189
|
+
Upstream opened MySQL and MariaDB connections as `latin1`, and execsql2 did the same until this was measured against a live server. A latin1 connection cannot carry CJK, emoji, Greek, or Cyrillic — pymysql raises `UnicodeEncodeError` before the server is reached — so any script moving text outside Latin-1 had to pass `-e utf8mb4` explicitly or fail.
|
|
190
|
+
|
|
191
|
+
The default is now `utf8mb4`. This does not change stored data or column charsets: the connection charset governs only how text moves between client and server, and MySQL transcodes to and from each column's own charset. A latin1 column read over a utf8mb4 connection returns the same string it always did. `-e latin1` restores the upstream behavior.
|
|
192
|
+
|
|
193
|
+
### Database Support Tiers
|
|
194
|
+
|
|
195
|
+
Upstream documented all nine DBMS adapters with equal confidence. execsql2 splits them by what is actually verified: **Supported** (PostgreSQL, MySQL/MariaDB, MS SQL Server, SQLite, DuckDB) run against a live server or a real database file on every CI run, while **best effort** (MS Access, Firebird, Oracle, ODBC DSN) are carried forward from the monolith and are exercised nowhere in CI.
|
|
196
|
+
|
|
197
|
+
No adapter is removed, deprecated, or changed in behavior — the tiers describe verification, not intent. Opening a best-effort connection writes one informational line to stderr per DBMS per run, which `support_tier_notice=No` in the `[interface]` section silences. Adapters declare their own tier through `Database.support_tier`, and `tests/db/test_tiers.py` fails if the code and the documented tables disagree.
|
|
198
|
+
|
|
187
199
|
### Default Database Type
|
|
188
200
|
|
|
189
201
|
The default database type (`-t`) changed from Access (`a`) to SQLite (`l`). Upstream defaulted to Access, which requires Windows and pyodbc. SQLite is cross-platform, ships with Python, and is the most common use case. Users targeting Access databases should pass `-t a` explicitly.
|
|
@@ -216,7 +228,9 @@ All 33 mutable runtime globals in `state.py` have been consolidated into a `Runt
|
|
|
216
228
|
- **Connection timeouts** — PostgreSQL and SQLite adapters accept a connection timeout parameter (default 30 seconds).
|
|
217
229
|
- **DuckDB temporal types** — `TIMESTAMPTZ`, `TIMESTAMP`, `DATE`, `TIME` now map to native DuckDB types instead of `TEXT`.
|
|
218
230
|
- **SQLite `DT_Long` mapping** — `DT_Long` maps to `"hugeint"` in the SQLite type table. SQLite does not have a native `HUGEINT` type; the value receives `TEXT` affinity. In practice this is harmless because SQLite's type affinity system handles large integers transparently, but the mapping name differs from upstream.
|
|
219
|
-
- **`Database.auto_commits_ddl()` not provided** — 2.18.0 shipped this capability hook on the base `Database` class with `True` overrides on Oracle, MySQL, SQL Server, and MS Access
|
|
231
|
+
- **`Database.auto_commits_ddl()` not provided** — 2.18.0 shipped this capability hook on the base `Database` class with `True` overrides on Oracle, MySQL, SQL Server, and MS Access. No call site ever consumed the hook, so 2.19.0 removed it. Its replacement is `Database.implicit_commit_rx`: MySQL and Oracle name the statements their server commits on its own (DDL), and a warning is written when one runs while `AUTOCOMMIT` is `OFF` or a batch is open. SQL Server holds DDL in a transaction like any other statement. (The companion hook `Database.needs_explicit_commit_after_ddl()`, used by the Firebird import path, remains.)
|
|
232
|
+
- **Statements are held until commit on every adapter** — while `AUTOCOMMIT` is `OFF` or a batch is open, `Database.before_statement()` makes sure each statement is inside a transaction. Most drivers already opened one; SQLite's (Python's `sqlite3`) opened one only before `INSERT`/`UPDATE`/`DELETE`/`REPLACE`, and DuckDB's never did, so upstream committed SQLite DDL, and every DuckDB statement, at once. SQLite now begins a transaction before other statements (except `VACUUM`, `ATTACH`, `DETACH`, `PRAGMA`); DuckDB begins one itself, tracks it, and runs statements on the connection rather than on cursors while it is open, since a DuckDB cursor is a separate connection with its own transaction.
|
|
233
|
+
- **DuckDB `$LAST_ROWCOUNT`** — the duckdb driver's `rowcount` is always `-1`; DuckDB answers an `INSERT`, `UPDATE` or `DELETE` with a one-column `Count` result instead. The DuckDB adapter reads that result, so `$LAST_ROWCOUNT` holds the rows changed, as on the other adapters. Upstream set it to `-1`.
|
|
220
234
|
|
|
221
235
|
### Execution Engine
|
|
222
236
|
|
|
@@ -253,14 +267,15 @@ These are behavioral changes driven by security or correctness issues in the ups
|
|
|
253
267
|
|
|
254
268
|
### Template and Export Safety
|
|
255
269
|
|
|
256
|
-
| Area | Fix
|
|
257
|
-
| ---------------------- |
|
|
258
|
-
| Jinja2
|
|
259
|
-
|
|
|
260
|
-
|
|
|
261
|
-
|
|
|
262
|
-
|
|
|
263
|
-
|
|
|
270
|
+
| Area | Fix |
|
|
271
|
+
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
272
|
+
| Jinja2 reports | `SandboxedEnvironment` was created without `autoescape`, so Jinja2's default (off) applied and a value from the database rendered into an HTML report as live markup — a `<script>` in a text column became a script tag. The sandbox guards against a hostile template; nothing guarded against hostile data. Autoescaping is now selected by the template's extension: `html`, `htm` and `xml` escape, every other extension renders byte-identically to before, and a template that means to emit markup asks with \`{{ value |
|
|
273
|
+
| Jinja2 sandboxing | Templates run in `SandboxedEnvironment` instead of the default `jinja2.Template`. |
|
|
274
|
+
| HTML export | Column headers and cell values are escaped with `html.escape()` to prevent XSS. |
|
|
275
|
+
| XML export | Values are escaped with `xml.sax.saxutils.escape()`. Invalid XML element name characters are replaced. |
|
|
276
|
+
| JSON export | The `description` field and all column names use `json.dumps()` instead of string interpolation. |
|
|
277
|
+
| XLSX zip-bomb defence | `EXPORT … FORMAT xlsx`, `EXPORT … FORMAT xls`, and (as of 2.19.0) `IMPORT … FORMAT xlsx` reject zip-based workbooks whose decompression ratio exceeds 100:1 per member or whose aggregate uncompressed size exceeds 500 MB. Helper at `execsql.utils.fileio.check_zip_decompression_ratio`. Legacy `.xls` (OLE-CDF, not zip) is unaffected — the helper no-ops on it. |
|
|
278
|
+
| ODS XML attack defence | `EXPORT … FORMAT ods` / `IMPORT … FORMAT ods` defuse the stdlib XML parsers via `defusedxml.defuse_stdlib()` on first `OdsFile` construction, protecting odfpy from billion-laughs and external-entity attacks. |
|
|
264
279
|
|
|
265
280
|
### Credential and Logging Safety
|
|
266
281
|
|
|
@@ -299,6 +314,8 @@ These are behavioral changes driven by security or correctness issues in the ups
|
|
|
299
314
|
| `DT_Timestamp` claims time-only values | `dateutil.parser.parse()` silently fills in today's date for bare time strings like `"13:15:45"`, so `DT_Timestamp` matched before `DT_Time` in the inference order. `parse_datetime()` now rejects time-only strings. |
|
|
300
315
|
| `CounterVars.substitute` skipped pos 0–1 | `re.I` was passed as the positional `pos` argument, skipping the first 2 characters of every string during counter variable expansion. Counter variables at the very start of a line were never matched. |
|
|
301
316
|
| `exec_cmd` (SQLite/DuckDB) always raised TypeError | Passed bytes to `curs.execute()` via `.encode()`, which Python 3 `sqlite3`/`duckdb` reject. `EXECUTE PROCEDURE` metacommand was non-functional on these backends. |
|
|
317
|
+
| PostgreSQL `IMPORT` with no quote character | The `COPY` fast path uses COPY's CSV format, whose quote character is `"` unless another is named. With `QUOTE NONE`, or when no quote character was detected, none was named, so `"` was treated as a quote and stripped: `Well "A" 12` was stored as `Well A 12`. Such files now take the row-by-row path, which keeps every character (#54). |
|
|
318
|
+
| PostgreSQL `IMPORT` with a detected escape character | The `COPY` fast path was never told about a detected escape character, so `\"` inside a quoted field was stored as `\`. Such files now take the row-by-row path, which reads the detected format. |
|
|
302
319
|
| MySQL `LOAD DATA INFILE` injection | File path, delimiter, and quotechar were interpolated without escaping. Now single-quotes are escaped consistent with the PostgreSQL COPY path. |
|
|
303
320
|
| Missing server/database name at connect | Connecting to a server-based database without a server or database name now stops with a clear fatal error ("Configured database requires a server name, but none is provided.") instead of crashing inside the driver with a `TypeError`. |
|
|
304
321
|
| Config file chain infinite loop | The `config_file` directive could chain config files without limit. A circular reference (via symlinks or different relative paths) caused an infinite loop at startup. Now capped at 20 files. |
|
|
@@ -325,6 +342,20 @@ These are behavioral changes driven by security or correctness issues in the ups
|
|
|
325
342
|
| Formatter dropped clauses from unparsable SQL | The sqlglot parse ran at `ErrorLevel.IGNORE`, which answers a statement it cannot parse with a partial AST rather than an error — the tokens it failed on are absent, and regenerating from that AST wrote back SQL the author never had. `INSERT ... CROSS JOIN LATERAL (VALUES ...) AS g(a, b) ON CONFLICT (a) DO NOTHING` came back without its `DO NOTHING`, a syntax error that `--in-place` reported as `reformatted`. Every guard passed it: one statement for one semicolon, 88% of the input's alphanumeric characters, and no string literals to round-trip. The guards are quantitative and the loss is qualitative — a dropped `DISTINCT`, `CASCADE`, `DESC`, or `NOT` is smaller still — so the parse now runs at `ErrorLevel.RAISE` and SQL that does not fully parse is emitted exactly as written. |
|
|
326
343
|
| Formatter moved a comment into the middle of an expression | Comments are hidden from sqlglot as `/* marker */ line`, and sqlglot attaches the marker to an AST node. When it then reflows that node across several lines the marker surfaces deep inside the expression — `COALESCE((a.meas_value).undetected, FALSE)` carried it onto the `).undetected, FALSE)` continuation line — so restoring the comment above the marker split the expression around it, and the resulting layout reformatted differently on the next run. Comments whose markers sqlglot dropped entirely (inside a `CASE`) were re-inserted by token matching, which picked continuation lines the same way. Both paths now place the comment above the line where its expression opens, found by walking back to the parenthesis depth the comment sat at in the input. |
|
|
327
344
|
| Formatter left a space before punctuation where a comment had been | Marker runs are replaced by the one space that keeps their neighbours apart, so `AND /* m */ RTRIM(` does not become `ANDRTRIM(`. Where sqlglot placed the run against a `;`, `,`, `)`, or `.` that space was residue rather than a separator: `1 = 1 /* m */;` came back as `1 = 1 ;`, normalised only on the next run, so `--check` failed on output the formatter had just produced. The run is now removed without a space at the ends of a line and against punctuation that binds to the token before it. |
|
|
345
|
+
| Delimited export corrupted embedded newlines on Windows | `EncodedFile.open()` passed `newline=None`, so Python rewrote every `\n` written as `os.linesep`. That is right for row terminators and wrong for data: a newline inside a quoted CSV field was rewritten too, and the value read back was not the value exported. Python's own `csv` documentation calls for `newline=""` for exactly this reason. The delimited writer now opts out of translation, so row terminators are `\n` on every platform and a query produces identical bytes everywhere. Found by exporting a value containing a newline — nothing in the suite had ever done so — which failed on all five Windows runners and none of the others. |
|
|
346
|
+
| CSV import could not read a quoted field containing a newline | `diagnose_delim()` iterated physical lines, so a record split across two of them gave inconsistent delimiter counts and the real delimiter was rejected. The header came back as one column and the record as two rows with the quotes retained. Lines are now joined while a quote is open, so diagnosis sees logical records. RFC 4180 allows the construct, the stdlib reads it, and execsql's own CSV writer produces it — so this was a failure to read back what it had just written. |
|
|
347
|
+
| MySQL import turned empty fields into zero values | `LOAD DATA` coerces an empty field to the column's zero value: an empty numeric became `0` and an empty date `0000-00-00`, while every other backend produced NULL. Columns are now read into user variables and `NULLIF`-ed back. |
|
|
348
|
+
| MySQL import left a carriage return on the last field of each row | `LOAD DATA` defaults to `\n` as its line terminator, so a CRLF file — what Python's `csv` writer produces by default — left `\r` on each row's final value. It was not empty, so the `NULLIF` above could not see it, and a date column became `0000-00-00`. The statement now declares `lines terminated by '\r\n'` when the file is CRLF. |
|
|
349
|
+
| Pretty-printed tables were broken by a newline or tab in a value | The fixed-width writer emitted string values as-is, so a newline ended the row mid-cell — the remaining columns appeared on a line with no table structure — and a tab misaligned everything after it. Both are now rendered as their two-character escapes. This is a display format, so the escape keeps the row on one line, keeps the columns square, and still shows the reader that a break is present. |
|
|
350
|
+
| DuckDB float columns were single precision | `dbt_duckdb` mapped `DT_Float` to `REAL`. `DT_Float` is documented as IEEE double precision and a Python float always is one, so every float written to DuckDB was narrowed: `1234.567` was stored and returned as `1234.5670166015625`. PostgreSQL, SQLite, SQL Server, MySQL, Access, Firebird and DSN all map `DT_Float` to a double; DuckDB was the only backend that did not, and is now `DOUBLE`. |
|
|
351
|
+
| VALUES export wrote dates and timestamps unquoted | The literal renderer quoted `str` and emitted `NULL` for `None`, but sent everything else through `str()` bare. A `date` became `2026-09-24` in the generated `INSERT`, which is not a syntax error — it is a valid SQL expression evaluating to 1993, so PostgreSQL inserted that and reported nothing. A `timestamp` contains a space and failed outright. Quoting is now the default and only numbers are emitted bare, so a type the renderer has not been taught about is safe rather than silently wrong. |
|
|
352
|
+
| LaTeX export escaped only the underscore | Cells were written as `str(c).replace("_", r"\_")`, leaving every other LaTeX special raw. `&` is the column separator this exporter itself emits, so an ampersand in a value added a column and the `.tex` no longer compiled; `%` commented out the rest of the row. All of `\ & % $ # _ { } ~ ^` are now escaped, in both the header and the body. |
|
|
353
|
+
| LaTeX export wrote NULL as the literal "None" | `str(c)` was applied to every value including `None`, so a NULL appeared in the typeset table as the word None. It is now an empty cell, matching every other exporter. |
|
|
354
|
+
| HTML export rendered zero as an empty cell | Cells were written as `html.escape(str(v)) if v else ''`. `0`, `False`, and `""` are all falsy, so a zero measurement rendered exactly like a NULL and the two could not be told apart in the output. The test is now `if v is not None`. Both `export_html` and `export_cgi_html` were affected. |
|
|
355
|
+
| Markdown export broke the table on an embedded newline | `_cell()` escaped pipes but not newlines, and a GFM table row is a single line: a value containing one ended the row mid-cell and left the rest of that row's values on a stray line outside the table entirely. Newlines are now written as `<br>`, which is how GFM expresses a line break inside a cell. |
|
|
356
|
+
| ODS export wrote `numeric` columns with no type | The cell-type dispatch tested \`isinstance(item, float |
|
|
357
|
+
| ODS export replaced newlines inside a value with spaces | String cells were written after `item.replace("\n", " ")`, so an exported value silently differed from the one in the database. An XML parser does normalise a newline inside an attribute, so `office:string-value` cannot carry one — but ODF represents a multi-line cell as one `<text:p>` per line, which is what LibreOffice writes and what execsql2 now writes. Single-line values still use the attribute. |
|
|
358
|
+
| XLSX export wrote `numeric` columns as text | `_cell_value()` passed `int` and `float` straight to openpyxl but let every other type fall through to `str()`. PostgreSQL, MySQL, and DuckDB all return `Decimal` for `numeric`/`decimal` columns, so measurement values arrived in Excel as text cells — invisible in a screenshot, useless in a formula, a sort, or a chart. openpyxl lists `Decimal` in its own `NUMERIC_TYPES` and stores it as a real numeric cell, so the conversion was never needed. Found by exporting from a live PostgreSQL container and checking `cell.data_type` rather than `cell.value`. |
|
|
328
359
|
| `EMAIL` SMTP login without a password | Upstream calls `smtplib.login()` with only a username when `[email] password` and `enc_password` are both unset, which raises `TypeError` from the stdlib — `login()` has no single-argument form. execsql2 reports a configuration error naming the missing password instead. |
|
|
329
360
|
|
|
330
361
|
______________________________________________________________________
|
|
@@ -48,6 +48,23 @@ for t in threads:
|
|
|
48
48
|
|
|
49
49
|
Each thread gets its own database connections, IF/LOOP stacks, substitution variables, and error state. No locking is required.
|
|
50
50
|
|
|
51
|
+
### File Output { #file-output }
|
|
52
|
+
|
|
53
|
+
`WRITE ... TO <file>`, `TEE`, and the other metacommands that write text files hand their output to a separate FileWriter process. `run()` starts one if none is running, and flushes and closes every file before returning — so a file a script wrote is on disk and complete the moment `run()` hands back control.
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from execsql import run
|
|
57
|
+
|
|
58
|
+
result = run(sql='-- !x! WRITE "done" to report.txt\nselect 1;\n', dsn="sqlite:///data.db")
|
|
59
|
+
open("report.txt").read() # "done\n" — readable immediately
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The writer process stays up afterwards and is reused by later `run()` calls, exactly as the CLI keeps one for the life of the process; it is shut down automatically at interpreter exit. A writer you started yourself is left alone — `run()` neither replaces nor stops it.
|
|
63
|
+
|
|
64
|
+
!!! warning "Interactive interpreters"
|
|
65
|
+
|
|
66
|
+
On macOS and Windows, Python starts subprocesses with the `spawn` method, which re-imports the calling program's `__main__` module. That fails in a REPL, a notebook, or `python -c`, so the writer cannot start and file output is discarded — with a warning on stderr rather than in silence. Run scripts that write files from a `.py` file, guarded by `if __name__ == "__main__":`.
|
|
67
|
+
|
|
51
68
|
## Extension Guides
|
|
52
69
|
|
|
53
70
|
| Extension type | Guide | API reference |
|
|
@@ -36,6 +36,32 @@ Multiple extras can be combined: `pip install "execsql2[postgres,duckdb,formats]
|
|
|
36
36
|
|
|
37
37
|
The specific libraries installed by each extra are:
|
|
38
38
|
|
|
39
|
+
### Database support tiers { #support-tiers }
|
|
40
|
+
|
|
41
|
+
execsql2 ships nine DBMS adapters, but they are not all verified to the same
|
|
42
|
+
standard. The tier tells you which ones have actually been run.
|
|
43
|
+
|
|
44
|
+
| Tier | Databases | What it means |
|
|
45
|
+
| --------------- | -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
46
|
+
| **Supported** | PostgreSQL, MySQL/MariaDB, MS SQL Server, SQLite, DuckDB | Exercised against a live server (or a real database file) on every CI run. A regression blocks the release, and bugs are fixed. |
|
|
47
|
+
| **Best effort** | MS Access, Firebird, Oracle, ODBC DSN | Carried forward from the upstream monolith and not exercised in CI. The code is present and may work; nothing proves it still does. Issues and pull requests are welcome, but no guarantee is made. |
|
|
48
|
+
|
|
49
|
+
Opening a best-effort connection writes one informational line to stderr, once
|
|
50
|
+
per DBMS per run:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
Note: Firebird support is best-effort — it is not verified in CI and may break.
|
|
54
|
+
Set support_tier_notice=No in the [interface] section of execsql.conf to silence this.
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
It goes to stderr rather than stdout, so it cannot corrupt piped query output.
|
|
58
|
+
To turn it off, set [`support_tier_notice`](../reference/configuration.md#support_tier_notice)
|
|
59
|
+
to `No`.
|
|
60
|
+
|
|
61
|
+
No adapter is deprecated or scheduled for removal. The tiers describe what is
|
|
62
|
+
verified, not what is wanted — a best-effort adapter moves up when it gains
|
|
63
|
+
tests that run against something real.
|
|
64
|
+
|
|
39
65
|
### Database drivers
|
|
40
66
|
|
|
41
67
|
| Database / Format | Extra | Library |
|
|
@@ -233,6 +233,10 @@ The section and property names that may be used in a configuration file are list
|
|
|
233
233
|
|
|
234
234
|
: Determines whether warning messages are written to the console as well as to the log file. The default value is "No", indicating that warnings will not be written to the console. If it is set to "Yes", warnings will be written to the console.
|
|
235
235
|
|
|
236
|
+
`support_tier_notice` { #support_tier_notice }
|
|
237
|
+
|
|
238
|
+
: Determines whether opening a connection to a [best-effort database](../getting-started/requirements.md#support-tiers) — MS Access, Firebird, Oracle, or an ODBC DSN — writes an informational line to stderr saying that the adapter is not verified in CI. The default value is "Yes". Set it to "No" to silence the notice. The message is written once per DBMS per run, and always to stderr rather than stdout, so it cannot corrupt piped query output. It is recorded in the log file regardless of this setting.
|
|
239
|
+
|
|
236
240
|
`write_prefix`
|
|
237
241
|
|
|
238
242
|
: Text that will be prefixed to any output from the WRITE metacommand, with a space separator. If substitution variables are used, deferred substitution may be appropriate.
|
|
@@ -115,6 +115,19 @@ SQL statements that were issued while AUTOCOMMIT is OFF can also be committed us
|
|
|
115
115
|
|
|
116
116
|
The AUTOCOMMIT metacommand is database-specific, and affects only the database in use when the metacommand is used. This contrasts with the [BATCH](#batch) metacommand, which affects all databases.
|
|
117
117
|
|
|
118
|
+
**What each DBMS can hold back.** While AUTOCOMMIT is OFF, or inside a [batch](#batch), *execsql* keeps every statement in a transaction until it is committed or rolled back, including `CREATE TABLE` and other DDL. Some databases commit certain statements on their own regardless:
|
|
119
|
+
|
|
120
|
+
| DBMS | While AUTOCOMMIT is OFF or a batch is open |
|
|
121
|
+
| --- | --- |
|
|
122
|
+
| PostgreSQL, SQL Server, SQLite, DuckDB, Firebird | Every statement is held, DDL included, and can be rolled back. |
|
|
123
|
+
| MySQL / MariaDB | The server commits DDL (`CREATE`, `ALTER`, `DROP`, `RENAME`, `TRUNCATE`, `GRANT`, …) and `BEGIN` / `START TRANSACTION` at once, together with every statement before them. `CREATE TEMPORARY TABLE` is held. *execsql* writes a warning naming each such statement. |
|
|
124
|
+
| Oracle | The server commits every DDL statement at once, together with every statement before it. *execsql* writes a warning naming each one. |
|
|
125
|
+
| SQLite | `VACUUM`, `ATTACH`, `DETACH` and `PRAGMA` run outside a transaction, since SQLite refuses or ignores them inside one. |
|
|
126
|
+
| ODBC DSN | If the ODBC driver has no transactions, each statement is committed as it runs; *execsql* warns once. |
|
|
127
|
+
| MS Access | `CREATE TEMPORARY QUERY` reopens the connection, which rolls back statements not yet committed; *execsql* warns once. |
|
|
128
|
+
|
|
129
|
+
Before execsql2 fixed it, SQLite committed DDL at once and DuckDB committed every statement, even with AUTOCOMMIT OFF.
|
|
130
|
+
|
|
118
131
|
|
|
119
132
|
The [IMPORT](#import) and [COPY](#copy) metacommands do not commit data changes while AUTOCOMMIT is off (except when the NEW or REPLACEMENT clauses are used with Firebird; in those cases the 'create table' statement that *execsql* generates and runs will be committed). The SQL statements generated by the [IMPORT](#import) and [COPY](#copy) metacommands are sent to the database, however. Therefore the AUTOCOMMIT metacommand is recommended when explicit transaction control is to be applied to the [IMPORT](#import) and [COPY](#copy) metacommands.
|
|
120
133
|
|
|
@@ -196,6 +209,8 @@ BEGIN BATCH marks the beginning of a set of SQL statements to be executed in a s
|
|
|
196
209
|
|
|
197
210
|
Metacommands may be included inside a batch, but note that the [IMPORT](#import) and [COPY](#copy) metacommands always commit the changes they make, unless [AUTOCOMMIT](#autocommit) is OFF, so if IMPORT or COPY metacommands are used inside a batch, any preceding SQL statements in the batch will also be committed unless AUTOCOMMIT is OFF.
|
|
198
211
|
|
|
212
|
+
What a batch can hold back depends on the DBMS; see the table under [AUTOCOMMIT](#autocommit). MySQL and Oracle commit DDL inside a batch at once, with everything before it, and *execsql* warns when that happens.
|
|
213
|
+
|
|
199
214
|
When the END BATCH metacommand is processed by *execsql*, a commit command is sent to all databases that have been used inside the batch. Multiple databases may be used inside a batch if the [USE](#use) metacommand is used inside the batch. The BATCH metacommands therefore provide a limited sort of cross-database transaction control — note, however, that the commit statements are issued sequentially, and if a later commit statement fails, this will not roll back changes to databases for which the commit has already succeeded.
|
|
200
215
|
|
|
201
216
|
The BEGIN/END BATCH metacommands can be nested. However, the inner END BATCH metacommand will ordinarily commit all changes to the databases that have been used, which may include databases used in the outer batch as well. (This behavior may be DBMS-specific, and also depend on whether any additional explicit transaction control is being used.) Therefore completion of a nested batch may result in premature commitment of some or all SQL statements in the outer batch. Similarly, a ROLLBACK BATCH metacommand within the inner batch will also roll back any SQL commands sent to the same databases in the outer batch. Thus, although the BATCH commands can be nested, database transactions may not be, depending on the DBMS in use. Nesting of BATCH metacommands allows a script file or a [SCRIPT](#beginscript) containing a batch to be [INCLUDEEd](#include) or [EXECUTEEd](#executescript), respectively, within another batch.
|
|
@@ -148,7 +148,7 @@ $LAST_ERROR
|
|
|
148
148
|
: The text of the last SQL statement or metacommand that caused an error. This value will only be available if the [ERROR_HALT OFF](metacommands.md#error_halt) or [METACOMMAND_ERROR_HALT OFF](metacommands.md#metacommanderrorhalt) metacommand has been used, or in an [ON ERROR_HALT EMAIL](metacommands.md#error_halt_email), [ON ERROR_HALT WRITE](metacommands.md#error_halt_write), or [ON ERROR_HALT EXECUTE SCRIPT](metacommands.md#error_halt_exec) metacommand.
|
|
149
149
|
|
|
150
150
|
$LAST_ROWCOUNT
|
|
151
|
-
: The number of rows that were affected by the last INSERT, UPDATE, or SELECT statement. Note that support for $LAST_ROWCOUNT varies among DBMSs. For example, for SELECT statements, Postgres provides an accurate count, SQLite always
|
|
151
|
+
: The number of rows that were affected by the last INSERT, UPDATE, or SELECT statement. Note that support for $LAST_ROWCOUNT varies among DBMSs. For example, for SELECT statements, Postgres provides an accurate count, SQLite and DuckDB always return -1, and Firebird always returns 0. For INSERT, UPDATE and DELETE, every supported DBMS reports the number of rows changed.
|
|
152
152
|
|
|
153
153
|
$LAST_SQL
|
|
154
154
|
: The text of the last SQL statement that ran without error.
|