execsql2 2.12.5__tar.gz → 2.12.6__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.12.5 → execsql2-2.12.6}/CHANGELOG.md +13 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/PKG-INFO +3 -3
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/reference/metacommands.md +5 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/pyproject.toml +3 -3
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/metacommands/upsert.py +28 -3
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/metacommands/test_pg_upsert.py +116 -1
- {execsql2-2.12.5 → execsql2-2.12.6}/uv.lock +5 -5
- {execsql2-2.12.5 → execsql2-2.12.6}/.claude/agents/dba.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/.claude/agents/herald.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/.claude/agents/inspector.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/.claude/agents/liaison.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/.claude/agents/oracle.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/.claude/agents/patcher.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/.claude/agents/qa.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/.claude/agents/scribe.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/.claude/commands/code-oracle.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/.claude/commands/migrate.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/.claude/commands/review-changes.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/.claude/commands/test-module.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/.claude/commands/update-changelog.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/.claude/commands/where-is.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/.claude/project_context.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/.claude/state/status.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/.github/workflows/ci-cd.yml +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/.gitignore +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/.pre-commit-config.yaml +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/.pre-commit-hooks.yaml +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/.python-version +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/.readthedocs.yaml +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/CLAUDE.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/CONTRIBUTING.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/LICENSE.txt +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/NOTICE +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/README.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/SECURITY.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/about/contributors.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/about/copyright.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/about/divergence.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/api/cli.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/api/db.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/api/exporters.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/api/importers.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/api/index.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/api/metacommands.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/dev/adding_db_adapters.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/dev/adding_exporters.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/dev/adding_importers.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/dev/adding_metacommands.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/dev/architecture.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/getting-started/installation.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/getting-started/requirements.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/getting-started/syntax.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/guides/debugging.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/guides/documentation.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/guides/encoding.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/guides/examples.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/guides/formatter.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/guides/logging.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/guides/sql_syntax.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/guides/usage.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/guides/using_scripts.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/images/Compare_planets.png +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/images/actions.png +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/images/actions2.png +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/images/checkboxes.png +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/images/connect.b64 +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/images/connect.png +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/images/create_conf.png +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/images/data_error1_screenshot.jpg +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/images/entry_form.png +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/images/execsql_console.png +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/images/execsql_logo_01.png +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/images/fatals.png +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/images/logo_small.png +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/images/pause_terminal.png +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/images/pause_terminal_sm.b64 +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/images/pause_terminal_sm.png +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/images/prompt_compare.png +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/images/set_build_commands.jpg +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/images/unit_conversions.b64 +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/images/unit_conversions_029.png +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/images/unmatched.png +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/images/vim_execsql_highlight.png +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/index.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/reference/configuration.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/reference/security.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/docs/reference/substitution_vars.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/extras/vscode-execsql/README.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/extras/vscode-execsql/package.json +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/extras/vscode-execsql/syntaxes/execsql.tmLanguage.json +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/justfile +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/scripts/generate_vscode_grammar.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/__init__.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/__main__.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/cli/__init__.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/cli/dsn.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/cli/help.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/cli/lint.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/cli/run.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/config.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/constants.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/db/__init__.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/db/access.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/db/base.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/db/dsn.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/db/duckdb.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/db/factory.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/db/firebird.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/db/mysql.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/db/oracle.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/db/postgres.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/db/sqlite.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/db/sqlserver.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/debug/__init__.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/debug/repl.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/exceptions.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/exporters/__init__.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/exporters/base.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/exporters/delimited.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/exporters/duckdb.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/exporters/feather.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/exporters/html.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/exporters/json.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/exporters/latex.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/exporters/markdown.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/exporters/ods.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/exporters/parquet.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/exporters/pretty.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/exporters/protocol.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/exporters/raw.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/exporters/sqlite.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/exporters/templates.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/exporters/values.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/exporters/xls.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/exporters/xlsx.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/exporters/xml.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/exporters/yaml.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/exporters/zip.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/format.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/gui/__init__.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/gui/base.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/gui/console.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/gui/desktop.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/gui/tui.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/importers/__init__.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/importers/base.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/importers/csv.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/importers/feather.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/importers/ods.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/importers/xls.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/metacommands/__init__.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/metacommands/conditions.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/metacommands/connect.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/metacommands/control.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/metacommands/data.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/metacommands/debug.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/metacommands/dispatch.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/metacommands/io.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/metacommands/io_export.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/metacommands/io_fileops.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/metacommands/io_import.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/metacommands/io_write.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/metacommands/prompt.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/metacommands/script_ext.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/metacommands/system.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/models.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/parser.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/py.typed +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/script/__init__.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/script/control.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/script/engine.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/script/variables.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/state.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/types.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/utils/__init__.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/utils/auth.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/utils/crypto.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/utils/datetime.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/utils/errors.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/utils/fileio.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/utils/gui.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/utils/mail.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/utils/numeric.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/utils/regex.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/utils/strings.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/utils/timer.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/templates/README.md +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/templates/config_settings.sqlite +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/templates/example_config_prompt.sql +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/templates/execsql.conf +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/templates/make_config_db.sql +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/templates/md_compare.sql +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/templates/md_glossary.sql +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/templates/md_upsert.sql +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/templates/pg_compare.sql +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/templates/pg_glossary.sql +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/templates/pg_upsert.sql +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/templates/script_template.sql +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/templates/ss_compare.sql +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/templates/ss_glossary.sql +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/templates/ss_upsert.sql +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/__init__.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/cli/__init__.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/cli/test_cli.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/cli/test_cli_e2e.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/cli/test_cli_run.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/cli/test_lint.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/cli/test_ping.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/cli/test_profile.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/conftest.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/db/__init__.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/db/test_base.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/db/test_duckdb.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/db/test_factory.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/db/test_postgres.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/db/test_sqlite.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/db/test_sqlite_extra.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/exporters/__init__.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/exporters/test_base.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/exporters/test_db.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/exporters/test_delimited.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/exporters/test_duckdb_exporter.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/exporters/test_exporters.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/exporters/test_feather.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/exporters/test_html_latex.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/exporters/test_json.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/exporters/test_markdown.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/exporters/test_ods.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/exporters/test_parquet.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/exporters/test_sqlite_exporter.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/exporters/test_templates.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/exporters/test_xls_xlsx.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/exporters/test_xlsx.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/exporters/test_xml.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/exporters/test_yaml.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/exporters/test_zip.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/gui/__init__.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/gui/test_backends.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/importers/__init__.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/importers/test_csv_importer.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/importers/test_feather_importer.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/importers/test_ods_importer.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/importers/test_xls_importer.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/integration/__init__.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/integration/conftest.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/integration/test_dsn.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/integration/test_duckdb.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/integration/test_mysql.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/integration/test_postgres.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/integration/test_sqlite.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/metacommands/__init__.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/metacommands/test_assert.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/metacommands/test_breakpoint.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/metacommands/test_connect.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/metacommands/test_io_export.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/metacommands/test_io_import.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/metacommands/test_metacommands.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/metacommands/test_metacommands_connect.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/metacommands/test_metacommands_data.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/metacommands/test_metacommands_extended.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/metacommands/test_metacommands_fileops_extra.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/metacommands/test_metacommands_io.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/metacommands/test_metacommands_io_write_extra.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/metacommands/test_metacommands_script_ext.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/metacommands/test_metacommands_system.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/metacommands/test_metacommands_system_extra.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/metacommands/test_row_count.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/test_config.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/test_config_data.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/test_constants.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/test_engine.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/test_error_messages.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/test_exceptions.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/test_format.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/test_mail.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/test_models.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/test_package.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/test_parser.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/test_registry.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/test_script.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/test_state.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/test_types.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/utils/__init__.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/utils/test_auth.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/utils/test_auth_extra.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/utils/test_crypto.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/utils/test_datetime.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/utils/test_errors.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/utils/test_errors_extra.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/utils/test_fileio.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/utils/test_fileio_extra.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/utils/test_numeric.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/utils/test_regex.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/utils/test_strings.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/utils/test_timer.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/tests/utils/test_timer_extra.py +0 -0
- {execsql2-2.12.5 → execsql2-2.12.6}/zensical.toml +0 -0
|
@@ -13,8 +13,21 @@ ______________________________________________________________________
|
|
|
13
13
|
|
|
14
14
|
______________________________________________________________________
|
|
15
15
|
|
|
16
|
+
## [2.12.6] - 2026-04-03
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- `PG_UPSERT` now supports per-table progress via pg-upsert's callback API. New substitution variables `$PG_UPSERT_CURRENT_TABLE`, `$PG_UPSERT_TABLE_QA_PASSED`, `$PG_UPSERT_TABLE_ROWS_UPDATED`, and `$PG_UPSERT_TABLE_ROWS_INSERTED` are updated as each table is processed.
|
|
21
|
+
- New `CLEANUP` keyword for `PG_UPSERT` — drops all `ups_*` temporary tables and views after execution. Without it, temp objects persist for inspection (default).
|
|
22
|
+
|
|
23
|
+
______________________________________________________________________
|
|
24
|
+
|
|
16
25
|
## [2.12.5] - 2026-04-03
|
|
17
26
|
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- Fixed CLI test `test_nonexistent_file_error_message_is_clear` failing with `ValueError: stderr not separately captured` when CliRunner mixes stderr into stdout.
|
|
30
|
+
|
|
18
31
|
______________________________________________________________________
|
|
19
32
|
|
|
20
33
|
## [2.12.4] - 2026-04-03
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: execsql2
|
|
3
|
-
Version: 2.12.
|
|
3
|
+
Version: 2.12.6
|
|
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
|
|
@@ -51,7 +51,7 @@ Requires-Dist: keyring; extra == 'all'
|
|
|
51
51
|
Requires-Dist: odfpy; extra == 'all'
|
|
52
52
|
Requires-Dist: openpyxl; extra == 'all'
|
|
53
53
|
Requires-Dist: oracledb; extra == 'all'
|
|
54
|
-
Requires-Dist: pg-upsert>=1.
|
|
54
|
+
Requires-Dist: pg-upsert>=1.18.0; extra == 'all'
|
|
55
55
|
Requires-Dist: polars; extra == 'all'
|
|
56
56
|
Requires-Dist: psycopg2-binary; extra == 'all'
|
|
57
57
|
Requires-Dist: pymysql; extra == 'all'
|
|
@@ -109,7 +109,7 @@ Requires-Dist: oracledb; extra == 'oracle'
|
|
|
109
109
|
Provides-Extra: postgres
|
|
110
110
|
Requires-Dist: psycopg2-binary; extra == 'postgres'
|
|
111
111
|
Provides-Extra: upsert
|
|
112
|
-
Requires-Dist: pg-upsert>=1.
|
|
112
|
+
Requires-Dist: pg-upsert>=1.18.0; extra == 'upsert'
|
|
113
113
|
Description-Content-Type: text/markdown
|
|
114
114
|
|
|
115
115
|
> [!NOTE]
|
|
@@ -2099,6 +2099,7 @@ Keywords can appear in any order after the table list.
|
|
|
2099
2099
|
| `INTERACTIVE` | Enable pg-upsert's interactive UI dialogs (tkinter or textual) for reviewing QA failures. Without it, runs non-interactively. |
|
|
2100
2100
|
| `COMPACT` | Use compact grid format for QA summary instead of detailed per-table panels. |
|
|
2101
2101
|
| `LOGFILE <path>` | Append pg-upsert's plain-text log output to the given file. Supports quoted paths for spaces: `LOGFILE "path/to/log.txt"`. |
|
|
2102
|
+
| `CLEANUP` | Drop all `ups_*` temporary tables and views after execution. Without it, temp objects persist for inspection (default). |
|
|
2102
2103
|
|
|
2103
2104
|
### Substitution variables
|
|
2104
2105
|
|
|
@@ -2118,6 +2119,10 @@ Set after every `PG_UPSERT` execution:
|
|
|
2118
2119
|
| `$PG_UPSERT_STARTED_AT` | string | ISO 8601 start timestamp |
|
|
2119
2120
|
| `$PG_UPSERT_FINISHED_AT` | string | ISO 8601 end timestamp |
|
|
2120
2121
|
| `$PG_UPSERT_RESULT_JSON` | JSON string | Full result for detailed inspection |
|
|
2122
|
+
| `$PG_UPSERT_CURRENT_TABLE` | string | Last table processed (updated per table during execution) |
|
|
2123
|
+
| `$PG_UPSERT_TABLE_QA_PASSED` | TRUE/FALSE | QA result for the current table (updated per table) |
|
|
2124
|
+
| `$PG_UPSERT_TABLE_ROWS_UPDATED` | integer | Rows updated for the current table (updated per table) |
|
|
2125
|
+
| `$PG_UPSERT_TABLE_ROWS_INSERTED` | integer | Rows inserted for the current table (updated per table) |
|
|
2121
2126
|
|
|
2122
2127
|
!!! note "Using `$PG_UPSERT_RESULT_JSON` with WRITE"
|
|
2123
2128
|
The JSON value is stored as compact single-line JSON. Because it contains double quotes (`"`), square brackets (`[]`), and apostrophes may appear in data, use tilde or backtick delimiters with WRITE:
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "execsql2"
|
|
7
|
-
version = "2.12.
|
|
7
|
+
version = "2.12.6"
|
|
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" }
|
|
@@ -58,7 +58,7 @@ odbc = ["pyodbc"]
|
|
|
58
58
|
# Feature bundles
|
|
59
59
|
formats = ["odfpy", "xlrd", "openpyxl", "Jinja2", "polars", "tables", "PyYAML"]
|
|
60
60
|
auth = ["keyring"]
|
|
61
|
-
upsert = ["pg-upsert>=1.
|
|
61
|
+
upsert = ["pg-upsert>=1.18.0"]
|
|
62
62
|
# Convenience groups
|
|
63
63
|
all-db = [
|
|
64
64
|
"psycopg2-binary",
|
|
@@ -161,7 +161,7 @@ skip-magic-trailing-comma = false
|
|
|
161
161
|
line-ending = "auto"
|
|
162
162
|
|
|
163
163
|
[tool.bumpversion]
|
|
164
|
-
current_version = "2.12.
|
|
164
|
+
current_version = "2.12.6"
|
|
165
165
|
commit = true
|
|
166
166
|
commit_args = "--no-verify"
|
|
167
167
|
tag = true
|
|
@@ -26,21 +26,22 @@ from execsql.utils.errors import exception_desc
|
|
|
26
26
|
|
|
27
27
|
_KW_METHOD = re.compile(r"\bMETHOD\s+(upsert|update|insert)\b", re.IGNORECASE)
|
|
28
28
|
_KW_EXCLUDE = re.compile(
|
|
29
|
-
r"\bEXCLUDE\s+([\w\s,]+?)(?=\s+(?:METHOD|COMMIT|INTERACTIVE|COMPACT|EXCLUDE_NULL|LOGFILE)\b|\s*$)",
|
|
29
|
+
r"\bEXCLUDE\s+([\w\s,]+?)(?=\s+(?:METHOD|COMMIT|INTERACTIVE|COMPACT|EXCLUDE_NULL|LOGFILE|CLEANUP)\b|\s*$)",
|
|
30
30
|
re.IGNORECASE,
|
|
31
31
|
)
|
|
32
32
|
_KW_EXCLUDE_NULL = re.compile(
|
|
33
|
-
r"\bEXCLUDE_NULL\s+([\w\s,]+?)(?=\s+(?:METHOD|COMMIT|INTERACTIVE|COMPACT|EXCLUDE|LOGFILE)\b|\s*$)",
|
|
33
|
+
r"\bEXCLUDE_NULL\s+([\w\s,]+?)(?=\s+(?:METHOD|COMMIT|INTERACTIVE|COMPACT|EXCLUDE|LOGFILE|CLEANUP)\b|\s*$)",
|
|
34
34
|
re.IGNORECASE,
|
|
35
35
|
)
|
|
36
36
|
_KW_COMMIT = re.compile(r"\bCOMMIT\b", re.IGNORECASE)
|
|
37
37
|
_KW_INTERACTIVE = re.compile(r"\bINTERACTIVE\b", re.IGNORECASE)
|
|
38
38
|
_KW_COMPACT = re.compile(r"\bCOMPACT\b", re.IGNORECASE)
|
|
39
|
+
_KW_CLEANUP = re.compile(r"\bCLEANUP\b", re.IGNORECASE)
|
|
39
40
|
_KW_LOGFILE = re.compile(r"""\bLOGFILE\s+(?:"([^"]+)"|'([^']+)'|(\S+))""", re.IGNORECASE)
|
|
40
41
|
|
|
41
42
|
# All recognized keywords — used to split table names from options.
|
|
42
43
|
_ALL_KEYWORDS = re.compile(
|
|
43
|
-
r"\b(?:METHOD|COMMIT|INTERACTIVE|COMPACT|EXCLUDE_NULL|EXCLUDE|LOGFILE)\b",
|
|
44
|
+
r"\b(?:METHOD|COMMIT|INTERACTIVE|COMPACT|EXCLUDE_NULL|EXCLUDE|LOGFILE|CLEANUP)\b",
|
|
44
45
|
re.IGNORECASE,
|
|
45
46
|
)
|
|
46
47
|
|
|
@@ -98,6 +99,7 @@ def _parse_tables_and_options(tail: str) -> dict[str, Any]:
|
|
|
98
99
|
"exclude_cols": exclude_cols,
|
|
99
100
|
"exclude_null_check_cols": exclude_null,
|
|
100
101
|
"logfile": logfile,
|
|
102
|
+
"cleanup": bool(_KW_CLEANUP.search(opts_part)),
|
|
101
103
|
}
|
|
102
104
|
|
|
103
105
|
|
|
@@ -210,6 +212,22 @@ def _build_result_from_qa_errors(ups: Any) -> Any:
|
|
|
210
212
|
)
|
|
211
213
|
|
|
212
214
|
|
|
215
|
+
def _make_callback() -> Any:
|
|
216
|
+
"""Return a pg-upsert pipeline callback that sets per-table subvars."""
|
|
217
|
+
from pg_upsert import CallbackEvent
|
|
218
|
+
|
|
219
|
+
def _on_event(event: Any) -> None:
|
|
220
|
+
sv = _state.subvars.add_substitution
|
|
221
|
+
sv("$PG_UPSERT_CURRENT_TABLE", event.table)
|
|
222
|
+
if event.event == CallbackEvent.QA_TABLE_COMPLETE:
|
|
223
|
+
sv("$PG_UPSERT_TABLE_QA_PASSED", str(event.qa_passed).upper())
|
|
224
|
+
elif event.event == CallbackEvent.UPSERT_TABLE_COMPLETE:
|
|
225
|
+
sv("$PG_UPSERT_TABLE_ROWS_UPDATED", str(event.rows_updated))
|
|
226
|
+
sv("$PG_UPSERT_TABLE_ROWS_INSERTED", str(event.rows_inserted))
|
|
227
|
+
|
|
228
|
+
return _on_event
|
|
229
|
+
|
|
230
|
+
|
|
213
231
|
def _create_pgupsert(
|
|
214
232
|
db: Any,
|
|
215
233
|
staging_schema: str,
|
|
@@ -235,6 +253,7 @@ def _create_pgupsert(
|
|
|
235
253
|
exclude_cols=opts["exclude_cols"],
|
|
236
254
|
exclude_null_check_cols=opts["exclude_null_check_cols"],
|
|
237
255
|
ui_mode=ui_mode,
|
|
256
|
+
callback=_make_callback(),
|
|
238
257
|
)
|
|
239
258
|
return ups
|
|
240
259
|
|
|
@@ -362,6 +381,8 @@ def x_pg_upsert(**kwargs: Any) -> None:
|
|
|
362
381
|
_detach_log_handlers(loggers, handlers, prev_levels)
|
|
363
382
|
|
|
364
383
|
_set_subvars(result)
|
|
384
|
+
if opts.get("cleanup"):
|
|
385
|
+
ups.cleanup()
|
|
365
386
|
|
|
366
387
|
if not result.qa_passed:
|
|
367
388
|
raise ErrInfo(
|
|
@@ -399,6 +420,8 @@ def x_pg_upsert_qa(**kwargs: Any) -> None:
|
|
|
399
420
|
|
|
400
421
|
result = _build_result_from_qa_errors(ups)
|
|
401
422
|
_set_subvars(result)
|
|
423
|
+
if opts.get("cleanup"):
|
|
424
|
+
ups.cleanup()
|
|
402
425
|
|
|
403
426
|
if not result.qa_passed:
|
|
404
427
|
raise ErrInfo(
|
|
@@ -439,6 +462,8 @@ def x_pg_upsert_check(**kwargs: Any) -> None:
|
|
|
439
462
|
|
|
440
463
|
result = _build_result_from_qa_errors(ups)
|
|
441
464
|
_set_subvars(result)
|
|
465
|
+
if opts.get("cleanup"):
|
|
466
|
+
ups.cleanup()
|
|
442
467
|
|
|
443
468
|
if not result.qa_passed:
|
|
444
469
|
raise ErrInfo(
|
|
@@ -230,7 +230,7 @@ class TestParseTablesAndOptions:
|
|
|
230
230
|
|
|
231
231
|
def test_all_keywords_combined(self):
|
|
232
232
|
result = _parse_tables_and_options(
|
|
233
|
-
'books, authors METHOD update EXCLUDE rev_time EXCLUDE_NULL created_at INTERACTIVE COMPACT LOGFILE "upsert.log" COMMIT',
|
|
233
|
+
'books, authors METHOD update EXCLUDE rev_time EXCLUDE_NULL created_at INTERACTIVE COMPACT LOGFILE "upsert.log" CLEANUP COMMIT',
|
|
234
234
|
)
|
|
235
235
|
assert result["tables"] == ["books", "authors"]
|
|
236
236
|
assert result["method"] == "update"
|
|
@@ -240,6 +240,7 @@ class TestParseTablesAndOptions:
|
|
|
240
240
|
assert result["compact"] is True
|
|
241
241
|
assert result["commit"] is True
|
|
242
242
|
assert result["logfile"] == "upsert.log"
|
|
243
|
+
assert result["cleanup"] is True
|
|
243
244
|
|
|
244
245
|
def test_keyword_order_independent(self):
|
|
245
246
|
r1 = _parse_tables_and_options("books COMPACT INTERACTIVE COMMIT")
|
|
@@ -995,3 +996,117 @@ class TestLoggerLevelRestoration:
|
|
|
995
996
|
|
|
996
997
|
assert display_logger.level == orig_display
|
|
997
998
|
assert main_logger.level == orig_main
|
|
999
|
+
|
|
1000
|
+
|
|
1001
|
+
# ---------------------------------------------------------------------------
|
|
1002
|
+
# CLEANUP keyword tests
|
|
1003
|
+
# ---------------------------------------------------------------------------
|
|
1004
|
+
|
|
1005
|
+
|
|
1006
|
+
class TestCleanup:
|
|
1007
|
+
def test_cleanup_keyword_parsed(self):
|
|
1008
|
+
result = _parse_tables_and_options("books CLEANUP COMMIT")
|
|
1009
|
+
assert result["cleanup"] is True
|
|
1010
|
+
|
|
1011
|
+
def test_no_cleanup_by_default(self):
|
|
1012
|
+
result = _parse_tables_and_options("books COMMIT")
|
|
1013
|
+
assert result["cleanup"] is False
|
|
1014
|
+
|
|
1015
|
+
def test_cleanup_calls_ups_cleanup(self, mock_state):
|
|
1016
|
+
state, db = mock_state
|
|
1017
|
+
fake_result = FakeUpsertResult(
|
|
1018
|
+
tables=[FakeTableResult(table_name="books")],
|
|
1019
|
+
staging_schema="staging",
|
|
1020
|
+
base_schema="public",
|
|
1021
|
+
)
|
|
1022
|
+
|
|
1023
|
+
with (
|
|
1024
|
+
patch("execsql.metacommands.upsert._require_pg_upsert"),
|
|
1025
|
+
patch("execsql.metacommands.upsert._create_pgupsert") as mock_create,
|
|
1026
|
+
):
|
|
1027
|
+
mock_ups = mock_create.return_value
|
|
1028
|
+
mock_ups.run.return_value = fake_result
|
|
1029
|
+
x_pg_upsert(
|
|
1030
|
+
staging_schema="staging",
|
|
1031
|
+
base_schema="public",
|
|
1032
|
+
tail="books CLEANUP COMMIT",
|
|
1033
|
+
metacommandline="PG_UPSERT FROM staging TO public TABLES books CLEANUP COMMIT",
|
|
1034
|
+
)
|
|
1035
|
+
mock_ups.cleanup.assert_called_once()
|
|
1036
|
+
|
|
1037
|
+
def test_no_cleanup_without_keyword(self, mock_state):
|
|
1038
|
+
state, db = mock_state
|
|
1039
|
+
fake_result = FakeUpsertResult(
|
|
1040
|
+
tables=[FakeTableResult(table_name="books")],
|
|
1041
|
+
staging_schema="staging",
|
|
1042
|
+
base_schema="public",
|
|
1043
|
+
)
|
|
1044
|
+
|
|
1045
|
+
with (
|
|
1046
|
+
patch("execsql.metacommands.upsert._require_pg_upsert"),
|
|
1047
|
+
patch("execsql.metacommands.upsert._create_pgupsert") as mock_create,
|
|
1048
|
+
):
|
|
1049
|
+
mock_ups = mock_create.return_value
|
|
1050
|
+
mock_ups.run.return_value = fake_result
|
|
1051
|
+
x_pg_upsert(
|
|
1052
|
+
staging_schema="staging",
|
|
1053
|
+
base_schema="public",
|
|
1054
|
+
tail="books COMMIT",
|
|
1055
|
+
metacommandline="PG_UPSERT FROM staging TO public TABLES books COMMIT",
|
|
1056
|
+
)
|
|
1057
|
+
mock_ups.cleanup.assert_not_called()
|
|
1058
|
+
|
|
1059
|
+
|
|
1060
|
+
# ---------------------------------------------------------------------------
|
|
1061
|
+
# Callback per-table subvar tests
|
|
1062
|
+
# ---------------------------------------------------------------------------
|
|
1063
|
+
|
|
1064
|
+
|
|
1065
|
+
class TestCallback:
|
|
1066
|
+
def test_callback_sets_qa_table_subvars(self, mock_state):
|
|
1067
|
+
from execsql.metacommands.upsert import _make_callback
|
|
1068
|
+
|
|
1069
|
+
state, db = mock_state
|
|
1070
|
+
|
|
1071
|
+
# Create a fake QA_TABLE_COMPLETE event
|
|
1072
|
+
mock_event = MagicMock()
|
|
1073
|
+
mock_event.event = MagicMock()
|
|
1074
|
+
mock_event.event.value = "qa_table_complete"
|
|
1075
|
+
mock_event.table = "books"
|
|
1076
|
+
mock_event.qa_passed = True
|
|
1077
|
+
|
|
1078
|
+
# We need the real CallbackEvent enum for comparison
|
|
1079
|
+
# Patch _state so the callback can access subvars
|
|
1080
|
+
with patch("execsql.metacommands.upsert._state", state):
|
|
1081
|
+
cb = _make_callback()
|
|
1082
|
+
|
|
1083
|
+
# Simulate CallbackEvent.QA_TABLE_COMPLETE match
|
|
1084
|
+
from pg_upsert import CallbackEvent
|
|
1085
|
+
|
|
1086
|
+
mock_event.event = CallbackEvent.QA_TABLE_COMPLETE
|
|
1087
|
+
cb(mock_event)
|
|
1088
|
+
|
|
1089
|
+
calls = {c[0][0]: c[0][1] for c in state.subvars.add_substitution.call_args_list}
|
|
1090
|
+
assert calls["$PG_UPSERT_CURRENT_TABLE"] == "books"
|
|
1091
|
+
assert calls["$PG_UPSERT_TABLE_QA_PASSED"] == "TRUE"
|
|
1092
|
+
|
|
1093
|
+
def test_callback_sets_upsert_table_subvars(self, mock_state):
|
|
1094
|
+
from execsql.metacommands.upsert import _make_callback
|
|
1095
|
+
from pg_upsert import CallbackEvent
|
|
1096
|
+
|
|
1097
|
+
state, db = mock_state
|
|
1098
|
+
|
|
1099
|
+
mock_event = MagicMock()
|
|
1100
|
+
mock_event.event = CallbackEvent.UPSERT_TABLE_COMPLETE
|
|
1101
|
+
mock_event.table = "authors"
|
|
1102
|
+
mock_event.rows_updated = 15
|
|
1103
|
+
mock_event.rows_inserted = 3
|
|
1104
|
+
|
|
1105
|
+
with patch("execsql.metacommands.upsert._state", state):
|
|
1106
|
+
cb = _make_callback()
|
|
1107
|
+
cb(mock_event)
|
|
1108
|
+
|
|
1109
|
+
calls = {c[0][0]: c[0][1] for c in state.subvars.add_substitution.call_args_list}
|
|
1110
|
+
assert calls["$PG_UPSERT_CURRENT_TABLE"] == "authors"
|
|
1111
|
+
assert calls["$PG_UPSERT_TABLE_ROWS_UPDATED"] == "15"
|
|
1112
|
+
assert calls["$PG_UPSERT_TABLE_ROWS_INSERTED"] == "3"
|
|
@@ -648,7 +648,7 @@ wheels = [
|
|
|
648
648
|
|
|
649
649
|
[[package]]
|
|
650
650
|
name = "execsql2"
|
|
651
|
-
version = "2.12.
|
|
651
|
+
version = "2.12.6"
|
|
652
652
|
source = { editable = "." }
|
|
653
653
|
dependencies = [
|
|
654
654
|
{ name = "rich" },
|
|
@@ -766,7 +766,7 @@ requires-dist = [
|
|
|
766
766
|
{ name = "openpyxl", marker = "extra == 'formats'" },
|
|
767
767
|
{ name = "oracledb", marker = "extra == 'all-db'" },
|
|
768
768
|
{ name = "oracledb", marker = "extra == 'oracle'" },
|
|
769
|
-
{ name = "pg-upsert", marker = "extra == 'upsert'", specifier = ">=1.
|
|
769
|
+
{ name = "pg-upsert", marker = "extra == 'upsert'", specifier = ">=1.18.0" },
|
|
770
770
|
{ name = "polars", marker = "extra == 'formats'" },
|
|
771
771
|
{ name = "pre-commit", marker = "extra == 'dev'", specifier = ">=3.5.0" },
|
|
772
772
|
{ name = "psycopg2-binary", marker = "extra == 'all-db'" },
|
|
@@ -1780,7 +1780,7 @@ wheels = [
|
|
|
1780
1780
|
|
|
1781
1781
|
[[package]]
|
|
1782
1782
|
name = "pg-upsert"
|
|
1783
|
-
version = "1.
|
|
1783
|
+
version = "1.18.0"
|
|
1784
1784
|
source = { registry = "https://pypi.org/simple" }
|
|
1785
1785
|
dependencies = [
|
|
1786
1786
|
{ name = "psycopg2-binary" },
|
|
@@ -1788,9 +1788,9 @@ dependencies = [
|
|
|
1788
1788
|
{ name = "rich" },
|
|
1789
1789
|
{ name = "typer" },
|
|
1790
1790
|
]
|
|
1791
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
1791
|
+
sdist = { url = "https://files.pythonhosted.org/packages/43/86/2e6adaa45b6746635e77ce7dc94729af28bcedc9f2daa139a5cbeb00926d/pg_upsert-1.18.0.tar.gz", hash = "sha256:1188d56f8dc6b3436f509b43a280e6f0c932e3e896615b657094c4a24c41af82", size = 1446377, upload-time = "2026-04-03T18:38:55.253Z" }
|
|
1792
1792
|
wheels = [
|
|
1793
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1793
|
+
{ url = "https://files.pythonhosted.org/packages/3e/24/ecf173ed388766a090896d18ca1234264e1b4f69fff5a103d32703f8d935/pg_upsert-1.18.0-py3-none-any.whl", hash = "sha256:eeb4c27f89bd421d58818359043aad9b4593775f6b677d1eaed832ece6c11645", size = 80421, upload-time = "2026-04-03T18:38:53.689Z" },
|
|
1794
1794
|
]
|
|
1795
1795
|
|
|
1796
1796
|
[[package]]
|
|
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
|
|
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
|