execsql2 2.21.2__tar.gz → 2.22.0__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.21.2 → execsql2-2.22.0}/.github/workflows/ci-cd.yml +50 -1
- {execsql2-2.21.2 → execsql2-2.22.0}/.pre-commit-config.yaml +12 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/CHANGELOG.md +15 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/CONTRIBUTING.md +20 -8
- {execsql2-2.21.2 → execsql2-2.22.0}/PKG-INFO +3 -3
- {execsql2-2.21.2 → execsql2-2.22.0}/README.md +2 -2
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/about/divergence.md +44 -40
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/dev/architecture.md +1 -1
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/getting-started/installation.md +2 -1
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/guides/formatter.md +1 -1
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/guides/sql_syntax.md +1 -1
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/reference/configuration.md +3 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/reference/metacommands.md +9 -5
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/reference/security.md +14 -2
- {execsql2-2.21.2 → execsql2-2.22.0}/justfile +2 -2
- {execsql2-2.21.2 → execsql2-2.22.0}/pyproject.toml +11 -2
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/api.py +21 -4
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/cli/run.py +37 -17
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/config.py +37 -22
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/data/execsql.conf.template +6 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/db/access.py +6 -5
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/db/base.py +23 -5
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/db/dsn.py +2 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/db/duckdb.py +1 -1
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/db/mysql.py +6 -8
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/db/sqlite.py +1 -1
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/exceptions.py +14 -11
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/exporters/base.py +1 -1
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/exporters/delimited.py +22 -16
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/exporters/duckdb.py +6 -1
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/exporters/feather.py +1 -1
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/exporters/json.py +5 -3
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/exporters/latex.py +2 -2
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/exporters/markdown.py +1 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/exporters/ods.py +7 -3
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/exporters/raw.py +8 -8
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/exporters/sqlite.py +11 -1
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/exporters/templates.py +2 -1
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/exporters/xls.py +10 -10
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/exporters/xml.py +1 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/exporters/yaml.py +1 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/exporters/zip.py +9 -7
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/format.py +6 -6
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/gui/console.py +3 -3
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/gui/desktop.py +28 -10
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/gui/tui.py +8 -6
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/importers/base.py +14 -8
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/importers/csv.py +8 -3
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/importers/xls.py +1 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/metacommands/conditions.py +14 -14
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/metacommands/connect.py +2 -2
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/metacommands/control.py +1 -1
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/metacommands/data.py +2 -1
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/metacommands/io_export.py +4 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/metacommands/io_fileops.py +85 -39
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/metacommands/io_import.py +1 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/metacommands/io_write.py +1 -1
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/metacommands/prompt.py +24 -22
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/metacommands/system.py +14 -1
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/models.py +56 -17
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/parser.py +8 -8
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/plugins.py +1 -1
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/script/engine.py +4 -4
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/script/executor.py +67 -28
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/script/parser.py +13 -12
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/script/variables.py +4 -2
- execsql2-2.22.0/src/execsql/state.pyi +122 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/types.py +31 -26
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/utils/auth.py +6 -2
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/utils/datetime.py +3 -3
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/utils/errors.py +7 -5
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/utils/fileio.py +77 -31
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/utils/gui.py +15 -11
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/utils/mail.py +9 -2
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/utils/numeric.py +1 -1
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/utils/regex.py +9 -9
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/cli/test_cli_run.py +45 -1
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/db/test_mysql_case_folding.py +18 -1
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/exporters/test_duckdb_exporter.py +29 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/exporters/test_feather.py +1 -1
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/exporters/test_latex_extended.py +10 -7
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/exporters/test_sqlite_exporter.py +38 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/importers/test_base_extended.py +19 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/importers/test_csv_importer.py +8 -25
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/metacommands/test_metacommands_fileops_extra.py +102 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/metacommands/test_metacommands_system.py +66 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/security/test_log_redaction.py +45 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/test_api.py +33 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/test_config_data.py +26 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/test_models.py +106 -4
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/test_types.py +2 -3
- {execsql2-2.21.2 → execsql2-2.22.0}/uv.lock +1 -1
- {execsql2-2.21.2 → execsql2-2.22.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/.github/dependabot.yml +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/.gitignore +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/.pre-commit-hooks.yaml +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/.python-version +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/.readthedocs.yaml +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/LICENSE.txt +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/NOTICE +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/SECURITY.md +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/about/contributors.md +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/about/copyright.md +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/api/cli.md +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/api/db.md +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/api/exporters.md +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/api/importers.md +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/api/index.md +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/api/metacommands.md +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/dev/adding_db_adapters.md +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/dev/adding_exporters.md +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/dev/adding_importers.md +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/dev/adding_metacommands.md +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/dev/releasing.md +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/getting-started/requirements.md +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/getting-started/syntax.md +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/guides/debugging.md +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/guides/documentation.md +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/guides/encoding.md +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/guides/examples.md +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/guides/logging.md +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/guides/usage.md +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/guides/using_scripts.md +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/images/Compare_planets.png +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/images/actions.png +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/images/actions2.png +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/images/checkboxes.png +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/images/connect.b64 +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/images/connect.png +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/images/create_conf.png +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/images/data_error1_screenshot.jpg +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/images/entry_form.png +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/images/execsql_console.png +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/images/execsql_logo_01.png +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/images/fatals.png +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/images/logo_small.png +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/images/pause_terminal.png +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/images/pause_terminal_sm.b64 +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/images/pause_terminal_sm.png +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/images/prompt_compare.png +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/images/set_build_commands.jpg +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/images/unit_conversions.b64 +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/images/unit_conversions_029.png +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/images/unmatched.png +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/images/vim_execsql_highlight.png +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/index.md +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/docs/reference/substitution_vars.md +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/extras/plugin-template/README.md +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/extras/plugin-template/pyproject.toml +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/extras/plugin-template/src/execsql_plugin_YOURNAME/__init__.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/extras/plugin-template/tests/test_plugin.py.example +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/extras/vscode-execsql/README.md +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/extras/vscode-execsql/package.json +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/extras/vscode-execsql/syntaxes/execsql.tmLanguage.json +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/scripts/generate_vscode_grammar.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/__init__.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/__main__.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/cli/__init__.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/cli/dsn.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/cli/help.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/cli/lint.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/data/__init__.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/db/__init__.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/db/factory.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/db/firebird.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/db/oracle.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/db/postgres.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/db/sqlserver.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/debug/__init__.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/debug/repl.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/exporters/__init__.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/exporters/html.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/exporters/parquet.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/exporters/pretty.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/exporters/protocol.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/exporters/values.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/exporters/xlsx.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/gui/__init__.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/gui/base.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/importers/__init__.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/importers/feather.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/importers/json.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/importers/ods.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/metacommands/__init__.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/metacommands/debug.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/metacommands/dispatch.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/metacommands/io.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/metacommands/script_ext.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/metacommands/upsert.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/py.typed +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/script/__init__.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/script/ast.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/script/control.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/state.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/utils/__init__.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/utils/crypto.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/utils/strings.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/src/execsql/utils/timer.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/templates/README.md +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/templates/config_settings.sqlite +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/templates/example_config_prompt.sql +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/templates/make_config_db.sql +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/templates/md_compare.sql +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/templates/md_glossary.sql +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/templates/md_upsert.sql +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/templates/pg_compare.sql +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/templates/pg_glossary.sql +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/templates/pg_upsert.sql +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/templates/script_template.sql +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/templates/ss_compare.sql +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/templates/ss_glossary.sql +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/templates/ss_upsert.sql +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/__init__.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/cli/__init__.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/cli/test_cli.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/cli/test_cli_e2e.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/cli/test_lint.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/cli/test_ping.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/cli/test_profile.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/conftest.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/db/__init__.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/db/test_access_windows.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/db/test_base.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/db/test_db_adapters_mocked.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/db/test_db_adapters_mocked_extra.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/db/test_dsn.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/db/test_duckdb.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/db/test_factory.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/db/test_mysql_inprocess.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/db/test_postgres.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/db/test_postgres_inprocess.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/db/test_sqlite.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/db/test_sqlite_extra.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/db/test_sqlserver_inprocess.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/exporters/__init__.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/exporters/test_base.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/exporters/test_db.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/exporters/test_delimited.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/exporters/test_exporters.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/exporters/test_html_extended.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/exporters/test_html_latex.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/exporters/test_json.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/exporters/test_json_extended.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/exporters/test_markdown.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/exporters/test_ods.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/exporters/test_ods_export.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/exporters/test_parquet.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/exporters/test_pretty_extended.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/exporters/test_raw_extended.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/exporters/test_templates.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/exporters/test_templates_extended.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/exporters/test_values_extended.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/exporters/test_xls_xlsx.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/exporters/test_xlsx.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/exporters/test_xml.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/exporters/test_yaml.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/exporters/test_zip.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/gui/__init__.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/gui/test_backends.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/gui/test_backends_extended.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/gui/test_compare_stats.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/gui/test_compute_row_diffs.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/gui/test_desktop_dialogs.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/gui/test_tui_pilot.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/gui/test_tui_pilot_complex.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/gui/test_utils_gui_extended.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/importers/__init__.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/importers/test_csv_edge_cases.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/importers/test_feather_importer.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/importers/test_json_importer.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/importers/test_ods_importer.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/importers/test_xls_importer.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/integration/__init__.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/integration/conftest.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/integration/test_dsn.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/integration/test_duckdb.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/integration/test_mysql.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/integration/test_postgres.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/integration/test_sqlite.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/metacommands/__init__.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/metacommands/test_assert.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/metacommands/test_breakpoint.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/metacommands/test_conditions_extra.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/metacommands/test_connect.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/metacommands/test_io_export.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/metacommands/test_io_import.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/metacommands/test_metacommands.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/metacommands/test_metacommands_connect.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/metacommands/test_metacommands_data.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/metacommands/test_metacommands_extended.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/metacommands/test_metacommands_io.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/metacommands/test_metacommands_io_write_extra.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/metacommands/test_metacommands_script_ext.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/metacommands/test_metacommands_system_extra.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/metacommands/test_pg_upsert.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/metacommands/test_prompt.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/metacommands/test_row_count.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/metacommands/test_show_scripts.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/scripts/__init__.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/scripts/fixtures/audit_lint_bad.sql +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/scripts/fixtures/audit_smoke/sample.json +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/scripts/fixtures/audit_smoke/sample.jsonl +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/scripts/fixtures/audit_smoke.sql +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/scripts/fixtures/config_runtime.sql +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/scripts/fixtures/control_flow.sql +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/scripts/fixtures/counters_and_locals.sql +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/scripts/fixtures/error_handling.sql +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/scripts/fixtures/import_options/has_comments.csv +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/scripts/fixtures/import_options.sql +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/scripts/fixtures/includes/helper.sql +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/scripts/fixtures/includes.sql +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/scripts/fixtures/io_formats.sql +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/scripts/fixtures/io_roundtrip.sql +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/scripts/fixtures/multi_db.sql +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/scripts/fixtures/parquet_feather_roundtrip.sql +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/scripts/fixtures/parse_only/parse_tree.sql +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/scripts/fixtures/predicates.sql +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/scripts/fixtures/scripts.sql +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/scripts/fixtures/smoke.sql +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/scripts/fixtures/subroutine_loops.sql +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/scripts/fixtures/subvars_advanced.sql +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/scripts/fixtures/template_export/greeting.tmpl +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/scripts/fixtures/template_export.sql +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/scripts/fixtures/timer.sql +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/scripts/fixtures/transactions.sql +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/scripts/fixtures/write_create_table.sql +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/scripts/fixtures/xlsx_ods_roundtrip.sql +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/scripts/test_sql_scripts.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/security/__init__.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/security/test_env_detection.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/security/test_expansion_bomb.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/security/test_import_regex_hardening.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/security/test_odbc_injection.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/security/test_path_containment.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/security/test_substitution_injection.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/security/test_zip_bomb.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/test_api_extended.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/test_ast.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/test_ast_parser.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/test_bundled_templates.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/test_config.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/test_config_extended.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/test_debug_repl.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/test_engine.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/test_error_messages.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/test_exceptions.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/test_executor.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/test_executor_inprocess.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/test_format.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/test_mail.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/test_package.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/test_parser.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/test_parser_params.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/test_plugins.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/test_registry.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/test_script.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/test_state.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/utils/__init__.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/utils/test_auth.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/utils/test_auth_extra.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/utils/test_crypto.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/utils/test_datetime.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/utils/test_encodedfile_context.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/utils/test_errors.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/utils/test_errors_extra.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/utils/test_fileio.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/utils/test_fileio_extra.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/utils/test_numeric.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/utils/test_regex.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/utils/test_strings.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/utils/test_timer.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/tests/utils/test_timer_extra.py +0 -0
- {execsql2-2.21.2 → execsql2-2.22.0}/zensical.toml +0 -0
|
@@ -28,6 +28,55 @@ jobs:
|
|
|
28
28
|
- run: ruff check src/ tests/
|
|
29
29
|
- run: ruff format --check src/ tests/
|
|
30
30
|
|
|
31
|
+
typecheck:
|
|
32
|
+
name: typecheck
|
|
33
|
+
runs-on: ubuntu-latest
|
|
34
|
+
permissions:
|
|
35
|
+
contents: read
|
|
36
|
+
steps:
|
|
37
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
38
|
+
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
|
39
|
+
with:
|
|
40
|
+
python-version: "3.13"
|
|
41
|
+
cache: "pip"
|
|
42
|
+
- run: python -m pip install --upgrade pip
|
|
43
|
+
- run: python -m pip install ".[dev]"
|
|
44
|
+
- run: mypy src/execsql/
|
|
45
|
+
|
|
46
|
+
pre-commit:
|
|
47
|
+
name: pre-commit
|
|
48
|
+
runs-on: ubuntu-latest
|
|
49
|
+
permissions:
|
|
50
|
+
contents: read
|
|
51
|
+
steps:
|
|
52
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
53
|
+
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
|
54
|
+
with:
|
|
55
|
+
python-version: "3.13"
|
|
56
|
+
cache: "pip"
|
|
57
|
+
- run: python -m pip install --upgrade pip
|
|
58
|
+
- run: python -m pip install ".[dev]"
|
|
59
|
+
# mypy runs in the dedicated typecheck job; the local hook needs uv
|
|
60
|
+
- run: pre-commit run --all-files
|
|
61
|
+
env:
|
|
62
|
+
SKIP: mypy
|
|
63
|
+
|
|
64
|
+
docs:
|
|
65
|
+
name: docs
|
|
66
|
+
runs-on: ubuntu-latest
|
|
67
|
+
permissions:
|
|
68
|
+
contents: read
|
|
69
|
+
steps:
|
|
70
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
71
|
+
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
|
72
|
+
with:
|
|
73
|
+
python-version: "3.13"
|
|
74
|
+
cache: "pip"
|
|
75
|
+
- run: python -m pip install --upgrade pip
|
|
76
|
+
- run: python -m pip install ".[dev]"
|
|
77
|
+
- run: cp CHANGELOG.md docs/about/change_log.md
|
|
78
|
+
- run: zensical build
|
|
79
|
+
|
|
31
80
|
tests:
|
|
32
81
|
strategy:
|
|
33
82
|
fail-fast: false
|
|
@@ -233,7 +282,7 @@ jobs:
|
|
|
233
282
|
build:
|
|
234
283
|
name: Build distribution 📦
|
|
235
284
|
runs-on: ubuntu-latest
|
|
236
|
-
needs: [ lint, tests, integration-tests ]
|
|
285
|
+
needs: [ lint, typecheck, pre-commit, docs, tests, integration-tests ]
|
|
237
286
|
if: startsWith(github.ref, 'refs/tags/v')
|
|
238
287
|
permissions:
|
|
239
288
|
contents: read
|
|
@@ -80,3 +80,15 @@ repos:
|
|
|
80
80
|
rev: v0.25
|
|
81
81
|
hooks:
|
|
82
82
|
- id: validate-pyproject
|
|
83
|
+
# Static type checking — same command as `just typecheck`, run in the
|
|
84
|
+
# project environment so results match local and CI. Whole-program run
|
|
85
|
+
# (pass_filenames: false) because mypy cannot check files in isolation.
|
|
86
|
+
# The CI pre-commit job sets SKIP=mypy; CI has a dedicated typecheck gate.
|
|
87
|
+
- repo: local
|
|
88
|
+
hooks:
|
|
89
|
+
- id: mypy
|
|
90
|
+
name: mypy
|
|
91
|
+
entry: uv run mypy src/execsql/
|
|
92
|
+
language: system
|
|
93
|
+
pass_filenames: false
|
|
94
|
+
files: ^(src/execsql/.*\.pyi?|pyproject\.toml)$
|
|
@@ -13,6 +13,21 @@ ______________________________________________________________________
|
|
|
13
13
|
|
|
14
14
|
______________________________________________________________________
|
|
15
15
|
|
|
16
|
+
## [2.22.0] - 2026-07-03
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- `run()` now accepts `allow_rm_file=False` and `allow_serve=False` to disable the `RM_FILE` and `SERVE` metacommands for library callers, matching the existing `allow_system_cmd` kwarg and the `--no-rm-file` / `--no-serve` CLI flags.
|
|
21
|
+
- `[config] system_cmd_timeout` can bound foreground `SYSTEM_CMD` execution; the default remains no timeout.
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- `EXPORT … FORMAT SQLITE` and `EXPORT … FORMAT DUCKDB` correctly preserve source column types when exporting from a typed database: text columns stay text (not re-inferred as TIMESTAMP or numeric from value shape), `decimal.Decimal` values bind without error (converted to their exact string representation for SQLite), and unsupported Python objects such as PostGIS geometry are stringified instead of raising a bind error.
|
|
26
|
+
- `EXPORT … FORMAT LATEX` into a ZIP archive no longer crashes with `AttributeError`.
|
|
27
|
+
- Connecting to a server-based database without a server or database name now stops with a clear error message instead of a driver traceback.
|
|
28
|
+
|
|
29
|
+
______________________________________________________________________
|
|
30
|
+
|
|
16
31
|
## [2.21.2] - 2026-06-30
|
|
17
32
|
|
|
18
33
|
### Fixed
|
|
@@ -32,13 +32,19 @@ Linting and formatting are handled by [ruff](https://docs.astral.sh/ruff/):
|
|
|
32
32
|
just lint
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
[
|
|
35
|
+
Static type checking is handled by [mypy](https://mypy.readthedocs.io/) and must pass with zero errors:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
just typecheck
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
[pre-commit](https://pre-commit.com/) hooks enforce additional checks (gitleaks, uv-lock, mdformat, markdownlint, typos, validate-pyproject, mypy) on every commit. To run them manually against all files:
|
|
36
42
|
|
|
37
43
|
```bash
|
|
38
44
|
just pre-commit
|
|
39
45
|
```
|
|
40
46
|
|
|
41
|
-
CI rejects PRs that fail
|
|
47
|
+
CI rejects PRs that fail ruff lint/format, mypy type checking, the full pre-commit suite, docs build, or tests.
|
|
42
48
|
|
|
43
49
|
## Running Tests
|
|
44
50
|
|
|
@@ -96,12 +102,18 @@ Pre-releases are published to PyPI but are not installed by default — `pip ins
|
|
|
96
102
|
|
|
97
103
|
Triggered on pushes to `main`, any `v*.*.*` tag, and pull requests.
|
|
98
104
|
|
|
99
|
-
| Job
|
|
100
|
-
|
|
|
101
|
-
| `
|
|
102
|
-
| `
|
|
103
|
-
| `
|
|
104
|
-
| `
|
|
105
|
+
| Job | Trigger | What it does |
|
|
106
|
+
| ---------------------- | ------------------ | ----------------------------------------------------- |
|
|
107
|
+
| `lint` | all events | Runs `ruff check` and `ruff format --check` |
|
|
108
|
+
| `typecheck` | all events | Runs `mypy src/execsql/` (zero-error gate) |
|
|
109
|
+
| `pre-commit` | all events | Runs the full pre-commit suite against all files |
|
|
110
|
+
| `docs` | all events | Builds the documentation site |
|
|
111
|
+
| `tests` | all events | Runs the test matrix (3 OS × 5 Python versions) |
|
|
112
|
+
| `integration-tests` | all events | Runs tests against live database service containers |
|
|
113
|
+
| `access-tests-windows` | all events | Runs MS Access driver tests on Windows (non-blocking) |
|
|
114
|
+
| `build` | `v*.*.*` tags only | Builds sdist + wheel, checks with twine |
|
|
115
|
+
| `publish` | `v*.*.*` tags only | Publishes to PyPI via OIDC trusted publishing |
|
|
116
|
+
| `generate-release` | `v*.*.*` tags only | Creates a GitHub Release with dist artifacts |
|
|
105
117
|
|
|
106
118
|
### PyPI trusted publishing setup
|
|
107
119
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: execsql2
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.22.0
|
|
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
|
|
@@ -173,7 +173,7 @@ Optional extras install database drivers and feature bundles:
|
|
|
173
173
|
|
|
174
174
|
```bash
|
|
175
175
|
# Database drivers
|
|
176
|
-
pip install execsql2[postgres] # PostgreSQL (
|
|
176
|
+
pip install execsql2[postgres] # PostgreSQL (psycopg3 / psycopg[binary])
|
|
177
177
|
pip install execsql2[mysql] # MySQL / MariaDB (pymysql)
|
|
178
178
|
pip install execsql2[mssql] # SQL Server (pyodbc)
|
|
179
179
|
pip install execsql2[duckdb] # DuckDB
|
|
@@ -403,7 +403,7 @@ execsql-format --no-sql --in-place scripts/
|
|
|
403
403
|
```yaml
|
|
404
404
|
repos:
|
|
405
405
|
- repo: https://github.com/geocoug/execsql
|
|
406
|
-
rev: v2.
|
|
406
|
+
rev: v2.22.0
|
|
407
407
|
hooks:
|
|
408
408
|
- id: execsql-format
|
|
409
409
|
```
|
|
@@ -42,7 +42,7 @@ Optional extras install database drivers and feature bundles:
|
|
|
42
42
|
|
|
43
43
|
```bash
|
|
44
44
|
# Database drivers
|
|
45
|
-
pip install execsql2[postgres] # PostgreSQL (
|
|
45
|
+
pip install execsql2[postgres] # PostgreSQL (psycopg3 / psycopg[binary])
|
|
46
46
|
pip install execsql2[mysql] # MySQL / MariaDB (pymysql)
|
|
47
47
|
pip install execsql2[mssql] # SQL Server (pyodbc)
|
|
48
48
|
pip install execsql2[duckdb] # DuckDB
|
|
@@ -272,7 +272,7 @@ execsql-format --no-sql --in-place scripts/
|
|
|
272
272
|
```yaml
|
|
273
273
|
repos:
|
|
274
274
|
- repo: https://github.com/geocoug/execsql
|
|
275
|
-
rev: v2.
|
|
275
|
+
rev: v2.22.0
|
|
276
276
|
hooks:
|
|
277
277
|
- id: execsql-format
|
|
278
278
|
```
|
|
@@ -179,7 +179,7 @@ ______________________________________________________________________
|
|
|
179
179
|
|
|
180
180
|
### CLI Interface
|
|
181
181
|
|
|
182
|
-
The CLI framework changed from `optparse` to [Typer](https://typer.tiangolo.com/) with Rich-formatted help text. All original short flags (`-a` through `-z`) are preserved. The
|
|
182
|
+
The CLI framework changed from `optparse` to [Typer](https://typer.tiangolo.com/) with Rich-formatted help text. All original short flags (`-a` through `-z`) are preserved. The PyPI distribution is named `execsql2`, but the installed executable remains `execsql` for compatibility.
|
|
183
183
|
|
|
184
184
|
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.
|
|
185
185
|
|
|
@@ -271,45 +271,49 @@ These are behavioral changes driven by security or correctness issues in the ups
|
|
|
271
271
|
|
|
272
272
|
### Bug Fixes
|
|
273
273
|
|
|
274
|
-
| Area | Fix
|
|
275
|
-
| --------------------------------------------------- |
|
|
276
|
-
| Oracle default port | Corrected from `5432` (PostgreSQL) to `1521`.
|
|
277
|
-
| MySQL `LOAD DATA INFILE` encoding | Python encoding names are now mapped to MySQL charset names.
|
|
278
|
-
| `dt_cast` type converters | Base `Database` class auto-populates 8 type converters that were previously left empty after the refactor.
|
|
279
|
-
| `FileWriter` CPU busy-loop | Uses blocking `queue.get(timeout=0.1)` instead of `get_nowait()` in a tight loop.
|
|
280
|
-
| Substitution variable cycles | 100-iteration limit prevents infinite loops on cyclic variable references.
|
|
281
|
-
| Script location in error messages | `ErrInfo.script_file` and `script_line_no` are now populated via `stamp_errinfo()` so error output includes "Line N of script foo.sql" context — restoring behavior present in the monolith.
|
|
282
|
-
| `$ERROR_MESSAGE` not updated | `$ERROR_MESSAGE` is now set on every error path: `exit_now()`, non-halting SQL errors, and non-halting metacommand errors. Previously it was initialized to `""` and never changed.
|
|
283
|
-
| Metacommand error message lost | When `halt_on_metacommand_err` is `ON`, the original handler `ErrInfo` is now re-raised; the generic "Unknown metacommand" message no longer replaces the specific error from the handler.
|
|
284
|
-
| Empty script name in error msg | `_execute_script_direct()` and `_execute_script_textual_console()` no longer append "in script , line 0" to uncaught-exception messages when `current_script_line()` returns an empty string.
|
|
285
|
-
| `PROMPT COMPARE` diff comparison | Diff engine uses native Python equality instead of string comparison — numeric types, Decimals, and booleans compare correctly. `None` is distinguished from empty string. Columns are matched by name (not position), key columns are excluded from comparison, and duplicate PKs keep the first row.
|
|
286
|
-
| `win_config_file` broken | Checked `os.name == "windows"` which Python never returns (correct value is `"nt"`). Fixed to `os.name == "nt"`.
|
|
287
|
-
| ELSEIF + ANDIF/ORIF | ANDIF/ORIF after an ELSEIF were silently attached to the parent IF condition instead of the ELSEIF clause. The compound condition was never evaluated for the ELSEIF branch. Fixed in the AST parser and executor.
|
|
288
|
-
| Cursor leak in `select_rowsource()` | Cursor was not closed on query execution failure. Row generators in EXPORT and COPY paths were not explicitly closed on error, relying on garbage collection for cleanup.
|
|
289
|
-
| `NumericParser` right-associative | Arithmetic operators were parsed right-to-left. `10 - 3 - 2` evaluated as `9` instead of `5`. Fixed to left-associative parsing.
|
|
290
|
-
| Empty-column check precedence | `DataTable` and `Database.populate_table()` had an operator precedence bug in the extra-column emptiness check — a redundant `and conf.del_empty_cols` caused incorrect short-circuit evaluation.
|
|
291
|
-
| SQLite import string processing | `SQLiteDatabase.populate_table()` applied `trim_strings`, `replace_newlines`, and `empty_strings` after copying row data, so processing never reached the INSERT. Fixed to process before extraction.
|
|
292
|
-
| `$CURRENT_DATABASE`/`$CURRENT_DBMS` stale after USE | These variables were only set at startup and on CONNECT, not refreshed when `USE` switched the active database. Now set in `set_static_system_vars()` so they update on any connection change.
|
|
293
|
-
| `DT_Text.data_type_name` wrong | Was `"character"` (same as `DT_Character`), making error messages indistinguishable. Corrected to `"text"`.
|
|
294
|
-
| `DT_Varchar` non-string data unchecked | `_from_data()` only enforced the 255-char limit for `str` inputs; non-string values passed through without conversion or length check.
|
|
295
|
-
| `WriteHooks.write_err()` crash on empty string | `strval[-1]` raised `IndexError` on empty input. Fixed to use `str.endswith()`.
|
|
296
|
-
| `NumericParser` division by zero | `NumericAstNode.eval()` raised unhandled `ZeroDivisionError`. Now raises `NumericParserError` with a clear message.
|
|
297
|
-
| `CondAstNode.eval()` could return `None` | Missing fallthrough for unknown node types silently returned `None`. Now raises `CondParserError`.
|
|
298
|
-
| `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.
|
|
299
|
-
| `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.
|
|
300
|
-
| `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.
|
|
301
|
-
| 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.
|
|
302
|
-
|
|
|
303
|
-
|
|
|
304
|
-
|
|
|
305
|
-
|
|
|
306
|
-
|
|
|
307
|
-
|
|
|
308
|
-
|
|
|
309
|
-
|
|
|
310
|
-
|
|
|
311
|
-
| AST parser
|
|
312
|
-
| AST
|
|
274
|
+
| Area | Fix |
|
|
275
|
+
| --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
276
|
+
| Oracle default port | Corrected from `5432` (PostgreSQL) to `1521`. |
|
|
277
|
+
| MySQL `LOAD DATA INFILE` encoding | Python encoding names are now mapped to MySQL charset names. |
|
|
278
|
+
| `dt_cast` type converters | Base `Database` class auto-populates 8 type converters that were previously left empty after the refactor. |
|
|
279
|
+
| `FileWriter` CPU busy-loop | Uses blocking `queue.get(timeout=0.1)` instead of `get_nowait()` in a tight loop. |
|
|
280
|
+
| Substitution variable cycles | 100-iteration limit prevents infinite loops on cyclic variable references. |
|
|
281
|
+
| Script location in error messages | `ErrInfo.script_file` and `script_line_no` are now populated via `stamp_errinfo()` so error output includes "Line N of script foo.sql" context — restoring behavior present in the monolith. |
|
|
282
|
+
| `$ERROR_MESSAGE` not updated | `$ERROR_MESSAGE` is now set on every error path: `exit_now()`, non-halting SQL errors, and non-halting metacommand errors. Previously it was initialized to `""` and never changed. |
|
|
283
|
+
| Metacommand error message lost | When `halt_on_metacommand_err` is `ON`, the original handler `ErrInfo` is now re-raised; the generic "Unknown metacommand" message no longer replaces the specific error from the handler. |
|
|
284
|
+
| Empty script name in error msg | `_execute_script_direct()` and `_execute_script_textual_console()` no longer append "in script , line 0" to uncaught-exception messages when `current_script_line()` returns an empty string. |
|
|
285
|
+
| `PROMPT COMPARE` diff comparison | Diff engine uses native Python equality instead of string comparison — numeric types, Decimals, and booleans compare correctly. `None` is distinguished from empty string. Columns are matched by name (not position), key columns are excluded from comparison, and duplicate PKs keep the first row. |
|
|
286
|
+
| `win_config_file` broken | Checked `os.name == "windows"` which Python never returns (correct value is `"nt"`). Fixed to `os.name == "nt"`. |
|
|
287
|
+
| ELSEIF + ANDIF/ORIF | ANDIF/ORIF after an ELSEIF were silently attached to the parent IF condition instead of the ELSEIF clause. The compound condition was never evaluated for the ELSEIF branch. Fixed in the AST parser and executor. |
|
|
288
|
+
| Cursor leak in `select_rowsource()` | Cursor was not closed on query execution failure. Row generators in EXPORT and COPY paths were not explicitly closed on error, relying on garbage collection for cleanup. |
|
|
289
|
+
| `NumericParser` right-associative | Arithmetic operators were parsed right-to-left. `10 - 3 - 2` evaluated as `9` instead of `5`. Fixed to left-associative parsing. |
|
|
290
|
+
| Empty-column check precedence | `DataTable` and `Database.populate_table()` had an operator precedence bug in the extra-column emptiness check — a redundant `and conf.del_empty_cols` caused incorrect short-circuit evaluation. |
|
|
291
|
+
| SQLite import string processing | `SQLiteDatabase.populate_table()` applied `trim_strings`, `replace_newlines`, and `empty_strings` after copying row data, so processing never reached the INSERT. Fixed to process before extraction. |
|
|
292
|
+
| `$CURRENT_DATABASE`/`$CURRENT_DBMS` stale after USE | These variables were only set at startup and on CONNECT, not refreshed when `USE` switched the active database. Now set in `set_static_system_vars()` so they update on any connection change. |
|
|
293
|
+
| `DT_Text.data_type_name` wrong | Was `"character"` (same as `DT_Character`), making error messages indistinguishable. Corrected to `"text"`. |
|
|
294
|
+
| `DT_Varchar` non-string data unchecked | `_from_data()` only enforced the 255-char limit for `str` inputs; non-string values passed through without conversion or length check. |
|
|
295
|
+
| `WriteHooks.write_err()` crash on empty string | `strval[-1]` raised `IndexError` on empty input. Fixed to use `str.endswith()`. |
|
|
296
|
+
| `NumericParser` division by zero | `NumericAstNode.eval()` raised unhandled `ZeroDivisionError`. Now raises `NumericParserError` with a clear message. |
|
|
297
|
+
| `CondAstNode.eval()` could return `None` | Missing fallthrough for unknown node types silently returned `None`. Now raises `CondParserError`. |
|
|
298
|
+
| `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. |
|
|
299
|
+
| `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. |
|
|
300
|
+
| `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. |
|
|
301
|
+
| 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. |
|
|
302
|
+
| 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`. |
|
|
303
|
+
| 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. |
|
|
304
|
+
| Cursor leaks in database adapters | ~15 methods across all adapters used `curs = self.cursor()` / `curs.close()` without `try/finally`. If the query raised, the cursor leaked. Converted to `with self._cursor() as curs:`. |
|
|
305
|
+
| JSON export malformed on special column names | Column names containing `"` or `\` produced invalid JSON. Now uses `json.dumps()` for all field names. |
|
|
306
|
+
| Temp file creation TOCTOU race | `TempFileMgr.new_temp_fn()` discarded the `NamedTemporaryFile` handle, creating a race window. Now uses `tempfile.mkstemp()` for secure creation. |
|
|
307
|
+
| `shlex.split` on Windows incorrect mode | Called without `posix=False` on Windows, mishandling backslash-heavy paths in SHELL commands. |
|
|
308
|
+
| AST executor `~`/`+` variable scoping broken | The AST executor passed `localvars` through function parameters but never pushed `CommandList` frames onto `commandliststack`. Legacy metacommand handlers (`x_sub`, `x_rm_sub`, `xf_sub_defined`, `SUB_LOCAL`, prompt handlers, REPL) access `commandliststack[-1]` for `~` local and `+` outer-scope variables. This caused `~` vars to be invisible to SQL, `SUB_DEFINED(~var)` to always return false, and the REPL `.vars`/`.stack` to show empty state. Fixed by pushing/popping `CommandList` frames in `execute()` and `_execute_script_native()`. |
|
|
309
|
+
| Firebird adapter import | Adapter imported `fdb` (the unmaintained legacy driver) while the `[firebird]` extra declared `firebird-driver`. `pip install execsql2[firebird]` followed by a Firebird connect raised `ModuleNotFoundError: No module named 'fdb'`. Adapter now imports `firebird.driver`, matching the declared dependency. |
|
|
310
|
+
| Connect timeouts on network adapters | MySQL, SQL Server / Access / DSN (pyodbc), Firebird, and SMTP all now apply a 30 s connect timeout matching the existing PostgreSQL default. A silently-dropped network peer (firewall rule, dead VM) used to hang scripts indefinitely; now the connect fails fast. |
|
|
311
|
+
| AST parser INCLUDE quoted paths broken | The AST parser captured the full INCLUDE target including surrounding quotes (`"path"`), but the legacy dispatch regex stripped them. Quoted INCLUDE paths failed with "File does not exist" even when the file was present. Fixed by stripping matched quote pairs in the parser. |
|
|
312
|
+
| AST parser `BEGIN SCRIPT name(params)` rejected | The regex required whitespace between the script name and parameter list. `BEGIN SCRIPT foo(a,b)` (no space before `(`) silently failed to match, causing the matching `END SCRIPT` to raise "Unmatched END SCRIPT metacommand." Fixed by allowing optional whitespace before the parameter expression. |
|
|
313
|
+
| AST executor forward SCRIPT references broken | The legacy engine registered all `BEGIN SCRIPT` blocks at parse time (two-pass), so `EXECUTE SCRIPT foo` could appear before the `BEGIN SCRIPT foo` definition. The AST executor walked the tree in a single pass, so forward references failed with "There is no SCRIPT named foo." Fixed by adding a pre-registration scan of all SCRIPT blocks before execution begins. |
|
|
314
|
+
| DB-export type inference overrides source types | `DataTable`/`Column` inferred column types from the *string shape* of values — correct for CSV import (everything is `str`) but wrong for DB-query exports where the driver returns typed Python objects. A text column with values like `"2026-06-30 10:07:20 PM"` was inferred as `TIMESTAMP`, causing DuckDB bind errors. SQLite, DuckDB, HDF5, and JSON-schema exports now pass `infer_strings=False` to `DataTable`: string values are classified as text types only, while native Python objects (`Decimal`, `datetime`, etc.) still run through the full accumulator set for accurate schema inference. |
|
|
315
|
+
| `EXPORT FORMAT SQLITE` rejects `decimal.Decimal` | SQLite's Python adapter does not accept `Decimal` in `executemany`, raising `ProgrammingError`. SQLite exports now convert `Decimal` values to their exact string representation before binding; SQLite's NUMERIC affinity stores them without loss of precision. |
|
|
316
|
+
| Unsupported Python objects crash native exports | `EXPORT FORMAT SQLITE` and `EXPORT FORMAT DUCKDB` raised bind errors when the DB driver returned user-defined Python objects (e.g. PostGIS geometry). Both exporters now stringify any object whose type is not in the adapter's safe-binding set, preserving values as their `str()` form instead of crashing. |
|
|
313
317
|
|
|
314
318
|
______________________________________________________________________
|
|
315
319
|
|
|
@@ -6,7 +6,7 @@ ______________________________________________________________________
|
|
|
6
6
|
|
|
7
7
|
## Execution Flow
|
|
8
8
|
|
|
9
|
-
When a user runs `
|
|
9
|
+
When a user runs `execsql script.sql mydb.sqlite -t l`, the following sequence occurs:
|
|
10
10
|
|
|
11
11
|
```mermaid
|
|
12
12
|
flowchart TD
|
|
@@ -14,7 +14,8 @@ Or with [uv](https://docs.astral.sh/uv/):
|
|
|
14
14
|
uv add execsql2
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
This installs the `
|
|
17
|
+
This installs the `execsql` and `execsql-format` command-line tools. The PyPI distribution is named
|
|
18
|
+
`execsql2`; the executable remains `execsql` for compatibility.
|
|
18
19
|
|
|
19
20
|
To install with optional dependencies:
|
|
20
21
|
|
|
@@ -12,7 +12,7 @@ When adding a very large amount of data with the [IMPORT](../reference/metacomma
|
|
|
12
12
|
|
|
13
13
|
## Implicit DROP TABLE Statements
|
|
14
14
|
|
|
15
|
-
The "REPLACEMENT" keyword for the [IMPORT](../reference/metacommands.md#import) and [COPY](../reference/metacommands.md#copy) metacommands allows a previously existing table to be replaced. To accomplish this, execsql issues a "DROP TABLE" statement to the database in use. PostgreSQL, SQLite, MySQL, MariaDB, Oracle, and DuckDB support a form of the "DROP TABLE" statement that automatically removes all foreign keys to the named table. execsql uses these forms of the "DROP TABLE" statement for these DBMSs
|
|
15
|
+
The "REPLACEMENT" keyword for the [IMPORT](../reference/metacommands.md#import) and [COPY](../reference/metacommands.md#copy) metacommands allows a previously existing table to be replaced. To accomplish this, execsql issues a "DROP TABLE" statement to the database in use. This is not an atomic replacement: if the drop succeeds but a later create or load step fails, the old table has already been removed. If the drop fails, execsql stops the metacommand before creating or loading the replacement table. PostgreSQL, SQLite, MySQL, MariaDB, Oracle, and DuckDB support a form of the "DROP TABLE" statement that automatically removes all foreign keys to the named table. execsql uses these forms of the "DROP TABLE" statement for these DBMSs. SQL Server, MS-Access, and Firebird do not have a form of the "DROP TABLE" statement that automatically removes foreign keys. Therefore, if the "REPLACEMENT" keyword is used with any of these three DBMSs, for a table that has foreign keys into it, that table will not be dropped, and the IMPORT or COPY command will stop. To avoid this, when using any of these three DBMSs, you should include in the script the appropriate SQL commands to remove foreign keys (and possibly even to remove the table) before using the IMPORT or COPY metacommands.
|
|
16
16
|
|
|
17
17
|
## Boolean Data Types
|
|
18
18
|
|
|
@@ -333,6 +333,9 @@ The section and property names that may be used in a configuration file are list
|
|
|
333
333
|
`allow_system_cmd` { #allow_system_cmd }
|
|
334
334
|
: When set to "No", the `SYSTEM_CMD` (SHELL) metacommand is disabled. Any script that attempts to execute an OS command will fail with an error. The default is "Yes". This can also be set via the `--no-system-cmd` CLI flag or `allow_system_cmd=False` in the library API. See [Security — Disabling SYSTEM_CMD](security.md#disable_system_cmd) for details.
|
|
335
335
|
|
|
336
|
+
`system_cmd_timeout` { #system_cmd_timeout }
|
|
337
|
+
: Maximum number of seconds a foreground `SYSTEM_CMD` may run before *execsql* stops it. The default is `0`, which means no timeout. When a foreground command times out, `$SYSTEM_CMD_EXIT_STATUS` is set to `124` and the metacommand fails. `SYSTEM_CMD ... CONTINUE` starts a detached background process and is not timeout-managed by this setting.
|
|
338
|
+
|
|
336
339
|
`allow_rm_file` { #allow_rm_file }
|
|
337
340
|
: When set to "No", the `RM_FILE` metacommand (which deletes a file) is disabled. Any script that attempts to remove a file will fail with an error. The default is "Yes". This can also be set via the `--no-rm-file` CLI flag or `allow_rm_file=False` in the library API. Symmetric with `allow_system_cmd`.
|
|
338
341
|
|
|
@@ -857,8 +857,8 @@ The second (destination) table must have column names that are identical to the
|
|
|
857
857
|
|
|
858
858
|
If the "NEW" keyword is used, the destination table will be automatically created with column names and data types that are compatible with the first (source) table. The data types used for the columns in the newly created table will be determined by a scan of all of the data in the first table, but may not exactly match those in the first table. If the destination table already exists when the "NEW" keyword is used, an error will occur.
|
|
859
859
|
|
|
860
|
-
If the "REPLACEMENT" keyword is used, the destination table will also be created to be compatible with the source table, but any existing destination table of the same name will be dropped first. *execsql* uses a "drop table" statement to drop an existing destination table, and this statement may not succeed if there are dependencies on that table (see the discussion of [implicit drop table
|
|
861
|
-
statements](../guides/sql_syntax.md#implicit_drop)).
|
|
860
|
+
If the "REPLACEMENT" keyword is used, the destination table will also be created to be compatible with the source table, but any existing destination table of the same name will be dropped first. This replacement is not atomic: if the drop succeeds but a later create or load step fails, the old destination table has already been removed. If the drop fails, *execsql* stops the COPY command before creating or loading the replacement table. *execsql* uses a "drop table" statement to drop an existing destination table, and this statement may not succeed if there are dependencies on that table (see the discussion of [implicit drop table
|
|
861
|
+
statements](../guides/sql_syntax.md#implicit_drop)).
|
|
862
862
|
|
|
863
863
|
If there are constraints on the second table that are not met by the data being added, an error will occur. If an error occurs at any point during the data copying process, no new data will be added to the second table.
|
|
864
864
|
|
|
@@ -1108,10 +1108,12 @@ DUCKDB
|
|
|
1108
1108
|
|
|
1109
1109
|
: A [DuckDB](https://duckdb.org/) file-based database. If the specified database file does not exist, it will be created. If the database file already exists and has a table of the same name as the table or view that is exported, then the existing table will be replaced unless the "APPEND" keyword is used. When "APPEND" is used and the table already exists, an error will occur. The "DESCRIPTION" keyword is ignored when this format is used. DuckDB database files cannot be created in a zipfile; if the "ZIPFILE" keyword is used, an error will occur.
|
|
1110
1110
|
|
|
1111
|
+
When exporting a query result from a typed database (PostgreSQL, MySQL, etc.), the column schema is inferred from the Python types returned by the database driver rather than re-parsed from the string shape of values. A `TEXT` column whose values look like timestamps or numbers is exported as `VARCHAR`, not as `TIMESTAMP` or `INTEGER`. Python objects not natively bindable by DuckDB (e.g. PostGIS geometry) are stringified rather than raising a bind error.
|
|
1112
|
+
|
|
1111
1113
|
|
|
1112
1114
|
FEATHER
|
|
1113
1115
|
|
|
1114
|
-
: The Feather binary file format established by the [Apache Arrow](https://arrow.apache.org/) project. The "APPEND" and "DESCRIPTION" keywords are ignored when this format is used. Exporting data in this format
|
|
1116
|
+
: The Feather binary file format established by the [Apache Arrow](https://arrow.apache.org/) project. The "APPEND" and "DESCRIPTION" keywords are ignored when this format is used. Exporting data in this format uses [Polars](https://pola.rs/) and materializes the result in memory before writing, so there is a system-specific limit to the size of the data set that can be exported in this format. Install the `formats` extra, which includes `polars`, to export data in Feather format. Not all data types that may be present in a database can necessarily be exported to a Feather data file, so some data types, like timestamps, may have to be converted to character data before export. Data exported in Feather format cannot be written into a zipfile.
|
|
1115
1117
|
|
|
1116
1118
|
|
|
1117
1119
|
HDF5
|
|
@@ -1163,6 +1165,8 @@ SQLITE
|
|
|
1163
1165
|
|
|
1164
1166
|
: A [SQLite](https://www.sqlite.org/index.html) file-based database. If the specified database file does not exist, it will be created. If the database file already exists and has a table of the same name as the table or view that is exported, then the existing table will be replaced unless the "APPEND" keyword is used. When "APPEND" is used and the table already exists, an error will occur. The "DESCRIPTION" keyword is ignored when this format is used. SQLite database files cannot be created in a zipfile; if the "ZIPFILE" keyword is used, an error will occur.
|
|
1165
1167
|
|
|
1168
|
+
When exporting a query result from a typed database (PostgreSQL, MySQL, etc.), the column schema is inferred from the Python types returned by the database driver rather than re-parsed from the string shape of values. A `TEXT` column whose values look like timestamps or numbers is exported as a text column, not as `TIMESTAMP` or `INTEGER`. `decimal.Decimal` values are stored as their exact string representation (SQLite's NUMERIC affinity handles them losslessly). Python objects not natively bindable by SQLite (e.g. PostGIS geometry) are stringified rather than raising a bind error.
|
|
1169
|
+
|
|
1166
1170
|
|
|
1167
1171
|
|
|
1168
1172
|
ODS
|
|
@@ -1905,7 +1909,7 @@ The delimiter characters that will be recognized in a text file, and that can be
|
|
|
1905
1909
|
|
|
1906
1910
|
The SKIP key phrase specifies the number of lines (or rows) at the beginning of the file (or worksheet) to discard before evaluating the remainder of the input as a data table.
|
|
1907
1911
|
|
|
1908
|
-
If the NEW keyword is used, the input will be scanned to determine the data type of each column, and a CREATE TABLE statement run to create a new table for the data. Scanning of the file to determine data formats is separate from the scanning that may be done to determine the quote and delimiter characters. If the table already exists when the NEW keyword is used, a fatal error will result and *execsql* will halt. If the REPLACEMENT keyword is used, the result is the same as if the NEW keyword were used, except that an existing table of the given name will be deleted first. *execsql* uses a "drop table" statement to drop an existing table, and the "drop table" statement may not succeed if there are dependencies on that table (see the discussion of [implicit drop table
|
|
1912
|
+
If the NEW keyword is used, the input will be scanned to determine the data type of each column, and a CREATE TABLE statement run to create a new table for the data. Scanning of the file to determine data formats is separate from the scanning that may be done to determine the quote and delimiter characters. If the table already exists when the NEW keyword is used, a fatal error will result and *execsql* will halt. If the REPLACEMENT keyword is used, the result is the same as if the NEW keyword were used, except that an existing table of the given name will be deleted first. This replacement is not atomic: if the drop succeeds but a later create or load step fails, the old table has already been removed. If the drop fails, *execsql* stops the IMPORT command before creating or loading the replacement table. *execsql* uses a "drop table" statement to drop an existing table, and the "drop table" statement may not succeed if there are dependencies on that table (see the discussion of [implicit drop table
|
|
1909
1913
|
statements](../guides/sql_syntax.md#implicit_drop)). If the table to be dropped does not exist, an informational message will be written to the log.
|
|
1910
1914
|
|
|
1911
1915
|
If neither the NEW or REPLACEMENT keywords are used, the table must exist, must have column names identical to those in the input data, and columns must have data types that are compatible with (though not necessarily identical to) those in the input data. If neither the NEW or REPLACEMENT keywords are used, the input data is not scanned to determine the data type of each column.
|
|
@@ -3061,7 +3065,7 @@ On non-POSIX operating systems (specifically, Windows), any backslashes in the c
|
|
|
3061
3065
|
|
|
3062
3066
|
The command line that is run will be automatically [logged](../guides/logging.md#logging) in `execsql.log`.
|
|
3063
3067
|
|
|
3064
|
-
The exit status of the command that is invoked will be stored in the [system variable](substitution_vars.md#system_vars) \$SYSTEM_CMD_EXIT_STATUS if the CONTINUE keyword has not been used. When the CONTINUE keyword is used, the process ID of the background process is stored in \$SYSTEM_CMD_PID.
|
|
3068
|
+
The exit status of the command that is invoked will be stored in the [system variable](substitution_vars.md#system_vars) \$SYSTEM_CMD_EXIT_STATUS if the CONTINUE keyword has not been used. A foreground command can be bounded with the [`system_cmd_timeout`](configuration.md#system_cmd_timeout) configuration setting; when a command times out, \$SYSTEM_CMD_EXIT_STATUS is set to `124` and the metacommand fails. When the CONTINUE keyword is used, the process ID of the background process is stored in \$SYSTEM_CMD_PID, and the background process is detached rather than timeout-managed.
|
|
3065
3069
|
|
|
3066
3070
|
|
|
3067
3071
|
## TIMER
|
|
@@ -109,9 +109,9 @@ This catches mainstream cloud, payment, observability, and VCS conventions: `AWS
|
|
|
109
109
|
|
|
110
110
|
## File System Access { #filesystem }
|
|
111
111
|
|
|
112
|
-
execsql can read and write any file that the process user has permission to access.
|
|
112
|
+
By default, execsql can read and write any file that the process user has permission to access. Configure containment roots for shared or semi-trusted environments; otherwise path arguments may resolve outside the current directory.
|
|
113
113
|
|
|
114
|
-
The [`INCLUDE`](metacommands.md#include) metacommand executes a script from any accessible path
|
|
114
|
+
The [`INCLUDE`](metacommands.md#include) metacommand executes a script from any accessible path unless `include_root` is set. If the included path is constructed from a variable, an attacker who controls that variable can cause execsql to execute an arbitrary script file.
|
|
115
115
|
|
|
116
116
|
```sql
|
|
117
117
|
-- Risky: included path derived from a variable
|
|
@@ -121,6 +121,18 @@ The [`INCLUDE`](metacommands.md#include) metacommand executes a script from any
|
|
|
121
121
|
|
|
122
122
|
Validate any variable used to construct file paths before use in file-related metacommands.
|
|
123
123
|
|
|
124
|
+
## Path containment roots { #path-containment-roots }
|
|
125
|
+
|
|
126
|
+
Containment roots are opt-in. When configured, paths must resolve under the configured root; attempts to escape with `../`, absolute paths, drive letters, or UNC paths are rejected.
|
|
127
|
+
|
|
128
|
+
| Setting | Applies to | Default |
|
|
129
|
+
| --------------- | ----------------------------------------- | ------------ |
|
|
130
|
+
| `include_root` | `INCLUDE` and `EXECUTE SCRIPT` targets | unrestricted |
|
|
131
|
+
| `serve_root` | `SERVE` targets | unrestricted |
|
|
132
|
+
| `template_root` | Jinja2 and `string.Template` loader paths | unrestricted |
|
|
133
|
+
|
|
134
|
+
These roots do not contain every filesystem operation. In particular, `EXPORT` and `WRITE` output paths are governed by their output-directory behavior, and remain writable anywhere the process user can write unless separately constrained by how you invoke execsql.
|
|
135
|
+
|
|
124
136
|
## Email (SMTP) { #smtp }
|
|
125
137
|
|
|
126
138
|
TLS is off by default. The relevant `execsql.conf` settings are:
|
|
@@ -24,9 +24,9 @@ update-hooks:
|
|
|
24
24
|
|
|
25
25
|
# ── Code Quality ──────────────────────────────────────────────────────────────
|
|
26
26
|
|
|
27
|
-
# Lint,
|
|
27
|
+
# Lint, type-check, and test
|
|
28
28
|
[group('quality')]
|
|
29
|
-
check: format lint test
|
|
29
|
+
check: format lint typecheck test
|
|
30
30
|
|
|
31
31
|
# Run linter
|
|
32
32
|
[group('quality')]
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "execsql2"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.22.0"
|
|
8
8
|
description = "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."
|
|
9
9
|
readme = { file = "README.md", content-type = "text/markdown" }
|
|
10
10
|
license = { file = "LICENSE.txt" }
|
|
@@ -178,7 +178,7 @@ skip-magic-trailing-comma = false
|
|
|
178
178
|
line-ending = "auto"
|
|
179
179
|
|
|
180
180
|
[tool.bumpversion]
|
|
181
|
-
current_version = "2.
|
|
181
|
+
current_version = "2.22.0"
|
|
182
182
|
commit = true
|
|
183
183
|
tag = true
|
|
184
184
|
tag_name = "v{new_version}"
|
|
@@ -236,6 +236,15 @@ exclude = [
|
|
|
236
236
|
"_execsql/",
|
|
237
237
|
]
|
|
238
238
|
|
|
239
|
+
[[tool.mypy.overrides]]
|
|
240
|
+
# numpy is only reached transitively (via polars and duckdb's stubs), and
|
|
241
|
+
# numpy >= 2.5 stubs use PEP 695 `type` statements that fail to parse under
|
|
242
|
+
# python_version 3.10. follow_imports_for_stubs is required for the skip to
|
|
243
|
+
# apply to numpy's .pyi files.
|
|
244
|
+
module = ["numpy", "numpy.*"]
|
|
245
|
+
follow_imports = "skip"
|
|
246
|
+
follow_imports_for_stubs = true
|
|
247
|
+
|
|
239
248
|
[tool.typos.default.extend-words]
|
|
240
249
|
# SQL table alias used throughout upsert templates
|
|
241
250
|
ot = "ot"
|