execsql2 2.15.7__tar.gz → 2.15.8__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.15.7 → execsql2-2.15.8}/CHANGELOG.md +19 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/PKG-INFO +3 -3
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/about/divergence.md +8 -8
- {execsql2-2.15.7 → execsql2-2.15.8}/pyproject.toml +3 -3
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/metacommands/upsert.py +20 -10
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/metacommands/test_pg_upsert.py +46 -23
- {execsql2-2.15.7 → execsql2-2.15.8}/uv.lock +5 -5
- {execsql2-2.15.7 → execsql2-2.15.8}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/.github/workflows/ci-cd.yml +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/.gitignore +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/.pre-commit-config.yaml +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/.pre-commit-hooks.yaml +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/.python-version +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/.readthedocs.yaml +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/CONTRIBUTING.md +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/LICENSE.txt +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/NOTICE +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/README.md +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/SECURITY.md +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/about/contributors.md +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/about/copyright.md +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/api/cli.md +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/api/db.md +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/api/exporters.md +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/api/importers.md +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/api/index.md +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/api/metacommands.md +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/dev/adding_db_adapters.md +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/dev/adding_exporters.md +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/dev/adding_importers.md +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/dev/adding_metacommands.md +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/dev/architecture.md +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/getting-started/installation.md +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/getting-started/requirements.md +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/getting-started/syntax.md +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/guides/debugging.md +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/guides/documentation.md +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/guides/encoding.md +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/guides/examples.md +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/guides/formatter.md +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/guides/logging.md +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/guides/sql_syntax.md +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/guides/usage.md +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/guides/using_scripts.md +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/images/Compare_planets.png +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/images/actions.png +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/images/actions2.png +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/images/checkboxes.png +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/images/connect.b64 +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/images/connect.png +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/images/create_conf.png +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/images/data_error1_screenshot.jpg +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/images/entry_form.png +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/images/execsql_console.png +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/images/execsql_logo_01.png +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/images/fatals.png +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/images/logo_small.png +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/images/pause_terminal.png +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/images/pause_terminal_sm.b64 +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/images/pause_terminal_sm.png +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/images/prompt_compare.png +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/images/set_build_commands.jpg +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/images/unit_conversions.b64 +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/images/unit_conversions_029.png +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/images/unmatched.png +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/images/vim_execsql_highlight.png +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/index.md +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/reference/configuration.md +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/reference/metacommands.md +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/reference/security.md +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/docs/reference/substitution_vars.md +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/extras/vscode-execsql/README.md +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/extras/vscode-execsql/package.json +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/extras/vscode-execsql/syntaxes/execsql.tmLanguage.json +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/justfile +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/scripts/generate_vscode_grammar.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/__init__.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/__main__.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/cli/__init__.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/cli/dsn.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/cli/help.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/cli/lint.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/cli/run.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/config.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/db/__init__.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/db/access.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/db/base.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/db/dsn.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/db/duckdb.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/db/factory.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/db/firebird.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/db/mysql.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/db/oracle.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/db/postgres.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/db/sqlite.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/db/sqlserver.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/debug/__init__.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/debug/repl.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/exceptions.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/exporters/__init__.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/exporters/base.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/exporters/delimited.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/exporters/duckdb.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/exporters/feather.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/exporters/html.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/exporters/json.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/exporters/latex.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/exporters/markdown.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/exporters/ods.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/exporters/parquet.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/exporters/pretty.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/exporters/protocol.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/exporters/raw.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/exporters/sqlite.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/exporters/templates.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/exporters/values.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/exporters/xls.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/exporters/xlsx.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/exporters/xml.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/exporters/yaml.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/exporters/zip.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/format.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/gui/__init__.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/gui/base.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/gui/console.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/gui/desktop.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/gui/tui.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/importers/__init__.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/importers/base.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/importers/csv.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/importers/feather.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/importers/json.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/importers/ods.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/importers/xls.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/metacommands/__init__.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/metacommands/conditions.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/metacommands/connect.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/metacommands/control.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/metacommands/data.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/metacommands/debug.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/metacommands/dispatch.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/metacommands/io.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/metacommands/io_export.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/metacommands/io_fileops.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/metacommands/io_import.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/metacommands/io_write.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/metacommands/prompt.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/metacommands/script_ext.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/metacommands/system.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/models.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/parser.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/py.typed +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/script/__init__.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/script/control.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/script/engine.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/script/variables.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/state.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/types.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/utils/__init__.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/utils/auth.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/utils/crypto.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/utils/datetime.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/utils/errors.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/utils/fileio.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/utils/gui.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/utils/mail.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/utils/numeric.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/utils/regex.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/utils/strings.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/utils/timer.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/templates/README.md +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/templates/config_settings.sqlite +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/templates/example_config_prompt.sql +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/templates/execsql.conf +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/templates/make_config_db.sql +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/templates/md_compare.sql +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/templates/md_glossary.sql +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/templates/md_upsert.sql +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/templates/pg_compare.sql +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/templates/pg_glossary.sql +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/templates/pg_upsert.sql +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/templates/script_template.sql +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/templates/ss_compare.sql +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/templates/ss_glossary.sql +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/templates/ss_upsert.sql +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/__init__.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/cli/__init__.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/cli/test_cli.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/cli/test_cli_e2e.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/cli/test_cli_run.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/cli/test_lint.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/cli/test_ping.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/cli/test_profile.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/conftest.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/db/__init__.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/db/test_base.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/db/test_duckdb.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/db/test_factory.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/db/test_postgres.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/db/test_sqlite.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/db/test_sqlite_extra.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/__init__.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_base.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_db.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_delimited.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_duckdb_exporter.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_exporters.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_feather.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_html_extended.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_html_latex.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_json.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_json_extended.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_latex_extended.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_markdown.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_ods.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_parquet.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_pretty_extended.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_raw_extended.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_sqlite_exporter.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_templates.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_templates_extended.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_values_extended.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_xls_xlsx.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_xlsx.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_xml.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_yaml.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_zip.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/gui/__init__.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/gui/test_backends.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/gui/test_compare_stats.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/gui/test_compute_row_diffs.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/importers/__init__.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/importers/test_base_extended.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/importers/test_csv_edge_cases.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/importers/test_csv_importer.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/importers/test_feather_importer.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/importers/test_json_importer.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/importers/test_ods_importer.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/importers/test_xls_importer.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/integration/__init__.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/integration/conftest.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/integration/test_dsn.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/integration/test_duckdb.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/integration/test_mysql.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/integration/test_postgres.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/integration/test_sqlite.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/metacommands/__init__.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/metacommands/test_assert.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/metacommands/test_breakpoint.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/metacommands/test_connect.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/metacommands/test_io_export.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/metacommands/test_io_import.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/metacommands/test_metacommands.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/metacommands/test_metacommands_connect.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/metacommands/test_metacommands_data.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/metacommands/test_metacommands_extended.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/metacommands/test_metacommands_fileops_extra.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/metacommands/test_metacommands_io.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/metacommands/test_metacommands_io_write_extra.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/metacommands/test_metacommands_script_ext.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/metacommands/test_metacommands_system.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/metacommands/test_metacommands_system_extra.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/metacommands/test_row_count.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/test_config.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/test_config_data.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/test_config_extended.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/test_debug_repl.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/test_engine.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/test_error_messages.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/test_exceptions.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/test_format.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/test_mail.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/test_models.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/test_package.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/test_parser.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/test_registry.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/test_script.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/test_state.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/test_types.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/utils/__init__.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/utils/test_auth.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/utils/test_auth_extra.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/utils/test_crypto.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/utils/test_datetime.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/utils/test_errors.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/utils/test_errors_extra.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/utils/test_fileio.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/utils/test_fileio_extra.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/utils/test_numeric.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/utils/test_regex.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/utils/test_strings.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/utils/test_timer.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/tests/utils/test_timer_extra.py +0 -0
- {execsql2-2.15.7 → execsql2-2.15.8}/zensical.toml +0 -0
|
@@ -13,6 +13,25 @@ ______________________________________________________________________
|
|
|
13
13
|
|
|
14
14
|
______________________________________________________________________
|
|
15
15
|
|
|
16
|
+
## [2.15.8] - 2026-04-20
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- `PG_UPSERT` / `PG_UPSERT QA` / `PG_UPSERT CHECK` now support the `STRICT_COLUMNS` keyword. When present, all missing columns in staging tables are treated as errors (not just PK and NOT NULL/no-default columns). Maps to pg-upsert's `strict_columns=True` parameter.
|
|
21
|
+
- New substitution variable `$PG_UPSERT_QA_WARNINGS` — a comma-separated list of table names that received WARNING-level QA findings. Scripts can use this to react to warnings without parsing `$PG_UPSERT_RESULT_JSON`.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- `$PG_UPSERT_RESULT_JSON` now includes a `qa_warnings` array per table (previously only `qa_errors` was present). This reflects pg-upsert v1.22's severity-aware QA model.
|
|
26
|
+
- Minimum pg-upsert version bumped from `>=1.21.0` to `>=1.22.0`.
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- `PG_UPSERT QA` and `PG_UPSERT CHECK` now capture all QA findings (errors + warnings) instead of only errors, so `$PG_UPSERT_RESULT_JSON` includes the full picture.
|
|
31
|
+
- Fixed compatibility with pg-upsert v1.22.0 where `TableResult.qa_errors` became a read-only property (now writes to `_qa_findings` field).
|
|
32
|
+
|
|
33
|
+
______________________________________________________________________
|
|
34
|
+
|
|
16
35
|
## [2.15.7] - 2026-04-20
|
|
17
36
|
|
|
18
37
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: execsql2
|
|
3
|
-
Version: 2.15.
|
|
3
|
+
Version: 2.15.8
|
|
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
|
|
@@ -52,7 +52,7 @@ Requires-Dist: keyring; extra == 'all'
|
|
|
52
52
|
Requires-Dist: odfpy; extra == 'all'
|
|
53
53
|
Requires-Dist: openpyxl; extra == 'all'
|
|
54
54
|
Requires-Dist: oracledb; extra == 'all'
|
|
55
|
-
Requires-Dist: pg-upsert>=1.
|
|
55
|
+
Requires-Dist: pg-upsert>=1.22.0; extra == 'all'
|
|
56
56
|
Requires-Dist: polars; extra == 'all'
|
|
57
57
|
Requires-Dist: psycopg2-binary; extra == 'all'
|
|
58
58
|
Requires-Dist: pymysql; extra == 'all'
|
|
@@ -117,7 +117,7 @@ Requires-Dist: oracledb; extra == 'oracle'
|
|
|
117
117
|
Provides-Extra: postgres
|
|
118
118
|
Requires-Dist: psycopg2-binary; extra == 'postgres'
|
|
119
119
|
Provides-Extra: upsert
|
|
120
|
-
Requires-Dist: pg-upsert>=1.
|
|
120
|
+
Requires-Dist: pg-upsert>=1.22.0; extra == 'upsert'
|
|
121
121
|
Description-Content-Type: text/markdown
|
|
122
122
|
|
|
123
123
|
> [!NOTE]
|
|
@@ -41,14 +41,14 @@ ______________________________________________________________________
|
|
|
41
41
|
|
|
42
42
|
### Metacommands
|
|
43
43
|
|
|
44
|
-
| Metacommand | Description
|
|
45
|
-
| ---------------------- |
|
|
46
|
-
| `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.
|
|
47
|
-
| `BREAKPOINT` | Pause script execution and drop into an interactive debug REPL. See [Debugging](#debugging) below for full details.
|
|
48
|
-
| `CONFIG SHOW_PROGRESS` | Enable the Rich progress bar for IMPORT operations at runtime.
|
|
49
|
-
| `CONFIG LOG_SQL` | Enable SQL query audit logging — writes executed SQL to the log file.
|
|
50
|
-
| `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`, and `
|
|
51
|
-
| `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.
|
|
44
|
+
| Metacommand | Description |
|
|
45
|
+
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
46
|
+
| `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. |
|
|
47
|
+
| `BREAKPOINT` | Pause script execution and drop into an interactive debug REPL. See [Debugging](#debugging) below for full details. |
|
|
48
|
+
| `CONFIG SHOW_PROGRESS` | Enable the Rich progress bar for IMPORT operations at runtime. |
|
|
49
|
+
| `CONFIG LOG_SQL` | Enable SQL query audit logging — writes executed SQL to the log file. |
|
|
50
|
+
| `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`). |
|
|
51
|
+
| `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. |
|
|
52
52
|
|
|
53
53
|
### Conditional Tests
|
|
54
54
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "execsql2"
|
|
7
|
-
version = "2.15.
|
|
7
|
+
version = "2.15.8"
|
|
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" }
|
|
@@ -61,7 +61,7 @@ formats = ["odfpy", "xlrd", "openpyxl", "Jinja2", "polars", "tables", "PyYAML"]
|
|
|
61
61
|
auth = ["keyring"]
|
|
62
62
|
auth-plaintext = ["keyring", "keyrings.alt"]
|
|
63
63
|
auth-encrypted = ["keyring", "keyrings.alt", "pycryptodome"]
|
|
64
|
-
upsert = ["pg-upsert>=1.
|
|
64
|
+
upsert = ["pg-upsert>=1.22.0"]
|
|
65
65
|
# Convenience groups
|
|
66
66
|
all-db = [
|
|
67
67
|
"psycopg2-binary",
|
|
@@ -164,7 +164,7 @@ skip-magic-trailing-comma = false
|
|
|
164
164
|
line-ending = "auto"
|
|
165
165
|
|
|
166
166
|
[tool.bumpversion]
|
|
167
|
-
current_version = "2.15.
|
|
167
|
+
current_version = "2.15.8"
|
|
168
168
|
commit = true
|
|
169
169
|
commit_args = "--no-verify"
|
|
170
170
|
tag = true
|
|
@@ -26,11 +26,11 @@ 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|CLEANUP|EXPORT_FAILURES|EXPORT_FORMAT|EXPORT_MAX_ROWS)\b|\s*$)",
|
|
29
|
+
r"\bEXCLUDE\s+([\w\s,]+?)(?=\s+(?:METHOD|COMMIT|INTERACTIVE|COMPACT|EXCLUDE_NULL|LOGFILE|CLEANUP|EXPORT_FAILURES|EXPORT_FORMAT|EXPORT_MAX_ROWS|STRICT_COLUMNS)\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|CLEANUP|EXPORT_FAILURES|EXPORT_FORMAT|EXPORT_MAX_ROWS)\b|\s*$)",
|
|
33
|
+
r"\bEXCLUDE_NULL\s+([\w\s,]+?)(?=\s+(?:METHOD|COMMIT|INTERACTIVE|COMPACT|EXCLUDE|LOGFILE|CLEANUP|EXPORT_FAILURES|EXPORT_FORMAT|EXPORT_MAX_ROWS|STRICT_COLUMNS)\b|\s*$)",
|
|
34
34
|
re.IGNORECASE,
|
|
35
35
|
)
|
|
36
36
|
_KW_COMMIT = re.compile(r"\bCOMMIT\b", re.IGNORECASE)
|
|
@@ -44,10 +44,11 @@ _KW_EXPORT_FAILURES = re.compile(
|
|
|
44
44
|
)
|
|
45
45
|
_KW_EXPORT_FORMAT = re.compile(r"\bEXPORT_FORMAT\s+(\S+)", re.IGNORECASE)
|
|
46
46
|
_KW_EXPORT_MAX_ROWS = re.compile(r"\bEXPORT_MAX_ROWS\s+(\S+)", re.IGNORECASE)
|
|
47
|
+
_KW_STRICT_COLUMNS = re.compile(r"\bSTRICT_COLUMNS\b", re.IGNORECASE)
|
|
47
48
|
|
|
48
49
|
# All recognized keywords — used to split table names from options.
|
|
49
50
|
_ALL_KEYWORDS = re.compile(
|
|
50
|
-
r"\b(?:METHOD|COMMIT|INTERACTIVE|COMPACT|EXCLUDE_NULL|EXCLUDE|LOGFILE|CLEANUP|EXPORT_FAILURES|EXPORT_FORMAT|EXPORT_MAX_ROWS)\b",
|
|
51
|
+
r"\b(?:METHOD|COMMIT|INTERACTIVE|COMPACT|EXCLUDE_NULL|EXCLUDE|LOGFILE|CLEANUP|EXPORT_FAILURES|EXPORT_FORMAT|EXPORT_MAX_ROWS|STRICT_COLUMNS)\b",
|
|
51
52
|
re.IGNORECASE,
|
|
52
53
|
)
|
|
53
54
|
|
|
@@ -145,6 +146,7 @@ def _parse_tables_and_options(tail: str) -> dict[str, Any]:
|
|
|
145
146
|
"export_failures": export_failures,
|
|
146
147
|
"export_format": export_format,
|
|
147
148
|
"export_max_rows": export_max_rows,
|
|
149
|
+
"strict_columns": bool(_KW_STRICT_COLUMNS.search(opts_part)),
|
|
148
150
|
}
|
|
149
151
|
|
|
150
152
|
|
|
@@ -191,6 +193,9 @@ def _set_subvars(result: Any) -> None:
|
|
|
191
193
|
sv("$PG_UPSERT_STARTED_AT", result.started_at)
|
|
192
194
|
sv("$PG_UPSERT_FINISHED_AT", result.finished_at)
|
|
193
195
|
sv("$PG_UPSERT_RESULT_JSON", json.dumps(result.to_dict(), separators=(",", ":")))
|
|
196
|
+
# Warnings: tables with WARNING-level findings (still qa_passed=True).
|
|
197
|
+
warned_tables = [t.table_name for t in result.tables if t.qa_warnings]
|
|
198
|
+
sv("$PG_UPSERT_QA_WARNINGS", ", ".join(warned_tables) if warned_tables else "")
|
|
194
199
|
# Default export path subvar to empty; _export_failures_if_requested
|
|
195
200
|
# will overwrite it with the actual path if an export was produced.
|
|
196
201
|
sv("$PG_UPSERT_EXPORT_PATH", "")
|
|
@@ -230,16 +235,20 @@ def _require_postgres(db: Any, metacommandline: str | None) -> None:
|
|
|
230
235
|
)
|
|
231
236
|
|
|
232
237
|
|
|
233
|
-
def
|
|
234
|
-
"""Build an UpsertResult from ``ups.
|
|
238
|
+
def _build_result_from_qa_findings(ups: Any) -> Any:
|
|
239
|
+
"""Build an UpsertResult from ``ups.qa_findings`` after a QA/CHECK run.
|
|
240
|
+
|
|
241
|
+
Uses ``qa_findings`` (all findings: errors + warnings) so that
|
|
242
|
+
``$PG_UPSERT_RESULT_JSON`` includes both severity levels.
|
|
243
|
+
"""
|
|
235
244
|
from pg_upsert.models import TableResult, UpsertResult
|
|
236
245
|
|
|
237
246
|
table_results: dict[str, Any] = {}
|
|
238
247
|
for table_name in ups.tables:
|
|
239
248
|
table_results[table_name] = TableResult(table_name=table_name)
|
|
240
|
-
for
|
|
241
|
-
if
|
|
242
|
-
table_results[
|
|
249
|
+
for finding in ups.qa_findings:
|
|
250
|
+
if finding.table in table_results:
|
|
251
|
+
table_results[finding.table]._qa_findings.append(finding)
|
|
243
252
|
return UpsertResult(
|
|
244
253
|
tables=list(table_results.values()),
|
|
245
254
|
committed=False,
|
|
@@ -289,6 +298,7 @@ def _create_pgupsert(
|
|
|
289
298
|
"upsert_method": opts["method"],
|
|
290
299
|
"exclude_cols": opts["exclude_cols"],
|
|
291
300
|
"exclude_null_check_cols": opts["exclude_null_check_cols"],
|
|
301
|
+
"strict_columns": opts.get("strict_columns", False),
|
|
292
302
|
"ui_mode": ui_mode,
|
|
293
303
|
"callback": _make_callback(),
|
|
294
304
|
}
|
|
@@ -508,7 +518,7 @@ def x_pg_upsert_qa(**kwargs: Any) -> None:
|
|
|
508
518
|
finally:
|
|
509
519
|
_detach_log_handlers(loggers, handlers, prev_levels)
|
|
510
520
|
|
|
511
|
-
result =
|
|
521
|
+
result = _build_result_from_qa_findings(ups)
|
|
512
522
|
_set_subvars(result)
|
|
513
523
|
_export_failures_if_requested(result, opts, metacommandline)
|
|
514
524
|
if opts.get("cleanup"):
|
|
@@ -551,7 +561,7 @@ def x_pg_upsert_check(**kwargs: Any) -> None:
|
|
|
551
561
|
finally:
|
|
552
562
|
_detach_log_handlers(loggers, handlers, prev_levels)
|
|
553
563
|
|
|
554
|
-
result =
|
|
564
|
+
result = _build_result_from_qa_findings(ups)
|
|
555
565
|
_set_subvars(result)
|
|
556
566
|
_export_failures_if_requested(result, opts, metacommandline)
|
|
557
567
|
if opts.get("cleanup"):
|
|
@@ -17,7 +17,7 @@ import pytest
|
|
|
17
17
|
from execsql.exceptions import ErrInfo
|
|
18
18
|
from execsql.metacommands.upsert import (
|
|
19
19
|
_FileWriterHandler,
|
|
20
|
-
|
|
20
|
+
_build_result_from_qa_findings,
|
|
21
21
|
_parse_tables_and_options,
|
|
22
22
|
_qa_failure_msg,
|
|
23
23
|
x_pg_upsert,
|
|
@@ -46,12 +46,20 @@ class FakeTableResult:
|
|
|
46
46
|
table_name: str = ""
|
|
47
47
|
rows_updated: int = 0
|
|
48
48
|
rows_inserted: int = 0
|
|
49
|
-
|
|
49
|
+
_qa_findings: list[Any] = field(default_factory=list)
|
|
50
50
|
|
|
51
51
|
@property
|
|
52
52
|
def qa_passed(self) -> bool:
|
|
53
53
|
return len(self.qa_errors) == 0
|
|
54
54
|
|
|
55
|
+
@property
|
|
56
|
+
def qa_errors(self) -> list[Any]:
|
|
57
|
+
return [f for f in self._qa_findings if getattr(f, "severity", "ERROR") == "ERROR"]
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def qa_warnings(self) -> list[Any]:
|
|
61
|
+
return [f for f in self._qa_findings if getattr(f, "severity", "ERROR") == "WARNING"]
|
|
62
|
+
|
|
55
63
|
def to_dict(self) -> dict[str, Any]:
|
|
56
64
|
return {
|
|
57
65
|
"table_name": self.table_name,
|
|
@@ -59,6 +67,7 @@ class FakeTableResult:
|
|
|
59
67
|
"rows_inserted": self.rows_inserted,
|
|
60
68
|
"qa_passed": self.qa_passed,
|
|
61
69
|
"qa_errors": [e.to_dict() for e in self.qa_errors],
|
|
70
|
+
"qa_warnings": [e.to_dict() for e in self.qa_warnings],
|
|
62
71
|
}
|
|
63
72
|
|
|
64
73
|
|
|
@@ -231,6 +240,20 @@ class TestParseTablesAndOptions:
|
|
|
231
240
|
result = _parse_tables_and_options("books COMPACT")
|
|
232
241
|
assert result["compact"] is True
|
|
233
242
|
|
|
243
|
+
def test_strict_columns_keyword(self):
|
|
244
|
+
result = _parse_tables_and_options("books STRICT_COLUMNS")
|
|
245
|
+
assert result["strict_columns"] is True
|
|
246
|
+
|
|
247
|
+
def test_strict_columns_default_false(self):
|
|
248
|
+
result = _parse_tables_and_options("books")
|
|
249
|
+
assert result["strict_columns"] is False
|
|
250
|
+
|
|
251
|
+
def test_strict_columns_with_other_keywords(self):
|
|
252
|
+
result = _parse_tables_and_options("books STRICT_COLUMNS COMMIT METHOD update")
|
|
253
|
+
assert result["strict_columns"] is True
|
|
254
|
+
assert result["commit"] is True
|
|
255
|
+
assert result["method"] == "update"
|
|
256
|
+
|
|
234
257
|
def test_exclude_cols(self):
|
|
235
258
|
result = _parse_tables_and_options("books EXCLUDE rev_time, created_at COMMIT")
|
|
236
259
|
assert result["tables"] == ["books"]
|
|
@@ -524,7 +547,7 @@ class TestFullMode:
|
|
|
524
547
|
tables=[
|
|
525
548
|
FakeTableResult(
|
|
526
549
|
table_name="books",
|
|
527
|
-
|
|
550
|
+
_qa_findings=[FakeQAError(table="books", check_type="null", details="col1 (5)")],
|
|
528
551
|
),
|
|
529
552
|
],
|
|
530
553
|
staging_schema="staging",
|
|
@@ -606,7 +629,7 @@ class TestQAMode:
|
|
|
606
629
|
with (
|
|
607
630
|
patch("execsql.metacommands.upsert._require_pg_upsert"),
|
|
608
631
|
patch("execsql.metacommands.upsert._create_pgupsert") as mock_create,
|
|
609
|
-
patch("execsql.metacommands.upsert.
|
|
632
|
+
patch("execsql.metacommands.upsert._build_result_from_qa_findings") as mock_build,
|
|
610
633
|
):
|
|
611
634
|
mock_ups = mock_create.return_value
|
|
612
635
|
mock_ups.qa_all.return_value = mock_ups
|
|
@@ -629,7 +652,7 @@ class TestQAMode:
|
|
|
629
652
|
with (
|
|
630
653
|
patch("execsql.metacommands.upsert._require_pg_upsert"),
|
|
631
654
|
patch("execsql.metacommands.upsert._create_pgupsert") as mock_create,
|
|
632
|
-
patch("execsql.metacommands.upsert.
|
|
655
|
+
patch("execsql.metacommands.upsert._build_result_from_qa_findings") as mock_build,
|
|
633
656
|
):
|
|
634
657
|
mock_ups = mock_create.return_value
|
|
635
658
|
mock_ups.qa_all.return_value = mock_ups
|
|
@@ -661,7 +684,7 @@ class TestCheckMode:
|
|
|
661
684
|
with (
|
|
662
685
|
patch("execsql.metacommands.upsert._require_pg_upsert"),
|
|
663
686
|
patch("execsql.metacommands.upsert._create_pgupsert") as mock_create,
|
|
664
|
-
patch("execsql.metacommands.upsert.
|
|
687
|
+
patch("execsql.metacommands.upsert._build_result_from_qa_findings") as mock_build,
|
|
665
688
|
):
|
|
666
689
|
mock_ups = mock_create.return_value
|
|
667
690
|
mock_ups.qa_column_existence.return_value = mock_ups
|
|
@@ -856,12 +879,12 @@ class TestQAFailureMessage:
|
|
|
856
879
|
tables=[
|
|
857
880
|
FakeTableResult(
|
|
858
881
|
table_name="books",
|
|
859
|
-
|
|
882
|
+
_qa_findings=[FakeQAError(table="books", check_type="null", details="title (3)")],
|
|
860
883
|
),
|
|
861
884
|
FakeTableResult(table_name="authors"), # passes
|
|
862
885
|
FakeTableResult(
|
|
863
886
|
table_name="genres",
|
|
864
|
-
|
|
887
|
+
_qa_findings=[FakeQAError(table="genres", check_type="fk", details="parent_id (5)")],
|
|
865
888
|
),
|
|
866
889
|
],
|
|
867
890
|
)
|
|
@@ -873,7 +896,7 @@ class TestQAFailureMessage:
|
|
|
873
896
|
tables=[
|
|
874
897
|
FakeTableResult(
|
|
875
898
|
table_name="books",
|
|
876
|
-
|
|
899
|
+
_qa_findings=[FakeQAError(table="books", check_type="pk", details="id (2)")],
|
|
877
900
|
),
|
|
878
901
|
],
|
|
879
902
|
)
|
|
@@ -918,7 +941,7 @@ class TestQAModeFailure:
|
|
|
918
941
|
tables=[
|
|
919
942
|
FakeTableResult(
|
|
920
943
|
table_name="books",
|
|
921
|
-
|
|
944
|
+
_qa_findings=[FakeQAError(table="books", check_type="null", details="col1 (5)")],
|
|
922
945
|
),
|
|
923
946
|
],
|
|
924
947
|
staging_schema="staging",
|
|
@@ -928,7 +951,7 @@ class TestQAModeFailure:
|
|
|
928
951
|
with (
|
|
929
952
|
patch("execsql.metacommands.upsert._require_pg_upsert"),
|
|
930
953
|
patch("execsql.metacommands.upsert._create_pgupsert") as mock_create,
|
|
931
|
-
patch("execsql.metacommands.upsert.
|
|
954
|
+
patch("execsql.metacommands.upsert._build_result_from_qa_findings") as mock_build,
|
|
932
955
|
):
|
|
933
956
|
mock_ups = mock_create.return_value
|
|
934
957
|
mock_ups.qa_all.return_value = mock_ups
|
|
@@ -951,7 +974,7 @@ class TestCheckModeFailure:
|
|
|
951
974
|
tables=[
|
|
952
975
|
FakeTableResult(
|
|
953
976
|
table_name="books",
|
|
954
|
-
|
|
977
|
+
_qa_findings=[FakeQAError(table="books", check_type="column", details="missing_col")],
|
|
955
978
|
),
|
|
956
979
|
],
|
|
957
980
|
staging_schema="staging",
|
|
@@ -961,7 +984,7 @@ class TestCheckModeFailure:
|
|
|
961
984
|
with (
|
|
962
985
|
patch("execsql.metacommands.upsert._require_pg_upsert"),
|
|
963
986
|
patch("execsql.metacommands.upsert._create_pgupsert") as mock_create,
|
|
964
|
-
patch("execsql.metacommands.upsert.
|
|
987
|
+
patch("execsql.metacommands.upsert._build_result_from_qa_findings") as mock_build,
|
|
965
988
|
):
|
|
966
989
|
mock_ups = mock_create.return_value
|
|
967
990
|
mock_ups.qa_column_existence.return_value = mock_ups
|
|
@@ -978,7 +1001,7 @@ class TestCheckModeFailure:
|
|
|
978
1001
|
|
|
979
1002
|
|
|
980
1003
|
# ---------------------------------------------------------------------------
|
|
981
|
-
#
|
|
1004
|
+
# _build_result_from_qa_findings direct test
|
|
982
1005
|
# ---------------------------------------------------------------------------
|
|
983
1006
|
|
|
984
1007
|
|
|
@@ -986,7 +1009,7 @@ class TestBuildResultFromQAErrors:
|
|
|
986
1009
|
def test_groups_errors_by_table(self):
|
|
987
1010
|
mock_ups = MagicMock()
|
|
988
1011
|
mock_ups.tables = ["books", "authors"]
|
|
989
|
-
mock_ups.
|
|
1012
|
+
mock_ups.qa_findings = [
|
|
990
1013
|
FakeQAError(table="books", check_type="null", details="title (3)"),
|
|
991
1014
|
FakeQAError(table="authors", check_type="pk", details="id (2)"),
|
|
992
1015
|
FakeQAError(table="books", check_type="fk", details="pub_id (1)"),
|
|
@@ -995,7 +1018,7 @@ class TestBuildResultFromQAErrors:
|
|
|
995
1018
|
mock_ups.base_schema = "public"
|
|
996
1019
|
mock_ups.upsert_method = "upsert"
|
|
997
1020
|
|
|
998
|
-
result =
|
|
1021
|
+
result = _build_result_from_qa_findings(mock_ups)
|
|
999
1022
|
assert result.staging_schema == "staging"
|
|
1000
1023
|
assert result.base_schema == "public"
|
|
1001
1024
|
assert result.upsert_method == "upsert"
|
|
@@ -1010,12 +1033,12 @@ class TestBuildResultFromQAErrors:
|
|
|
1010
1033
|
def test_no_errors_means_passed(self):
|
|
1011
1034
|
mock_ups = MagicMock()
|
|
1012
1035
|
mock_ups.tables = ["books"]
|
|
1013
|
-
mock_ups.
|
|
1036
|
+
mock_ups.qa_findings = []
|
|
1014
1037
|
mock_ups.staging_schema = "staging"
|
|
1015
1038
|
mock_ups.base_schema = "public"
|
|
1016
1039
|
mock_ups.upsert_method = "upsert"
|
|
1017
1040
|
|
|
1018
|
-
result =
|
|
1041
|
+
result = _build_result_from_qa_findings(mock_ups)
|
|
1019
1042
|
assert result.qa_passed
|
|
1020
1043
|
|
|
1021
1044
|
|
|
@@ -1393,7 +1416,7 @@ class TestExportFailures:
|
|
|
1393
1416
|
tables=[
|
|
1394
1417
|
FakeTableResult(
|
|
1395
1418
|
table_name="books",
|
|
1396
|
-
|
|
1419
|
+
_qa_findings=[FakeQAError(table="books", check_type="null", details="col1")],
|
|
1397
1420
|
),
|
|
1398
1421
|
],
|
|
1399
1422
|
staging_schema="staging",
|
|
@@ -1500,7 +1523,7 @@ class TestExportFailures:
|
|
|
1500
1523
|
):
|
|
1501
1524
|
mock_ups = mock_create.return_value
|
|
1502
1525
|
mock_ups.tables = ["books"]
|
|
1503
|
-
mock_ups.
|
|
1526
|
+
mock_ups.qa_findings = []
|
|
1504
1527
|
mock_ups.staging_schema = "staging"
|
|
1505
1528
|
mock_ups.base_schema = "public"
|
|
1506
1529
|
mock_ups.upsert_method = "upsert"
|
|
@@ -1516,7 +1539,7 @@ class TestExportFailures:
|
|
|
1516
1539
|
return r
|
|
1517
1540
|
|
|
1518
1541
|
with patch(
|
|
1519
|
-
"execsql.metacommands.upsert.
|
|
1542
|
+
"execsql.metacommands.upsert._build_result_from_qa_findings",
|
|
1520
1543
|
side_effect=_fake_build,
|
|
1521
1544
|
):
|
|
1522
1545
|
x_pg_upsert_qa(
|
|
@@ -1536,7 +1559,7 @@ class TestExportFailures:
|
|
|
1536
1559
|
):
|
|
1537
1560
|
mock_ups = mock_create.return_value
|
|
1538
1561
|
mock_ups.tables = ["books"]
|
|
1539
|
-
mock_ups.
|
|
1562
|
+
mock_ups.qa_findings = []
|
|
1540
1563
|
mock_ups.staging_schema = "staging"
|
|
1541
1564
|
mock_ups.base_schema = "public"
|
|
1542
1565
|
mock_ups.upsert_method = "upsert"
|
|
@@ -1555,7 +1578,7 @@ class TestExportFailures:
|
|
|
1555
1578
|
return r
|
|
1556
1579
|
|
|
1557
1580
|
with patch(
|
|
1558
|
-
"execsql.metacommands.upsert.
|
|
1581
|
+
"execsql.metacommands.upsert._build_result_from_qa_findings",
|
|
1559
1582
|
side_effect=_fake_build,
|
|
1560
1583
|
):
|
|
1561
1584
|
x_pg_upsert_check(
|
|
@@ -648,7 +648,7 @@ wheels = [
|
|
|
648
648
|
|
|
649
649
|
[[package]]
|
|
650
650
|
name = "execsql2"
|
|
651
|
-
version = "2.15.
|
|
651
|
+
version = "2.15.8"
|
|
652
652
|
source = { editable = "." }
|
|
653
653
|
dependencies = [
|
|
654
654
|
{ name = "python-dateutil" },
|
|
@@ -780,7 +780,7 @@ requires-dist = [
|
|
|
780
780
|
{ name = "openpyxl", marker = "extra == 'formats'" },
|
|
781
781
|
{ name = "oracledb", marker = "extra == 'all-db'" },
|
|
782
782
|
{ name = "oracledb", marker = "extra == 'oracle'" },
|
|
783
|
-
{ name = "pg-upsert", marker = "extra == 'upsert'", specifier = ">=1.
|
|
783
|
+
{ name = "pg-upsert", marker = "extra == 'upsert'", specifier = ">=1.22.0" },
|
|
784
784
|
{ name = "polars", marker = "extra == 'formats'" },
|
|
785
785
|
{ name = "pre-commit", marker = "extra == 'dev'", specifier = ">=3.5.0" },
|
|
786
786
|
{ name = "psycopg2-binary", marker = "extra == 'all-db'" },
|
|
@@ -1809,7 +1809,7 @@ wheels = [
|
|
|
1809
1809
|
|
|
1810
1810
|
[[package]]
|
|
1811
1811
|
name = "pg-upsert"
|
|
1812
|
-
version = "1.
|
|
1812
|
+
version = "1.22.0"
|
|
1813
1813
|
source = { registry = "https://pypi.org/simple" }
|
|
1814
1814
|
dependencies = [
|
|
1815
1815
|
{ name = "psycopg2-binary" },
|
|
@@ -1817,9 +1817,9 @@ dependencies = [
|
|
|
1817
1817
|
{ name = "rich" },
|
|
1818
1818
|
{ name = "typer" },
|
|
1819
1819
|
]
|
|
1820
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
1820
|
+
sdist = { url = "https://files.pythonhosted.org/packages/1a/f8/68b0b02d2e76561cfa580bee320eaf1884666d0d8faa2f2873650e907eef/pg_upsert-1.22.0.tar.gz", hash = "sha256:6ba63d3990689685464695c897be081e9d774c375228cc0bf1ee44340e6a6a6f", size = 1558932, upload-time = "2026-04-20T15:54:46.946Z" }
|
|
1821
1821
|
wheels = [
|
|
1822
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
1822
|
+
{ url = "https://files.pythonhosted.org/packages/c3/f3/965c57af3bc89103d2f04435277c2c5e7e9b2bb9ce0653f434c55a570e4a/pg_upsert-1.22.0-py3-none-any.whl", hash = "sha256:c823af17c996ba5cb3ed8a2393882ea4a830a14d04965c090b73164b806f2309", size = 98581, upload-time = "2026-04-20T15:54:44.816Z" },
|
|
1823
1823
|
]
|
|
1824
1824
|
|
|
1825
1825
|
[[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
|