execsql2 2.16.18__tar.gz → 2.17.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.16.18 → execsql2-2.17.0}/CHANGELOG.md +12 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/PKG-INFO +1 -1
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/about/divergence.md +9 -9
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/reference/metacommands.md +18 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/pyproject.toml +2 -2
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/metacommands/upsert.py +0 -29
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/script/executor.py +12 -8
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/metacommands/test_pg_upsert.py +39 -75
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/test_executor.py +39 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/uv.lock +1 -1
- {execsql2-2.16.18 → execsql2-2.17.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/.github/workflows/ci-cd.yml +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/.gitignore +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/.pre-commit-config.yaml +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/.pre-commit-hooks.yaml +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/.python-version +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/.readthedocs.yaml +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/CONTRIBUTING.md +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/LICENSE.txt +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/NOTICE +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/README.md +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/SECURITY.md +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/about/contributors.md +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/about/copyright.md +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/api/cli.md +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/api/db.md +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/api/exporters.md +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/api/importers.md +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/api/index.md +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/api/metacommands.md +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/dev/adding_db_adapters.md +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/dev/adding_exporters.md +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/dev/adding_importers.md +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/dev/adding_metacommands.md +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/dev/architecture.md +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/getting-started/installation.md +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/getting-started/requirements.md +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/getting-started/syntax.md +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/guides/debugging.md +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/guides/documentation.md +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/guides/encoding.md +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/guides/examples.md +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/guides/formatter.md +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/guides/logging.md +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/guides/sql_syntax.md +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/guides/usage.md +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/guides/using_scripts.md +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/images/Compare_planets.png +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/images/actions.png +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/images/actions2.png +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/images/checkboxes.png +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/images/connect.b64 +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/images/connect.png +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/images/create_conf.png +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/images/data_error1_screenshot.jpg +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/images/entry_form.png +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/images/execsql_console.png +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/images/execsql_logo_01.png +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/images/fatals.png +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/images/logo_small.png +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/images/pause_terminal.png +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/images/pause_terminal_sm.b64 +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/images/pause_terminal_sm.png +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/images/prompt_compare.png +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/images/set_build_commands.jpg +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/images/unit_conversions.b64 +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/images/unit_conversions_029.png +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/images/unmatched.png +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/images/vim_execsql_highlight.png +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/index.md +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/reference/configuration.md +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/reference/security.md +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/docs/reference/substitution_vars.md +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/extras/plugin-template/README.md +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/extras/plugin-template/pyproject.toml +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/extras/plugin-template/src/execsql_plugin_YOURNAME/__init__.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/extras/plugin-template/tests/test_plugin.py.example +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/extras/vscode-execsql/README.md +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/extras/vscode-execsql/package.json +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/extras/vscode-execsql/syntaxes/execsql.tmLanguage.json +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/justfile +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/scripts/generate_vscode_grammar.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/__init__.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/__main__.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/api.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/cli/__init__.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/cli/dsn.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/cli/help.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/cli/lint.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/cli/lint_ast.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/cli/run.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/config.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/data/__init__.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/data/execsql.conf.template +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/db/__init__.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/db/access.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/db/base.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/db/dsn.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/db/duckdb.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/db/factory.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/db/firebird.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/db/mysql.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/db/oracle.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/db/postgres.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/db/sqlite.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/db/sqlserver.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/debug/__init__.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/debug/repl.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/exceptions.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/exporters/__init__.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/exporters/base.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/exporters/delimited.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/exporters/duckdb.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/exporters/feather.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/exporters/html.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/exporters/json.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/exporters/latex.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/exporters/markdown.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/exporters/ods.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/exporters/parquet.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/exporters/pretty.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/exporters/protocol.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/exporters/raw.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/exporters/sqlite.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/exporters/templates.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/exporters/values.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/exporters/xls.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/exporters/xlsx.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/exporters/xml.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/exporters/yaml.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/exporters/zip.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/format.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/gui/__init__.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/gui/base.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/gui/console.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/gui/desktop.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/gui/tui.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/importers/__init__.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/importers/base.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/importers/csv.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/importers/feather.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/importers/json.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/importers/ods.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/importers/xls.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/metacommands/__init__.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/metacommands/conditions.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/metacommands/connect.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/metacommands/control.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/metacommands/data.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/metacommands/debug.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/metacommands/dispatch.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/metacommands/io.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/metacommands/io_export.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/metacommands/io_fileops.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/metacommands/io_import.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/metacommands/io_write.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/metacommands/prompt.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/metacommands/script_ext.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/metacommands/system.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/models.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/parser.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/plugins.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/py.typed +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/script/__init__.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/script/ast.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/script/control.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/script/engine.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/script/parser.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/script/variables.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/state.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/types.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/utils/__init__.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/utils/auth.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/utils/crypto.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/utils/datetime.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/utils/errors.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/utils/fileio.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/utils/gui.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/utils/mail.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/utils/numeric.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/utils/regex.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/utils/strings.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/src/execsql/utils/timer.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/templates/README.md +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/templates/config_settings.sqlite +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/templates/example_config_prompt.sql +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/templates/execsql.conf +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/templates/make_config_db.sql +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/templates/md_compare.sql +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/templates/md_glossary.sql +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/templates/md_upsert.sql +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/templates/pg_compare.sql +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/templates/pg_glossary.sql +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/templates/pg_upsert.sql +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/templates/script_template.sql +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/templates/ss_compare.sql +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/templates/ss_glossary.sql +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/templates/ss_upsert.sql +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/__init__.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/cli/__init__.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/cli/test_cli.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/cli/test_cli_e2e.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/cli/test_cli_run.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/cli/test_lint.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/cli/test_ping.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/cli/test_profile.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/conftest.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/db/__init__.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/db/test_base.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/db/test_db_adapters_mocked.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/db/test_dsn.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/db/test_duckdb.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/db/test_factory.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/db/test_postgres.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/db/test_sqlite.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/db/test_sqlite_extra.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/exporters/__init__.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/exporters/test_base.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/exporters/test_db.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/exporters/test_delimited.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/exporters/test_duckdb_exporter.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/exporters/test_exporters.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/exporters/test_feather.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/exporters/test_html_extended.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/exporters/test_html_latex.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/exporters/test_json.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/exporters/test_json_extended.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/exporters/test_latex_extended.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/exporters/test_markdown.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/exporters/test_ods.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/exporters/test_parquet.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/exporters/test_pretty_extended.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/exporters/test_raw_extended.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/exporters/test_sqlite_exporter.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/exporters/test_templates.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/exporters/test_templates_extended.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/exporters/test_values_extended.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/exporters/test_xls_xlsx.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/exporters/test_xlsx.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/exporters/test_xml.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/exporters/test_yaml.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/exporters/test_zip.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/gui/__init__.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/gui/test_backends.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/gui/test_compare_stats.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/gui/test_compute_row_diffs.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/importers/__init__.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/importers/test_base_extended.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/importers/test_csv_edge_cases.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/importers/test_csv_importer.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/importers/test_feather_importer.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/importers/test_json_importer.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/importers/test_ods_importer.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/importers/test_xls_importer.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/integration/__init__.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/integration/conftest.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/integration/test_dsn.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/integration/test_duckdb.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/integration/test_mysql.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/integration/test_postgres.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/integration/test_sqlite.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/metacommands/__init__.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/metacommands/test_assert.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/metacommands/test_breakpoint.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/metacommands/test_connect.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/metacommands/test_io_export.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/metacommands/test_io_import.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/metacommands/test_metacommands.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/metacommands/test_metacommands_connect.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/metacommands/test_metacommands_data.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/metacommands/test_metacommands_extended.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/metacommands/test_metacommands_fileops_extra.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/metacommands/test_metacommands_io.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/metacommands/test_metacommands_io_write_extra.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/metacommands/test_metacommands_script_ext.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/metacommands/test_metacommands_system.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/metacommands/test_metacommands_system_extra.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/metacommands/test_row_count.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/metacommands/test_show_scripts.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/scripts/__init__.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/scripts/fixtures/control_flow.sql +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/scripts/fixtures/io_roundtrip.sql +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/scripts/fixtures/parse_only/parse_tree.sql +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/scripts/fixtures/smoke.sql +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/scripts/test_sql_scripts.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/test_api.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/test_ast.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/test_ast_parser.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/test_config.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/test_config_data.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/test_config_extended.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/test_debug_repl.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/test_engine.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/test_error_messages.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/test_exceptions.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/test_format.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/test_mail.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/test_models.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/test_package.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/test_parser.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/test_parser_params.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/test_plugins.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/test_registry.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/test_script.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/test_state.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/test_types.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/utils/__init__.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/utils/test_auth.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/utils/test_auth_extra.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/utils/test_crypto.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/utils/test_datetime.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/utils/test_errors.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/utils/test_errors_extra.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/utils/test_fileio.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/utils/test_fileio_extra.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/utils/test_numeric.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/utils/test_regex.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/utils/test_strings.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/utils/test_timer.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/tests/utils/test_timer_extra.py +0 -0
- {execsql2-2.16.18 → execsql2-2.17.0}/zensical.toml +0 -0
|
@@ -13,6 +13,18 @@ ______________________________________________________________________
|
|
|
13
13
|
|
|
14
14
|
______________________________________________________________________
|
|
15
15
|
|
|
16
|
+
## [2.17.0] - 2026-05-07
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- **Behavior change.** `PG_UPSERT`, `PG_UPSERT QA`, and `PG_UPSERT CHECK` no longer raise a metacommand error when QA checks fail. The outcome is reported via `$PG_UPSERT_QA_PASSED` (and the per-table `$PG_UPSERT_TABLE_QA_PASSED`) along with `$PG_UPSERT_RESULT_JSON`, so the script controls flow with `IF` or `ASSERT` instead of being forced into execsql's halt-on-error path. `EXPORT_FAILURES` still runs on QA failure, and the upsert is still skipped (no commit). Scripts that previously relied on a hard halt should add `ASSERT !!$PG_UPSERT_QA_PASSED!! = TRUE` (or branch via `IF`) at the call site.
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- `!!$COUNTER_N!!` references inside metacommands (e.g. `WRITE`, `IF`, `SUB`) now return the documented sequence `1, 2, 3, …` starting at 1. The AST executor was calling `substitute_vars()` twice per metacommand — once to detect a `BREAK` token before dispatch, then again inside the dispatch handler — which double-incremented every counter reference so the first read returned 2 and subsequent reads stepped by 2. Counter references in plain SQL statements were unaffected (they already substituted exactly once). The duplicate substitution also re-rolled `!!$RANDOM!!` and `!!$UUID!!` for any metacommand that referenced them, so those values are now stable across BREAK detection and dispatch as well.
|
|
25
|
+
|
|
26
|
+
______________________________________________________________________
|
|
27
|
+
|
|
16
28
|
## [2.16.18] - 2026-05-05
|
|
17
29
|
|
|
18
30
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: execsql2
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.17.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
|
|
@@ -46,15 +46,15 @@ ______________________________________________________________________
|
|
|
46
46
|
|
|
47
47
|
### Metacommands
|
|
48
48
|
|
|
49
|
-
| Metacommand | Description
|
|
50
|
-
| ----------------------- |
|
|
51
|
-
| `ASSERT` | Evaluate a condition and raise an error (halting the script) if it is false. Supports all IF conditions. Optional quoted failure message. Skipped in false IF blocks.
|
|
52
|
-
| `BREAKPOINT` | Pause script execution and drop into an interactive debug REPL. See [Debugging](#debugging) below for full details.
|
|
53
|
-
| `CONFIG SHOW_PROGRESS` | Enable the Rich progress bar for IMPORT operations at runtime.
|
|
54
|
-
| `CONFIG LOG_SQL` | Enable SQL query audit logging — writes executed SQL to the log file.
|
|
55
|
-
| `PG_UPSERT` | QA-checked, FK-dependency-ordered upserts from staging to base schema on PostgreSQL. Integrates [pg-upsert](https://pg-upsert.readthedocs.io/) as an optional dependency. Three modes: full pipeline, QA-only, and schema check. Supports `EXPORT_FAILURES`, `EXPORT_FORMAT`, `EXPORT_MAX_ROWS`, and `STRICT_COLUMNS` keywords. `STRICT_COLUMNS` forces all missing columns to be errors (requires `pg-upsert>=1.22.0`). |
|
|
56
|
-
| `IMPORT … FROM JSON` | Import a JSON file (array of objects or NDJSON) into a database table. Nested objects are flattened with dot-separated column names; arrays are stored as JSON strings.
|
|
57
|
-
| `SHOW SCRIPTS [<name>]` | Without a name, lists all registered SCRIPT definitions with parameter signatures and source locations. With a name, shows detail including parameters (with defaults), source file/line range, and docstring.
|
|
49
|
+
| Metacommand | Description |
|
|
50
|
+
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
51
|
+
| `ASSERT` | Evaluate a condition and raise an error (halting the script) if it is false. Supports all IF conditions. Optional quoted failure message. Skipped in false IF blocks. |
|
|
52
|
+
| `BREAKPOINT` | Pause script execution and drop into an interactive debug REPL. See [Debugging](#debugging) below for full details. |
|
|
53
|
+
| `CONFIG SHOW_PROGRESS` | Enable the Rich progress bar for IMPORT operations at runtime. |
|
|
54
|
+
| `CONFIG LOG_SQL` | Enable SQL query audit logging — writes executed SQL to the log file. |
|
|
55
|
+
| `PG_UPSERT` | QA-checked, FK-dependency-ordered upserts from staging to base schema on PostgreSQL. Integrates [pg-upsert](https://pg-upsert.readthedocs.io/) as an optional dependency. Three modes: full pipeline, QA-only, and schema check. Supports `EXPORT_FAILURES`, `EXPORT_FORMAT`, `EXPORT_MAX_ROWS`, and `STRICT_COLUMNS` keywords. `STRICT_COLUMNS` forces all missing columns to be errors (requires `pg-upsert>=1.22.0`). QA failure does not raise — the outcome is reported via `$PG_UPSERT_QA_PASSED` so the user's own `IF` / `ON ERROR HALT` decides control flow. |
|
|
56
|
+
| `IMPORT … FROM JSON` | Import a JSON file (array of objects or NDJSON) into a database table. Nested objects are flattened with dot-separated column names; arrays are stored as JSON strings. |
|
|
57
|
+
| `SHOW SCRIPTS [<name>]` | Without a name, lists all registered SCRIPT definitions with parameter signatures and source locations. With a name, shows detail including parameters (with defaults), source file/line range, and docstring. |
|
|
58
58
|
|
|
59
59
|
### SCRIPT Enhancements
|
|
60
60
|
|
|
@@ -2248,6 +2248,24 @@ Set after every `PG_UPSERT` execution:
|
|
|
2248
2248
|
-- !x! WRITE `!!$PG_UPSERT_RESULT_JSON!!`
|
|
2249
2249
|
```
|
|
2250
2250
|
|
|
2251
|
+
### QA failure behavior
|
|
2252
|
+
|
|
2253
|
+
`PG_UPSERT`, `PG_UPSERT QA`, and `PG_UPSERT CHECK` do **not** raise an error when QA checks fail. The outcome is reported via `$PG_UPSERT_QA_PASSED` (and the per-table `$PG_UPSERT_TABLE_QA_PASSED`), so the script controls flow with `IF` or with execsql's standard error-halt configuration. When QA fails, the metacommand still runs `EXPORT_FAILURES`, populates all `$PG_UPSERT_*` substitution variables, and skips the upsert (no commit). Pair with `ASSERT` to halt explicitly:
|
|
2254
|
+
|
|
2255
|
+
```sql
|
|
2256
|
+
-- Soft failure: branch on the result, keep running.
|
|
2257
|
+
-- !x! PG_UPSERT FROM staging TO public TABLES books, authors EXPORT_FAILURES "qa/"
|
|
2258
|
+
-- !x! IF !!$PG_UPSERT_QA_PASSED!! = TRUE
|
|
2259
|
+
-- !x! PG_UPSERT FROM staging TO public TABLES books, authors COMMIT
|
|
2260
|
+
-- !x! ELSE
|
|
2261
|
+
-- !x! WRITE "QA failed — fix sheet at !!$PG_UPSERT_EXPORT_PATH!!"
|
|
2262
|
+
-- !x! ENDIF
|
|
2263
|
+
|
|
2264
|
+
-- Hard failure: halt the script when QA fails.
|
|
2265
|
+
-- !x! PG_UPSERT QA FROM staging TO public TABLES books, authors
|
|
2266
|
+
-- !x! ASSERT !!$PG_UPSERT_QA_PASSED!! = TRUE "Pre-load QA failed; aborting."
|
|
2267
|
+
```
|
|
2268
|
+
|
|
2251
2269
|
### Temporary objects
|
|
2252
2270
|
|
|
2253
2271
|
pg-upsert creates temporary tables and views (all prefixed `ups_`) that persist after the metacommand completes. Users can query these for debugging and inspection — for example, `SELECT * FROM ups_control` shows per-table QA results and row counts.
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "execsql2"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.17.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" }
|
|
@@ -165,7 +165,7 @@ skip-magic-trailing-comma = false
|
|
|
165
165
|
line-ending = "auto"
|
|
166
166
|
|
|
167
167
|
[tool.bumpversion]
|
|
168
|
-
current_version = "2.
|
|
168
|
+
current_version = "2.17.0"
|
|
169
169
|
commit = true
|
|
170
170
|
tag = true
|
|
171
171
|
tag_name = "v{new_version}"
|
|
@@ -201,14 +201,6 @@ def _set_subvars(result: Any) -> None:
|
|
|
201
201
|
sv("$PG_UPSERT_EXPORT_PATH", "")
|
|
202
202
|
|
|
203
203
|
|
|
204
|
-
def _qa_failure_msg(result: Any) -> str:
|
|
205
|
-
"""Build a concise QA failure message listing which tables failed."""
|
|
206
|
-
failed = [t.table_name for t in result.tables if not t.qa_passed]
|
|
207
|
-
if failed:
|
|
208
|
-
return f"PG_UPSERT QA failed for: {', '.join(failed)}"
|
|
209
|
-
return "PG_UPSERT QA checks failed."
|
|
210
|
-
|
|
211
|
-
|
|
212
204
|
# ---------------------------------------------------------------------------
|
|
213
205
|
# Import guard + helpers
|
|
214
206
|
# ---------------------------------------------------------------------------
|
|
@@ -484,13 +476,6 @@ def x_pg_upsert(**kwargs: Any) -> None:
|
|
|
484
476
|
if opts.get("cleanup"):
|
|
485
477
|
ups.cleanup()
|
|
486
478
|
|
|
487
|
-
if not result.qa_passed:
|
|
488
|
-
raise ErrInfo(
|
|
489
|
-
"cmd",
|
|
490
|
-
command_text=metacommandline,
|
|
491
|
-
other_msg=_qa_failure_msg(result),
|
|
492
|
-
)
|
|
493
|
-
|
|
494
479
|
|
|
495
480
|
def x_pg_upsert_qa(**kwargs: Any) -> None:
|
|
496
481
|
"""PG_UPSERT QA FROM <staging> TO <base> TABLES <t1>, <t2> [options]
|
|
@@ -524,13 +509,6 @@ def x_pg_upsert_qa(**kwargs: Any) -> None:
|
|
|
524
509
|
if opts.get("cleanup"):
|
|
525
510
|
ups.cleanup()
|
|
526
511
|
|
|
527
|
-
if not result.qa_passed:
|
|
528
|
-
raise ErrInfo(
|
|
529
|
-
"cmd",
|
|
530
|
-
command_text=metacommandline,
|
|
531
|
-
other_msg=_qa_failure_msg(result),
|
|
532
|
-
)
|
|
533
|
-
|
|
534
512
|
|
|
535
513
|
def x_pg_upsert_check(**kwargs: Any) -> None:
|
|
536
514
|
"""PG_UPSERT CHECK FROM <staging> TO <base> TABLES <t1>, <t2>
|
|
@@ -567,13 +545,6 @@ def x_pg_upsert_check(**kwargs: Any) -> None:
|
|
|
567
545
|
if opts.get("cleanup"):
|
|
568
546
|
ups.cleanup()
|
|
569
547
|
|
|
570
|
-
if not result.qa_passed:
|
|
571
|
-
raise ErrInfo(
|
|
572
|
-
"cmd",
|
|
573
|
-
command_text=metacommandline,
|
|
574
|
-
other_msg=_qa_failure_msg(result),
|
|
575
|
-
)
|
|
576
|
-
|
|
577
548
|
|
|
578
549
|
# ---------------------------------------------------------------------------
|
|
579
550
|
# Plugin registration
|
|
@@ -243,15 +243,17 @@ def _exec_sql(
|
|
|
243
243
|
|
|
244
244
|
def _exec_metacommand(
|
|
245
245
|
ctx: RuntimeContext,
|
|
246
|
-
|
|
246
|
+
cmd: str,
|
|
247
247
|
source: str,
|
|
248
248
|
line_no: int,
|
|
249
|
-
localvars: SubVarSet | None = None,
|
|
250
249
|
) -> Any:
|
|
251
|
-
"""Dispatch a metacommand through the dispatch table.
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
250
|
+
"""Dispatch a metacommand through the dispatch table.
|
|
251
|
+
|
|
252
|
+
*cmd* must already have ``!!$VAR!!`` substitution applied. The caller is
|
|
253
|
+
responsible for expansion so that side-effecting substitutions (counter
|
|
254
|
+
increments, ``$RANDOM``, ``$UUID``) are evaluated exactly once per
|
|
255
|
+
metacommand reference.
|
|
256
|
+
"""
|
|
255
257
|
if _VARLIKE.search(cmd):
|
|
256
258
|
ctx.output.write(
|
|
257
259
|
f"Warning: There is a potential un-substituted variable in the command\n {cmd}\n",
|
|
@@ -355,13 +357,15 @@ def _execute_node(
|
|
|
355
357
|
command = node.command
|
|
356
358
|
if in_loop:
|
|
357
359
|
command = _convert_deferred_vars(command)
|
|
358
|
-
#
|
|
360
|
+
# Substitute once: the same expanded text is used for BREAK detection
|
|
361
|
+
# and dispatch. Calling substitute_vars twice would double-increment
|
|
362
|
+
# !!$COUNTER_N!! and re-roll !!$RANDOM!!/!!$UUID!! references.
|
|
359
363
|
effective_locals = _stack_localvars(ctx) or localvars
|
|
360
364
|
expanded = substitute_vars(command, effective_locals, ctx=ctx)
|
|
361
365
|
if _BREAK_RX.match(expanded):
|
|
362
366
|
raise _BreakLoop
|
|
363
367
|
ctx.last_command = _FakeScriptCmd(node)
|
|
364
|
-
_exec_metacommand(ctx,
|
|
368
|
+
_exec_metacommand(ctx, expanded, node.span.file, node.span.start_line)
|
|
365
369
|
|
|
366
370
|
elif isinstance(node, IfBlock):
|
|
367
371
|
ctx.last_command = _FakeScriptCmd(node)
|
|
@@ -19,7 +19,6 @@ from execsql.metacommands.upsert import (
|
|
|
19
19
|
_FileWriterHandler,
|
|
20
20
|
_build_result_from_qa_findings,
|
|
21
21
|
_parse_tables_and_options,
|
|
22
|
-
_qa_failure_msg,
|
|
23
22
|
x_pg_upsert,
|
|
24
23
|
x_pg_upsert_check,
|
|
25
24
|
x_pg_upsert_qa,
|
|
@@ -541,7 +540,9 @@ class TestFullMode:
|
|
|
541
540
|
assert "$PG_UPSERT_FINISHED_AT" in calls
|
|
542
541
|
assert "$PG_UPSERT_RESULT_JSON" in calls
|
|
543
542
|
|
|
544
|
-
def
|
|
543
|
+
def test_qa_failure_does_not_raise(self, mock_state):
|
|
544
|
+
"""QA failure surfaces via $PG_UPSERT_QA_PASSED; the user's own IF /
|
|
545
|
+
ON ERROR HALT decides control flow."""
|
|
545
546
|
state, db = mock_state
|
|
546
547
|
fake_result = FakeUpsertResult(
|
|
547
548
|
tables=[
|
|
@@ -559,17 +560,15 @@ class TestFullMode:
|
|
|
559
560
|
patch("execsql.metacommands.upsert._create_pgupsert") as mock_create,
|
|
560
561
|
):
|
|
561
562
|
mock_create.return_value.run.return_value = fake_result
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
assert "
|
|
571
|
-
# Subvars should still be set before the error
|
|
572
|
-
assert state.subvars.add_substitution.called
|
|
563
|
+
x_pg_upsert(
|
|
564
|
+
staging_schema="staging",
|
|
565
|
+
base_schema="public",
|
|
566
|
+
tail="books",
|
|
567
|
+
metacommandline="PG_UPSERT FROM staging TO public TABLES books",
|
|
568
|
+
)
|
|
569
|
+
calls = {c[0][0]: c[0][1] for c in state.subvars.add_substitution.call_args_list}
|
|
570
|
+
assert calls["$PG_UPSERT_QA_PASSED"] == "FALSE"
|
|
571
|
+
assert calls["$PG_UPSERT_COMMITTED"] == "FALSE"
|
|
573
572
|
|
|
574
573
|
def test_commit_keyword_passed(self, mock_state):
|
|
575
574
|
state, db = mock_state
|
|
@@ -868,42 +867,6 @@ class TestUserCancelled:
|
|
|
868
867
|
assert "cancelled" in str(exc_info.value)
|
|
869
868
|
|
|
870
869
|
|
|
871
|
-
# ---------------------------------------------------------------------------
|
|
872
|
-
# Logging bridge test
|
|
873
|
-
# ---------------------------------------------------------------------------
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
class TestQAFailureMessage:
|
|
877
|
-
def test_lists_failed_tables(self):
|
|
878
|
-
result = FakeUpsertResult(
|
|
879
|
-
tables=[
|
|
880
|
-
FakeTableResult(
|
|
881
|
-
table_name="books",
|
|
882
|
-
_qa_findings=[FakeQAError(table="books", check_type="null", details="title (3)")],
|
|
883
|
-
),
|
|
884
|
-
FakeTableResult(table_name="authors"), # passes
|
|
885
|
-
FakeTableResult(
|
|
886
|
-
table_name="genres",
|
|
887
|
-
_qa_findings=[FakeQAError(table="genres", check_type="fk", details="parent_id (5)")],
|
|
888
|
-
),
|
|
889
|
-
],
|
|
890
|
-
)
|
|
891
|
-
msg = _qa_failure_msg(result)
|
|
892
|
-
assert msg == "PG_UPSERT QA failed for: books, genres"
|
|
893
|
-
|
|
894
|
-
def test_single_failed_table(self):
|
|
895
|
-
result = FakeUpsertResult(
|
|
896
|
-
tables=[
|
|
897
|
-
FakeTableResult(
|
|
898
|
-
table_name="books",
|
|
899
|
-
_qa_findings=[FakeQAError(table="books", check_type="pk", details="id (2)")],
|
|
900
|
-
),
|
|
901
|
-
],
|
|
902
|
-
)
|
|
903
|
-
msg = _qa_failure_msg(result)
|
|
904
|
-
assert msg == "PG_UPSERT QA failed for: books"
|
|
905
|
-
|
|
906
|
-
|
|
907
870
|
# ---------------------------------------------------------------------------
|
|
908
871
|
# Logging bridge tests
|
|
909
872
|
# ---------------------------------------------------------------------------
|
|
@@ -934,7 +897,7 @@ class TestLoggingBridge:
|
|
|
934
897
|
|
|
935
898
|
|
|
936
899
|
class TestQAModeFailure:
|
|
937
|
-
def
|
|
900
|
+
def test_qa_failure_does_not_raise(self, mock_state):
|
|
938
901
|
state, db = mock_state
|
|
939
902
|
|
|
940
903
|
failed_result = FakeUpsertResult(
|
|
@@ -956,18 +919,19 @@ class TestQAModeFailure:
|
|
|
956
919
|
mock_ups = mock_create.return_value
|
|
957
920
|
mock_ups.qa_all.return_value = mock_ups
|
|
958
921
|
mock_build.return_value = failed_result
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
922
|
+
x_pg_upsert_qa(
|
|
923
|
+
staging_schema="staging",
|
|
924
|
+
base_schema="public",
|
|
925
|
+
tail="books",
|
|
926
|
+
metacommandline="PG_UPSERT QA FROM staging TO public TABLES books",
|
|
927
|
+
)
|
|
928
|
+
calls = {c[0][0]: c[0][1] for c in state.subvars.add_substitution.call_args_list}
|
|
929
|
+
assert calls["$PG_UPSERT_QA_PASSED"] == "FALSE"
|
|
930
|
+
assert calls["$PG_UPSERT_COMMITTED"] == "FALSE"
|
|
967
931
|
|
|
968
932
|
|
|
969
933
|
class TestCheckModeFailure:
|
|
970
|
-
def
|
|
934
|
+
def test_check_failure_does_not_raise(self, mock_state):
|
|
971
935
|
state, db = mock_state
|
|
972
936
|
|
|
973
937
|
failed_result = FakeUpsertResult(
|
|
@@ -990,14 +954,14 @@ class TestCheckModeFailure:
|
|
|
990
954
|
mock_ups.qa_column_existence.return_value = mock_ups
|
|
991
955
|
mock_ups.qa_type_mismatch.return_value = mock_ups
|
|
992
956
|
mock_build.return_value = failed_result
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
957
|
+
x_pg_upsert_check(
|
|
958
|
+
staging_schema="staging",
|
|
959
|
+
base_schema="public",
|
|
960
|
+
tail="books",
|
|
961
|
+
metacommandline="PG_UPSERT CHECK FROM staging TO public TABLES books",
|
|
962
|
+
)
|
|
963
|
+
calls = {c[0][0]: c[0][1] for c in state.subvars.add_substitution.call_args_list}
|
|
964
|
+
assert calls["$PG_UPSERT_QA_PASSED"] == "FALSE"
|
|
1001
965
|
|
|
1002
966
|
|
|
1003
967
|
# ---------------------------------------------------------------------------
|
|
@@ -1427,15 +1391,15 @@ class TestExportFailures:
|
|
|
1427
1391
|
patch("execsql.metacommands.upsert._create_pgupsert") as mock_create,
|
|
1428
1392
|
):
|
|
1429
1393
|
mock_create.return_value.run.return_value = fake_result
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
)
|
|
1437
|
-
# Export MUST have happened before the ErrInfo was raised.
|
|
1394
|
+
x_pg_upsert(
|
|
1395
|
+
staging_schema="staging",
|
|
1396
|
+
base_schema="public",
|
|
1397
|
+
tail="books EXPORT_FAILURES /tmp/out",
|
|
1398
|
+
metacommandline="PG_UPSERT FROM staging TO public TABLES books EXPORT_FAILURES /tmp/out",
|
|
1399
|
+
)
|
|
1438
1400
|
assert fake_result.export_calls == [("/tmp/out", "csv")]
|
|
1401
|
+
calls = {c[0][0]: c[0][1] for c in state.subvars.add_substitution.call_args_list}
|
|
1402
|
+
assert calls["$PG_UPSERT_QA_PASSED"] == "FALSE"
|
|
1439
1403
|
|
|
1440
1404
|
def test_no_export_when_keyword_absent(self, mock_state):
|
|
1441
1405
|
state, db = mock_state
|
|
@@ -154,6 +154,45 @@ class TestVariableSubstitution:
|
|
|
154
154
|
rows = _query_db(tmp_path, "SELECT val FROM t")
|
|
155
155
|
assert rows == [(8,)]
|
|
156
156
|
|
|
157
|
+
def test_counter_in_metacommand_starts_at_one(self, tmp_path):
|
|
158
|
+
# Regression: the AST executor previously called substitute_vars()
|
|
159
|
+
# twice per metacommand (once for BREAK detection, once for dispatch),
|
|
160
|
+
# which double-incremented $COUNTER_N so refs returned 2, 4, 6.
|
|
161
|
+
result = _run_ast(
|
|
162
|
+
'-- !x! WRITE "First: !!$COUNTER_1!!"\n'
|
|
163
|
+
'-- !x! WRITE "Second: !!$COUNTER_1!!"\n'
|
|
164
|
+
'-- !x! WRITE "Third: !!$COUNTER_1!!"\n',
|
|
165
|
+
tmp_path,
|
|
166
|
+
)
|
|
167
|
+
assert result.returncode == 0, result.stderr
|
|
168
|
+
assert "First: 1" in result.stdout
|
|
169
|
+
assert "Second: 2" in result.stdout
|
|
170
|
+
assert "Third: 3" in result.stdout
|
|
171
|
+
|
|
172
|
+
def test_counter_in_sql_starts_at_one(self, tmp_path):
|
|
173
|
+
# Sibling check: SQL-statement counters were already correct, but
|
|
174
|
+
# pin the behavior so a future regression in the SQL path is caught.
|
|
175
|
+
result = _run_ast(
|
|
176
|
+
"CREATE TABLE t (n INT);\n"
|
|
177
|
+
"INSERT INTO t VALUES (!!$COUNTER_2!!);\n"
|
|
178
|
+
"INSERT INTO t VALUES (!!$COUNTER_2!!);\n"
|
|
179
|
+
"INSERT INTO t VALUES (!!$COUNTER_2!!);\n",
|
|
180
|
+
tmp_path,
|
|
181
|
+
)
|
|
182
|
+
assert result.returncode == 0, result.stderr
|
|
183
|
+
rows = _query_db(tmp_path, "SELECT n FROM t ORDER BY n")
|
|
184
|
+
assert rows == [(1,), (2,), (3,)]
|
|
185
|
+
|
|
186
|
+
def test_counter_same_ref_twice_in_one_metacommand(self, tmp_path):
|
|
187
|
+
# Per docs: multiple references in one command share the same value.
|
|
188
|
+
result = _run_ast(
|
|
189
|
+
'-- !x! WRITE "A=!!$COUNTER_3!! B=!!$COUNTER_3!!"\n-- !x! WRITE "C=!!$COUNTER_3!!"\n',
|
|
190
|
+
tmp_path,
|
|
191
|
+
)
|
|
192
|
+
assert result.returncode == 0, result.stderr
|
|
193
|
+
assert "A=1 B=1" in result.stdout
|
|
194
|
+
assert "C=2" in result.stdout
|
|
195
|
+
|
|
157
196
|
|
|
158
197
|
# ---------------------------------------------------------------------------
|
|
159
198
|
# IF / ELSE / ELSEIF
|
|
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
|
|
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
|
{execsql2-2.16.18 → execsql2-2.17.0}/extras/plugin-template/src/execsql_plugin_YOURNAME/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|