execsql2 2.16.1__tar.gz → 2.16.3__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.16.1 → execsql2-2.16.3}/CHANGELOG.md +16 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/CONTRIBUTING.md +0 -20
- {execsql2-2.16.1 → execsql2-2.16.3}/PKG-INFO +1 -1
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/about/divergence.md +2 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/pyproject.toml +2 -2
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/script/executor.py +5 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/script/parser.py +10 -2
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/cli/test_cli_run.py +173 -1
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/test_executor.py +20 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/test_plugins.py +122 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/utils/test_strings.py +5 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/uv.lock +1 -1
- {execsql2-2.16.1 → execsql2-2.16.3}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/.github/workflows/ci-cd.yml +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/.gitignore +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/.pre-commit-config.yaml +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/.pre-commit-hooks.yaml +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/.python-version +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/.readthedocs.yaml +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/LICENSE.txt +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/NOTICE +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/README.md +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/SECURITY.md +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/about/contributors.md +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/about/copyright.md +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/api/cli.md +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/api/db.md +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/api/exporters.md +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/api/importers.md +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/api/index.md +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/api/metacommands.md +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/dev/adding_db_adapters.md +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/dev/adding_exporters.md +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/dev/adding_importers.md +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/dev/adding_metacommands.md +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/dev/architecture.md +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/getting-started/installation.md +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/getting-started/requirements.md +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/getting-started/syntax.md +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/guides/debugging.md +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/guides/documentation.md +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/guides/encoding.md +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/guides/examples.md +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/guides/formatter.md +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/guides/logging.md +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/guides/sql_syntax.md +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/guides/usage.md +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/guides/using_scripts.md +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/images/Compare_planets.png +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/images/actions.png +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/images/actions2.png +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/images/checkboxes.png +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/images/connect.b64 +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/images/connect.png +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/images/create_conf.png +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/images/data_error1_screenshot.jpg +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/images/entry_form.png +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/images/execsql_console.png +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/images/execsql_logo_01.png +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/images/fatals.png +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/images/logo_small.png +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/images/pause_terminal.png +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/images/pause_terminal_sm.b64 +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/images/pause_terminal_sm.png +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/images/prompt_compare.png +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/images/set_build_commands.jpg +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/images/unit_conversions.b64 +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/images/unit_conversions_029.png +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/images/unmatched.png +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/images/vim_execsql_highlight.png +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/index.md +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/reference/configuration.md +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/reference/metacommands.md +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/reference/security.md +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/docs/reference/substitution_vars.md +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/extras/plugin-template/README.md +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/extras/plugin-template/pyproject.toml +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/extras/plugin-template/src/execsql_plugin_YOURNAME/__init__.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/extras/plugin-template/tests/test_plugin.py.example +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/extras/vscode-execsql/README.md +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/extras/vscode-execsql/package.json +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/extras/vscode-execsql/syntaxes/execsql.tmLanguage.json +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/justfile +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/scripts/generate_vscode_grammar.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/__init__.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/__main__.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/api.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/cli/__init__.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/cli/dsn.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/cli/help.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/cli/lint.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/cli/lint_ast.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/cli/run.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/config.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/db/__init__.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/db/access.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/db/base.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/db/dsn.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/db/duckdb.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/db/factory.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/db/firebird.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/db/mysql.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/db/oracle.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/db/postgres.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/db/sqlite.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/db/sqlserver.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/debug/__init__.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/debug/repl.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/exceptions.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/exporters/__init__.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/exporters/base.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/exporters/delimited.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/exporters/duckdb.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/exporters/feather.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/exporters/html.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/exporters/json.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/exporters/latex.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/exporters/markdown.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/exporters/ods.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/exporters/parquet.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/exporters/pretty.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/exporters/protocol.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/exporters/raw.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/exporters/sqlite.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/exporters/templates.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/exporters/values.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/exporters/xls.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/exporters/xlsx.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/exporters/xml.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/exporters/yaml.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/exporters/zip.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/format.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/gui/__init__.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/gui/base.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/gui/console.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/gui/desktop.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/gui/tui.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/importers/__init__.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/importers/base.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/importers/csv.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/importers/feather.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/importers/json.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/importers/ods.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/importers/xls.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/metacommands/__init__.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/metacommands/conditions.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/metacommands/connect.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/metacommands/control.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/metacommands/data.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/metacommands/debug.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/metacommands/dispatch.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/metacommands/io.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/metacommands/io_export.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/metacommands/io_fileops.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/metacommands/io_import.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/metacommands/io_write.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/metacommands/prompt.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/metacommands/script_ext.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/metacommands/system.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/metacommands/upsert.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/models.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/parser.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/plugins.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/py.typed +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/script/__init__.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/script/ast.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/script/control.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/script/engine.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/script/variables.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/state.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/types.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/utils/__init__.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/utils/auth.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/utils/crypto.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/utils/datetime.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/utils/errors.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/utils/fileio.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/utils/gui.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/utils/mail.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/utils/numeric.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/utils/regex.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/utils/strings.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/src/execsql/utils/timer.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/templates/README.md +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/templates/config_settings.sqlite +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/templates/example_config_prompt.sql +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/templates/execsql.conf +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/templates/make_config_db.sql +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/templates/md_compare.sql +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/templates/md_glossary.sql +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/templates/md_upsert.sql +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/templates/pg_compare.sql +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/templates/pg_glossary.sql +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/templates/pg_upsert.sql +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/templates/script_template.sql +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/templates/ss_compare.sql +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/templates/ss_glossary.sql +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/templates/ss_upsert.sql +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/__init__.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/cli/__init__.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/cli/test_cli.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/cli/test_cli_e2e.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/cli/test_lint.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/cli/test_ping.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/cli/test_profile.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/conftest.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/db/__init__.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/db/test_base.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/db/test_db_adapters_mocked.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/db/test_dsn.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/db/test_duckdb.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/db/test_factory.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/db/test_postgres.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/db/test_sqlite.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/db/test_sqlite_extra.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/exporters/__init__.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/exporters/test_base.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/exporters/test_db.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/exporters/test_delimited.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/exporters/test_duckdb_exporter.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/exporters/test_exporters.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/exporters/test_feather.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/exporters/test_html_extended.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/exporters/test_html_latex.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/exporters/test_json.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/exporters/test_json_extended.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/exporters/test_latex_extended.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/exporters/test_markdown.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/exporters/test_ods.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/exporters/test_parquet.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/exporters/test_pretty_extended.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/exporters/test_raw_extended.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/exporters/test_sqlite_exporter.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/exporters/test_templates.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/exporters/test_templates_extended.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/exporters/test_values_extended.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/exporters/test_xls_xlsx.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/exporters/test_xlsx.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/exporters/test_xml.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/exporters/test_yaml.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/exporters/test_zip.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/gui/__init__.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/gui/test_backends.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/gui/test_compare_stats.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/gui/test_compute_row_diffs.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/importers/__init__.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/importers/test_base_extended.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/importers/test_csv_edge_cases.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/importers/test_csv_importer.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/importers/test_feather_importer.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/importers/test_json_importer.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/importers/test_ods_importer.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/importers/test_xls_importer.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/integration/__init__.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/integration/conftest.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/integration/test_dsn.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/integration/test_duckdb.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/integration/test_mysql.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/integration/test_postgres.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/integration/test_sqlite.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/metacommands/__init__.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/metacommands/test_assert.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/metacommands/test_breakpoint.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/metacommands/test_connect.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/metacommands/test_io_export.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/metacommands/test_io_import.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/metacommands/test_metacommands.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/metacommands/test_metacommands_connect.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/metacommands/test_metacommands_data.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/metacommands/test_metacommands_extended.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/metacommands/test_metacommands_fileops_extra.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/metacommands/test_metacommands_io.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/metacommands/test_metacommands_io_write_extra.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/metacommands/test_metacommands_script_ext.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/metacommands/test_metacommands_system.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/metacommands/test_metacommands_system_extra.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/metacommands/test_pg_upsert.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/metacommands/test_row_count.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/scripts/__init__.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/scripts/fixtures/control_flow.sql +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/scripts/fixtures/io_roundtrip.sql +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/scripts/fixtures/parse_only/parse_tree.sql +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/scripts/fixtures/smoke.sql +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/scripts/test_sql_scripts.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/test_api.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/test_ast.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/test_ast_parser.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/test_config.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/test_config_data.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/test_config_extended.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/test_debug_repl.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/test_engine.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/test_error_messages.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/test_exceptions.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/test_format.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/test_mail.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/test_models.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/test_package.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/test_parser.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/test_registry.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/test_script.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/test_state.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/test_types.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/utils/__init__.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/utils/test_auth.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/utils/test_auth_extra.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/utils/test_crypto.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/utils/test_datetime.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/utils/test_errors.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/utils/test_errors_extra.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/utils/test_fileio.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/utils/test_fileio_extra.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/utils/test_numeric.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/utils/test_regex.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/utils/test_timer.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/tests/utils/test_timer_extra.py +0 -0
- {execsql2-2.16.1 → execsql2-2.16.3}/zensical.toml +0 -0
|
@@ -13,6 +13,22 @@ ______________________________________________________________________
|
|
|
13
13
|
|
|
14
14
|
______________________________________________________________________
|
|
15
15
|
|
|
16
|
+
## [2.16.3] - 2026-04-30
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- `BEGIN SCRIPT name(params)` without a space before the opening parenthesis now parses correctly. The AST parser regex required whitespace between the script name and parameter list, causing `BEGIN SCRIPT` to be silently ignored and the matching `END SCRIPT` to fail with "Unmatched END SCRIPT metacommand."
|
|
21
|
+
|
|
22
|
+
______________________________________________________________________
|
|
23
|
+
|
|
24
|
+
## [2.16.2] - 2026-04-30
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- INCLUDE with quoted paths (e.g., `-- !x! INCLUDE "!!path!!/file.sql"`) now strips the surrounding quotes before resolving the file path. The AST parser captured the full target text including quotes, but the legacy dispatch regex stripped them — quoted INCLUDE paths would fail with "File does not exist" even when the file was present on disk.
|
|
29
|
+
|
|
30
|
+
______________________________________________________________________
|
|
31
|
+
|
|
16
32
|
## [2.16.1] - 2026-04-30
|
|
17
33
|
|
|
18
34
|
### Fixed
|
|
@@ -24,26 +24,6 @@ uv tool install rust-just # via uv
|
|
|
24
24
|
|
|
25
25
|
Run `just` with no arguments to list all available recipes.
|
|
26
26
|
|
|
27
|
-
```
|
|
28
|
-
just sync # Sync dependencies from lockfile
|
|
29
|
-
just update-hooks # Update pre-commit hooks to latest versions
|
|
30
|
-
|
|
31
|
-
just lint # Run ruff linter + formatter
|
|
32
|
-
just pre-commit # Run all pre-commit hooks against every file
|
|
33
|
-
just test # Run tests against the active Python version
|
|
34
|
-
just test-all # Run tests across all supported Python versions (3.10–3.13)
|
|
35
|
-
just clean # Remove build artifacts and caches
|
|
36
|
-
|
|
37
|
-
just docs # Build the documentation site
|
|
38
|
-
just docs-serve # Serve docs locally at http://127.0.0.1:8000
|
|
39
|
-
|
|
40
|
-
just bump # Show available version bumps from current version
|
|
41
|
-
just bump-patch # Bump patch version (e.g. 2.0.0 → 2.0.1)
|
|
42
|
-
just bump-minor # Bump minor version (e.g. 2.0.0 → 2.1.0)
|
|
43
|
-
just bump-major # Bump major version (e.g. 2.0.0 → 3.0.0)
|
|
44
|
-
just bump-pre 2.0.0a1 # Tag a specific pre-release version
|
|
45
|
-
```
|
|
46
|
-
|
|
47
27
|
## Code Quality
|
|
48
28
|
|
|
49
29
|
Linting and formatting are handled by [ruff](https://docs.astral.sh/ruff/):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: execsql2
|
|
3
|
-
Version: 2.16.
|
|
3
|
+
Version: 2.16.3
|
|
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
|
|
@@ -312,6 +312,8 @@ These are behavioral changes driven by security or correctness issues in the ups
|
|
|
312
312
|
| Temp file creation TOCTOU race | `TempFileMgr.new_temp_fn()` discarded the `NamedTemporaryFile` handle, creating a race window. Now uses `tempfile.mkstemp()` for secure creation. Inherited from upstream. |
|
|
313
313
|
| `shlex.split` on Windows incorrect mode | Called without `posix=False` on Windows, mishandling backslash-heavy paths in SHELL commands. Inherited from upstream. |
|
|
314
314
|
| 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()`. |
|
|
315
|
+
| 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. |
|
|
316
|
+
| 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. |
|
|
315
317
|
|
|
316
318
|
______________________________________________________________________
|
|
317
319
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "execsql2"
|
|
7
|
-
version = "2.16.
|
|
7
|
+
version = "2.16.3"
|
|
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" }
|
|
@@ -165,7 +165,7 @@ skip-magic-trailing-comma = false
|
|
|
165
165
|
line-ending = "auto"
|
|
166
166
|
|
|
167
167
|
[tool.bumpversion]
|
|
168
|
-
current_version = "2.16.
|
|
168
|
+
current_version = "2.16.3"
|
|
169
169
|
commit = true
|
|
170
170
|
tag = true
|
|
171
171
|
tag_name = "v{new_version}"
|
|
@@ -760,6 +760,11 @@ def _execute_include_native(
|
|
|
760
760
|
effective_locals = _stack_localvars(ctx) or localvars
|
|
761
761
|
target = substitute_vars(node.target, effective_locals, ctx=ctx).strip()
|
|
762
762
|
|
|
763
|
+
# Strip surrounding quotes — the AST parser captures the full target
|
|
764
|
+
# including any quotes, but the legacy dispatch regex stripped them.
|
|
765
|
+
if len(target) >= 2 and target[0] in ('"', "'") and target[-1] == target[0]:
|
|
766
|
+
target = target[1:-1]
|
|
767
|
+
|
|
763
768
|
# Tilde expansion (matches x_include legacy handler)
|
|
764
769
|
if len(target) > 1 and target[0] == "~" and target[1] == os.sep:
|
|
765
770
|
target = str(Path.home() / target[2:])
|
|
@@ -60,7 +60,7 @@ __all__ = [
|
|
|
60
60
|
# ---------------------------------------------------------------------------
|
|
61
61
|
|
|
62
62
|
_BEGIN_SCRIPT_RX = re.compile(
|
|
63
|
-
r"^\s*(?:BEGIN|CREATE)\s+SCRIPT\s+(?P<name>\w+)(?P<paramexpr>\s
|
|
63
|
+
r"^\s*(?:BEGIN|CREATE)\s+SCRIPT\s+(?P<name>\w+)(?P<paramexpr>\s*.+)?$",
|
|
64
64
|
re.I,
|
|
65
65
|
)
|
|
66
66
|
_END_SCRIPT_RX = re.compile(
|
|
@@ -95,6 +95,14 @@ _INCLUDE_RX = re.compile(
|
|
|
95
95
|
re.I,
|
|
96
96
|
)
|
|
97
97
|
|
|
98
|
+
|
|
99
|
+
def _strip_quotes(s: str) -> str:
|
|
100
|
+
"""Strip a matching pair of surrounding quotes from *s*."""
|
|
101
|
+
if len(s) >= 2 and s[0] in ('"', "'") and s[-1] == s[0]:
|
|
102
|
+
return s[1:-1]
|
|
103
|
+
return s
|
|
104
|
+
|
|
105
|
+
|
|
98
106
|
_EXEC_SCRIPT_RX = re.compile(
|
|
99
107
|
r"^\s*(?:EXEC(?:UTE)?|RUN)\s+SCRIPT"
|
|
100
108
|
r"(?P<exists>\s+IF\s+EXISTS)?"
|
|
@@ -545,7 +553,7 @@ def _parse_lines(lines: Iterable[str], source_name: str) -> Script:
|
|
|
545
553
|
_current_body().append(
|
|
546
554
|
IncludeDirective(
|
|
547
555
|
span=SourceSpan(source_name, file_lineno),
|
|
548
|
-
target=m.group("target").strip(),
|
|
556
|
+
target=_strip_quotes(m.group("target").strip()),
|
|
549
557
|
if_exists=m.group("exists") is not None,
|
|
550
558
|
),
|
|
551
559
|
)
|
|
@@ -22,7 +22,7 @@ from unittest.mock import MagicMock, patch
|
|
|
22
22
|
import pytest
|
|
23
23
|
|
|
24
24
|
import execsql.state as _state
|
|
25
|
-
from execsql.cli.run import _connect_initial_db, _print_dry_run, _run
|
|
25
|
+
from execsql.cli.run import _apply_cli_options, _connect_initial_db, _print_dry_run, _run, _seed_early_subvars
|
|
26
26
|
from execsql.exceptions import ConfigError
|
|
27
27
|
|
|
28
28
|
|
|
@@ -1312,3 +1312,175 @@ class TestPositionalNetworkRouting:
|
|
|
1312
1312
|
)
|
|
1313
1313
|
# server set by DSN, positional should go to db
|
|
1314
1314
|
assert _state.conf.db == "extra_db"
|
|
1315
|
+
|
|
1316
|
+
|
|
1317
|
+
# ---------------------------------------------------------------------------
|
|
1318
|
+
# _seed_early_subvars — unit tests for exception/filter branches
|
|
1319
|
+
# ---------------------------------------------------------------------------
|
|
1320
|
+
|
|
1321
|
+
|
|
1322
|
+
class TestSeedEarlySubvars:
|
|
1323
|
+
"""Unit tests for _seed_early_subvars() covering the sensitive-key filter
|
|
1324
|
+
and the exception-swallowing branch for invalid env-var names."""
|
|
1325
|
+
|
|
1326
|
+
def test_sensitive_key_filtered_out(self):
|
|
1327
|
+
"""Env vars whose names contain a sensitive substring are not seeded.
|
|
1328
|
+
|
|
1329
|
+
This exercises the ``continue`` branch (line 211 in run.py) where a key
|
|
1330
|
+
such as ``MY_SECRET_KEY`` is skipped before ``add_substitution`` is called.
|
|
1331
|
+
"""
|
|
1332
|
+
with patch.dict("os.environ", {"MY_SECRET_KEY": "s3cr3t"}, clear=False):
|
|
1333
|
+
subvars = _seed_early_subvars()
|
|
1334
|
+
# The sensitive var must not appear with the & prefix.
|
|
1335
|
+
assert subvars.varvalue("&MY_SECRET_KEY") is None
|
|
1336
|
+
assert subvars.varvalue("&my_secret_key") is None
|
|
1337
|
+
|
|
1338
|
+
def test_invalid_env_var_name_is_silently_skipped(self):
|
|
1339
|
+
"""An env var whose name produces an invalid substitution key does not raise.
|
|
1340
|
+
|
|
1341
|
+
Keys that contain characters outside word-character range (e.g. a hyphen)
|
|
1342
|
+
produce a variable name like ``&MY-VAR`` which fails SubVarSet.check_var_name().
|
|
1343
|
+
The ``except Exception: pass`` block (lines 214-215) swallows the error.
|
|
1344
|
+
"""
|
|
1345
|
+
# Use a key containing a hyphen — illegal in an execsql variable name.
|
|
1346
|
+
with patch.dict("os.environ", {"EXECSQL-INVALID-KEY": "value"}, clear=False):
|
|
1347
|
+
# Must not raise even though add_substitution will raise ErrInfo.
|
|
1348
|
+
subvars = _seed_early_subvars()
|
|
1349
|
+
# The variable should simply be absent from the internal dict rather than
|
|
1350
|
+
# causing a crash. Check the internal dict directly to avoid calling
|
|
1351
|
+
# varvalue() with an invalid key (which would itself raise).
|
|
1352
|
+
assert "&execsql-invalid-key" not in subvars._subs_dict
|
|
1353
|
+
|
|
1354
|
+
|
|
1355
|
+
# ---------------------------------------------------------------------------
|
|
1356
|
+
# _apply_cli_options — unit tests for default-fallback branches
|
|
1357
|
+
# ---------------------------------------------------------------------------
|
|
1358
|
+
|
|
1359
|
+
|
|
1360
|
+
class TestApplyCliOptionsDefaults:
|
|
1361
|
+
"""Unit tests for _apply_cli_options() that exercise the default-value
|
|
1362
|
+
fallback branches. These branches are only reachable when conf fields are
|
|
1363
|
+
``None`` (i.e. not set by ConfigData defaults), which never happens when
|
|
1364
|
+
going through _run() — hence these tests call _apply_cli_options() directly
|
|
1365
|
+
with a hand-crafted SimpleNamespace conf.
|
|
1366
|
+
"""
|
|
1367
|
+
|
|
1368
|
+
def _conf(self, **kwargs):
|
|
1369
|
+
"""Return a minimal SimpleNamespace conf with all relevant fields set to
|
|
1370
|
+
``None`` so that every default-fallback branch in _apply_cli_options()
|
|
1371
|
+
becomes reachable."""
|
|
1372
|
+
defaults = {
|
|
1373
|
+
"username": None,
|
|
1374
|
+
"passwd_prompt": True,
|
|
1375
|
+
"db_encoding": None,
|
|
1376
|
+
"script_encoding": None,
|
|
1377
|
+
"output_encoding": None,
|
|
1378
|
+
"import_encoding": None,
|
|
1379
|
+
"import_buffer": None,
|
|
1380
|
+
"make_export_dirs": None,
|
|
1381
|
+
"boolean_int": None,
|
|
1382
|
+
"scan_lines": None,
|
|
1383
|
+
"gui_level": None,
|
|
1384
|
+
"gui_framework": None,
|
|
1385
|
+
"db_type": None,
|
|
1386
|
+
"user_logfile": False,
|
|
1387
|
+
"port": None,
|
|
1388
|
+
"access_username": None,
|
|
1389
|
+
"new_db": False,
|
|
1390
|
+
"export_output_dir": None,
|
|
1391
|
+
"show_progress": False,
|
|
1392
|
+
}
|
|
1393
|
+
defaults.update(kwargs)
|
|
1394
|
+
return SimpleNamespace(**defaults)
|
|
1395
|
+
|
|
1396
|
+
def _call(self, conf, **kwargs):
|
|
1397
|
+
"""Invoke _apply_cli_options() with the supplied conf and kwargs,
|
|
1398
|
+
filling in None/False defaults for any omitted parameters."""
|
|
1399
|
+
defaults = {
|
|
1400
|
+
"user": None,
|
|
1401
|
+
"no_passwd": False,
|
|
1402
|
+
"database_encoding": None,
|
|
1403
|
+
"script_encoding": None,
|
|
1404
|
+
"output_encoding": None,
|
|
1405
|
+
"import_encoding": None,
|
|
1406
|
+
"import_buffer": None,
|
|
1407
|
+
"make_dirs": None,
|
|
1408
|
+
"boolean_int": None,
|
|
1409
|
+
"scanlines": None,
|
|
1410
|
+
"use_gui": None,
|
|
1411
|
+
"gui_framework": None,
|
|
1412
|
+
"db_type": None,
|
|
1413
|
+
"user_logfile": False,
|
|
1414
|
+
"port": None,
|
|
1415
|
+
"new_db": False,
|
|
1416
|
+
"output_dir": None,
|
|
1417
|
+
"progress": False,
|
|
1418
|
+
}
|
|
1419
|
+
defaults.update(kwargs)
|
|
1420
|
+
_apply_cli_options(conf, **defaults)
|
|
1421
|
+
|
|
1422
|
+
def test_script_encoding_defaults_to_utf8_when_conf_is_none(self):
|
|
1423
|
+
"""When conf.script_encoding is None and no CLI flag is given, default to 'utf8'."""
|
|
1424
|
+
conf = self._conf(script_encoding=None)
|
|
1425
|
+
self._call(conf, script_encoding=None)
|
|
1426
|
+
assert conf.script_encoding == "utf8"
|
|
1427
|
+
|
|
1428
|
+
def test_output_encoding_defaults_to_utf8_when_conf_is_none(self):
|
|
1429
|
+
"""When conf.output_encoding is None and no CLI flag is given, default to 'utf8'."""
|
|
1430
|
+
conf = self._conf(output_encoding=None)
|
|
1431
|
+
self._call(conf, output_encoding=None)
|
|
1432
|
+
assert conf.output_encoding == "utf8"
|
|
1433
|
+
|
|
1434
|
+
def test_import_encoding_defaults_to_utf8_when_conf_is_none(self):
|
|
1435
|
+
"""When conf.import_encoding is None and no CLI flag is given, default to 'utf8'."""
|
|
1436
|
+
conf = self._conf(import_encoding=None)
|
|
1437
|
+
self._call(conf, import_encoding=None)
|
|
1438
|
+
assert conf.import_encoding == "utf8"
|
|
1439
|
+
|
|
1440
|
+
def test_scan_lines_defaults_to_100_when_conf_is_none(self):
|
|
1441
|
+
"""When conf.scan_lines is None and scanlines CLI arg is None, default to 100."""
|
|
1442
|
+
conf = self._conf(scan_lines=None)
|
|
1443
|
+
self._call(conf, scanlines=None)
|
|
1444
|
+
assert conf.scan_lines == 100
|
|
1445
|
+
|
|
1446
|
+
def test_db_type_defaults_to_l_when_conf_is_none(self):
|
|
1447
|
+
"""When conf.db_type is None and db_type CLI arg is None, default to 'l' (SQLite)."""
|
|
1448
|
+
conf = self._conf(db_type=None)
|
|
1449
|
+
self._call(conf, db_type=None)
|
|
1450
|
+
assert conf.db_type == "l"
|
|
1451
|
+
|
|
1452
|
+
def test_gui_level_out_of_range_raises_config_error(self):
|
|
1453
|
+
"""A gui_level value outside 0-3 raises ConfigError after being set by use_gui."""
|
|
1454
|
+
conf = self._conf(gui_level=None)
|
|
1455
|
+
with pytest.raises(ConfigError, match="Invalid GUI level"):
|
|
1456
|
+
self._call(conf, use_gui="5")
|
|
1457
|
+
|
|
1458
|
+
def test_cli_script_encoding_overrides_none_conf(self):
|
|
1459
|
+
"""A CLI-supplied script_encoding wins over None in conf."""
|
|
1460
|
+
conf = self._conf(script_encoding=None)
|
|
1461
|
+
self._call(conf, script_encoding="latin-1")
|
|
1462
|
+
assert conf.script_encoding == "latin-1"
|
|
1463
|
+
|
|
1464
|
+
def test_cli_output_encoding_overrides_none_conf(self):
|
|
1465
|
+
"""A CLI-supplied output_encoding wins over None in conf."""
|
|
1466
|
+
conf = self._conf(output_encoding=None)
|
|
1467
|
+
self._call(conf, output_encoding="cp1252")
|
|
1468
|
+
assert conf.output_encoding == "cp1252"
|
|
1469
|
+
|
|
1470
|
+
def test_cli_import_encoding_overrides_none_conf(self):
|
|
1471
|
+
"""A CLI-supplied import_encoding wins over None in conf."""
|
|
1472
|
+
conf = self._conf(import_encoding=None)
|
|
1473
|
+
self._call(conf, import_encoding="utf-16")
|
|
1474
|
+
assert conf.import_encoding == "utf-16"
|
|
1475
|
+
|
|
1476
|
+
def test_cli_scanlines_overrides_none_conf(self):
|
|
1477
|
+
"""A CLI-supplied scanlines wins over None in conf (no default applied)."""
|
|
1478
|
+
conf = self._conf(scan_lines=None)
|
|
1479
|
+
self._call(conf, scanlines=500)
|
|
1480
|
+
assert conf.scan_lines == 500
|
|
1481
|
+
|
|
1482
|
+
def test_cli_db_type_overrides_none_conf(self):
|
|
1483
|
+
"""A CLI-supplied db_type wins over None in conf."""
|
|
1484
|
+
conf = self._conf(db_type=None)
|
|
1485
|
+
self._call(conf, db_type="p")
|
|
1486
|
+
assert conf.db_type == "p"
|
|
@@ -616,6 +616,26 @@ class TestInclude:
|
|
|
616
616
|
assert result.returncode == 0
|
|
617
617
|
assert _query_db(tmp_path, "SELECT x FROM t") == [(99,)]
|
|
618
618
|
|
|
619
|
+
def test_include_quoted_path(self, tmp_path):
|
|
620
|
+
"""INCLUDE with double-quoted path strips quotes correctly."""
|
|
621
|
+
included = tmp_path / "helper.sql"
|
|
622
|
+
included.write_text("INSERT INTO t VALUES (7);")
|
|
623
|
+
script = 'CREATE TABLE t (x INT);\n-- !x! INCLUDE "helper.sql"'
|
|
624
|
+
result = _run_ast(script, tmp_path)
|
|
625
|
+
assert result.returncode == 0, result.stderr
|
|
626
|
+
assert _query_db(tmp_path, "SELECT x FROM t") == [(7,)]
|
|
627
|
+
|
|
628
|
+
def test_include_quoted_path_with_vars(self, tmp_path):
|
|
629
|
+
"""INCLUDE with quoted path containing substitution variables."""
|
|
630
|
+
subdir = tmp_path / "lib"
|
|
631
|
+
subdir.mkdir()
|
|
632
|
+
included = subdir / "helper.sql"
|
|
633
|
+
included.write_text("INSERT INTO t VALUES (8);")
|
|
634
|
+
script = f'-- !x! SUB mylib {subdir}\nCREATE TABLE t (x INT);\n-- !x! INCLUDE "!!mylib!!!!$pathsep!!helper.sql"'
|
|
635
|
+
result = _run_ast(script, tmp_path)
|
|
636
|
+
assert result.returncode == 0, result.stderr
|
|
637
|
+
assert _query_db(tmp_path, "SELECT x FROM t") == [(8,)]
|
|
638
|
+
|
|
619
639
|
def test_include_if_exists_missing(self, tmp_path):
|
|
620
640
|
"""INCLUDE IF EXISTS silently skips missing files."""
|
|
621
641
|
script = "CREATE TABLE t (x INT);\nINSERT INTO t VALUES (1);\n-- !x! INCLUDE IF EXISTS no_such_file.sql"
|
|
@@ -11,9 +11,12 @@ from execsql.plugins import (
|
|
|
11
11
|
ImporterEntry,
|
|
12
12
|
ImporterRegistry,
|
|
13
13
|
_load_entry_points,
|
|
14
|
+
discover_all_plugins,
|
|
14
15
|
discover_exporter_plugins,
|
|
15
16
|
discover_importer_plugins,
|
|
16
17
|
discover_metacommand_plugins,
|
|
18
|
+
get_exporter_registry,
|
|
19
|
+
get_importer_registry,
|
|
17
20
|
)
|
|
18
21
|
|
|
19
22
|
|
|
@@ -211,3 +214,122 @@ class TestListPluginsCli:
|
|
|
211
214
|
result = runner.invoke(app, ["--list-plugins"], catch_exceptions=False)
|
|
212
215
|
assert result.exit_code == 0
|
|
213
216
|
assert "my_awesome_plugin" in result.output
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
# ---------------------------------------------------------------------------
|
|
220
|
+
# ImporterRegistry — untested methods
|
|
221
|
+
# ---------------------------------------------------------------------------
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class TestImporterRegistryMethods:
|
|
225
|
+
def test_formats_returns_sorted_uppercase(self):
|
|
226
|
+
reg = ImporterRegistry()
|
|
227
|
+
reg.add("zebra", import_fn=lambda: None)
|
|
228
|
+
reg.add("alpha", import_fn=lambda: None)
|
|
229
|
+
assert reg.formats() == ["ALPHA", "ZEBRA"]
|
|
230
|
+
|
|
231
|
+
def test_entries_returns_all(self):
|
|
232
|
+
reg = ImporterRegistry()
|
|
233
|
+
reg.add("a", import_fn=lambda: None)
|
|
234
|
+
reg.add("b", import_fn=lambda: None)
|
|
235
|
+
assert len(reg.entries()) == 2
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
# ---------------------------------------------------------------------------
|
|
239
|
+
# Module-level singletons
|
|
240
|
+
# ---------------------------------------------------------------------------
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
class TestRegistrySingletons:
|
|
244
|
+
def test_get_exporter_registry_returns_instance(self):
|
|
245
|
+
import execsql.plugins as _plugins
|
|
246
|
+
|
|
247
|
+
# Reset global to ensure we exercise the None-branch.
|
|
248
|
+
original = _plugins._exporter_registry
|
|
249
|
+
_plugins._exporter_registry = None
|
|
250
|
+
try:
|
|
251
|
+
reg = get_exporter_registry()
|
|
252
|
+
assert isinstance(reg, ExporterRegistry)
|
|
253
|
+
# Second call returns the same singleton.
|
|
254
|
+
assert get_exporter_registry() is reg
|
|
255
|
+
finally:
|
|
256
|
+
_plugins._exporter_registry = original
|
|
257
|
+
|
|
258
|
+
def test_get_importer_registry_returns_instance(self):
|
|
259
|
+
import execsql.plugins as _plugins
|
|
260
|
+
|
|
261
|
+
original = _plugins._importer_registry
|
|
262
|
+
_plugins._importer_registry = None
|
|
263
|
+
try:
|
|
264
|
+
reg = get_importer_registry()
|
|
265
|
+
assert isinstance(reg, ImporterRegistry)
|
|
266
|
+
assert get_importer_registry() is reg
|
|
267
|
+
finally:
|
|
268
|
+
_plugins._importer_registry = original
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
# ---------------------------------------------------------------------------
|
|
272
|
+
# _load_entry_points — error branch when entry_points() itself raises
|
|
273
|
+
# ---------------------------------------------------------------------------
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
class TestLoadEntryPointsError:
|
|
277
|
+
def test_entry_points_query_failure_returns_empty(self):
|
|
278
|
+
"""If entry_points() raises, _load_entry_points should return []."""
|
|
279
|
+
with patch("execsql.plugins.entry_points", side_effect=RuntimeError("registry broken")):
|
|
280
|
+
result = _load_entry_points(METACOMMAND_GROUP)
|
|
281
|
+
assert result == []
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
# ---------------------------------------------------------------------------
|
|
285
|
+
# discover_all_plugins
|
|
286
|
+
# ---------------------------------------------------------------------------
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
class TestDiscoverAllPlugins:
|
|
290
|
+
def test_discover_all_plugins_without_mcl_skips_metacommands(self):
|
|
291
|
+
"""When mcl is None, metacommand plugins are not attempted."""
|
|
292
|
+
with patch("execsql.plugins.entry_points", return_value=[]):
|
|
293
|
+
result = discover_all_plugins(mcl=None)
|
|
294
|
+
# Key for metacommand group must be absent since mcl is None.
|
|
295
|
+
from execsql.plugins import METACOMMAND_GROUP
|
|
296
|
+
|
|
297
|
+
assert METACOMMAND_GROUP not in result
|
|
298
|
+
# Exporter and importer counts are present.
|
|
299
|
+
from execsql.plugins import EXPORTER_GROUP, IMPORTER_GROUP
|
|
300
|
+
|
|
301
|
+
assert EXPORTER_GROUP in result
|
|
302
|
+
assert IMPORTER_GROUP in result
|
|
303
|
+
|
|
304
|
+
def test_discover_all_plugins_with_mcl_includes_metacommands(self):
|
|
305
|
+
"""When mcl is provided, all three plugin types are discovered."""
|
|
306
|
+
mock_mcl = MagicMock()
|
|
307
|
+
with patch("execsql.plugins.entry_points", return_value=[]):
|
|
308
|
+
result = discover_all_plugins(mcl=mock_mcl)
|
|
309
|
+
from execsql.plugins import EXPORTER_GROUP, IMPORTER_GROUP, METACOMMAND_GROUP
|
|
310
|
+
|
|
311
|
+
assert METACOMMAND_GROUP in result
|
|
312
|
+
assert EXPORTER_GROUP in result
|
|
313
|
+
assert IMPORTER_GROUP in result
|
|
314
|
+
|
|
315
|
+
def test_discover_exporter_plugin_registration_error_handled(self):
|
|
316
|
+
"""A plugin whose register function raises does not crash discover_exporter_plugins."""
|
|
317
|
+
mock_register = MagicMock(side_effect=ValueError("bad exporter"))
|
|
318
|
+
mock_ep = MagicMock()
|
|
319
|
+
mock_ep.name = "broken_exp"
|
|
320
|
+
mock_ep.load.return_value = mock_register
|
|
321
|
+
|
|
322
|
+
with patch("execsql.plugins.entry_points", return_value=[mock_ep]):
|
|
323
|
+
count = discover_exporter_plugins()
|
|
324
|
+
assert count == 0
|
|
325
|
+
|
|
326
|
+
def test_discover_importer_plugin_registration_error_handled(self):
|
|
327
|
+
"""A plugin whose register function raises does not crash discover_importer_plugins."""
|
|
328
|
+
mock_register = MagicMock(side_effect=ValueError("bad importer"))
|
|
329
|
+
mock_ep = MagicMock()
|
|
330
|
+
mock_ep.name = "broken_imp"
|
|
331
|
+
mock_ep.load.return_value = mock_register
|
|
332
|
+
|
|
333
|
+
with patch("execsql.plugins.entry_points", return_value=[mock_ep]):
|
|
334
|
+
count = discover_importer_plugins()
|
|
335
|
+
assert count == 0
|
|
@@ -222,3 +222,8 @@ class TestEncodingsMatch:
|
|
|
222
222
|
|
|
223
223
|
def test_koi8r_aliases(self):
|
|
224
224
|
assert encodings_match("koi8-r", "koi8r") is True
|
|
225
|
+
|
|
226
|
+
def test_leading_zero_normalization_matches(self):
|
|
227
|
+
# iso-8859-01 and iso-8859-1 differ only in a leading zero — the
|
|
228
|
+
# numeric-leading-zero stripping pass (line 133-137 in run.py) makes them equal.
|
|
229
|
+
assert encodings_match("iso-8859-01", "iso-8859-1") is True
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|