execsql2 2.12.2__tar.gz → 2.12.5__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.2 → execsql2-2.12.5}/CHANGELOG.md +23 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/PKG-INFO +4 -1
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/about/divergence.md +13 -6
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/getting-started/installation.md +1 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/reference/metacommands.md +116 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/extras/vscode-execsql/syntaxes/execsql.tmLanguage.json +1 -1
- {execsql2-2.12.2 → execsql2-2.12.5}/pyproject.toml +4 -2
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/db/base.py +7 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/exporters/delimited.py +44 -2
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/metacommands/__init__.py +9 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/metacommands/dispatch.py +30 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/metacommands/io_fileops.py +4 -0
- execsql2-2.12.5/src/execsql/metacommands/upsert.py +448 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/script/__init__.py +4 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/script/engine.py +55 -23
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/script/variables.py +35 -2
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/cli/test_cli.py +6 -1
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/exporters/test_delimited.py +12 -3
- execsql2-2.12.5/tests/metacommands/test_pg_upsert.py +997 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/uv.lock +30 -18
- {execsql2-2.12.2 → execsql2-2.12.5}/.claude/agents/dba.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/.claude/agents/herald.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/.claude/agents/inspector.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/.claude/agents/liaison.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/.claude/agents/oracle.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/.claude/agents/patcher.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/.claude/agents/qa.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/.claude/agents/scribe.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/.claude/commands/code-oracle.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/.claude/commands/migrate.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/.claude/commands/review-changes.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/.claude/commands/test-module.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/.claude/commands/update-changelog.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/.claude/commands/where-is.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/.claude/project_context.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/.claude/state/status.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/.github/workflows/ci-cd.yml +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/.gitignore +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/.pre-commit-config.yaml +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/.pre-commit-hooks.yaml +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/.python-version +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/.readthedocs.yaml +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/CLAUDE.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/CONTRIBUTING.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/LICENSE.txt +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/NOTICE +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/README.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/SECURITY.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/about/contributors.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/about/copyright.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/api/cli.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/api/db.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/api/exporters.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/api/importers.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/api/index.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/api/metacommands.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/dev/adding_db_adapters.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/dev/adding_exporters.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/dev/adding_importers.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/dev/adding_metacommands.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/dev/architecture.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/getting-started/requirements.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/getting-started/syntax.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/guides/debugging.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/guides/documentation.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/guides/encoding.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/guides/examples.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/guides/formatter.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/guides/logging.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/guides/sql_syntax.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/guides/usage.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/guides/using_scripts.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/images/Compare_planets.png +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/images/actions.png +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/images/actions2.png +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/images/checkboxes.png +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/images/connect.b64 +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/images/connect.png +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/images/create_conf.png +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/images/data_error1_screenshot.jpg +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/images/entry_form.png +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/images/execsql_console.png +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/images/execsql_logo_01.png +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/images/fatals.png +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/images/logo_small.png +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/images/pause_terminal.png +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/images/pause_terminal_sm.b64 +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/images/pause_terminal_sm.png +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/images/prompt_compare.png +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/images/set_build_commands.jpg +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/images/unit_conversions.b64 +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/images/unit_conversions_029.png +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/images/unmatched.png +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/images/vim_execsql_highlight.png +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/index.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/reference/configuration.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/reference/security.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/docs/reference/substitution_vars.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/extras/vscode-execsql/README.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/extras/vscode-execsql/package.json +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/justfile +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/scripts/generate_vscode_grammar.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/__init__.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/__main__.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/cli/__init__.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/cli/dsn.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/cli/help.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/cli/lint.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/cli/run.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/config.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/constants.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/db/__init__.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/db/access.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/db/dsn.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/db/duckdb.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/db/factory.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/db/firebird.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/db/mysql.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/db/oracle.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/db/postgres.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/db/sqlite.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/db/sqlserver.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/debug/__init__.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/debug/repl.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/exceptions.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/exporters/__init__.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/exporters/base.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/exporters/duckdb.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/exporters/feather.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/exporters/html.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/exporters/json.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/exporters/latex.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/exporters/markdown.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/exporters/ods.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/exporters/parquet.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/exporters/pretty.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/exporters/protocol.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/exporters/raw.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/exporters/sqlite.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/exporters/templates.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/exporters/values.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/exporters/xls.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/exporters/xlsx.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/exporters/xml.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/exporters/yaml.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/exporters/zip.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/format.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/gui/__init__.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/gui/base.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/gui/console.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/gui/desktop.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/gui/tui.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/importers/__init__.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/importers/base.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/importers/csv.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/importers/feather.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/importers/ods.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/importers/xls.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/metacommands/conditions.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/metacommands/connect.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/metacommands/control.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/metacommands/data.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/metacommands/debug.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/metacommands/io.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/metacommands/io_export.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/metacommands/io_import.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/metacommands/io_write.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/metacommands/prompt.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/metacommands/script_ext.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/metacommands/system.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/models.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/parser.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/py.typed +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/script/control.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/state.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/types.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/utils/__init__.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/utils/auth.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/utils/crypto.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/utils/datetime.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/utils/errors.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/utils/fileio.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/utils/gui.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/utils/mail.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/utils/numeric.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/utils/regex.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/utils/strings.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/src/execsql/utils/timer.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/templates/README.md +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/templates/config_settings.sqlite +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/templates/example_config_prompt.sql +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/templates/execsql.conf +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/templates/make_config_db.sql +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/templates/md_compare.sql +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/templates/md_glossary.sql +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/templates/md_upsert.sql +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/templates/pg_compare.sql +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/templates/pg_glossary.sql +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/templates/pg_upsert.sql +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/templates/script_template.sql +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/templates/ss_compare.sql +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/templates/ss_glossary.sql +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/templates/ss_upsert.sql +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/__init__.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/cli/__init__.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/cli/test_cli_e2e.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/cli/test_cli_run.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/cli/test_lint.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/cli/test_ping.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/cli/test_profile.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/conftest.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/db/__init__.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/db/test_base.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/db/test_duckdb.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/db/test_factory.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/db/test_postgres.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/db/test_sqlite.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/db/test_sqlite_extra.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/exporters/__init__.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/exporters/test_base.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/exporters/test_db.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/exporters/test_duckdb_exporter.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/exporters/test_exporters.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/exporters/test_feather.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/exporters/test_html_latex.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/exporters/test_json.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/exporters/test_markdown.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/exporters/test_ods.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/exporters/test_parquet.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/exporters/test_sqlite_exporter.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/exporters/test_templates.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/exporters/test_xls_xlsx.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/exporters/test_xlsx.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/exporters/test_xml.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/exporters/test_yaml.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/exporters/test_zip.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/gui/__init__.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/gui/test_backends.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/importers/__init__.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/importers/test_csv_importer.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/importers/test_feather_importer.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/importers/test_ods_importer.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/importers/test_xls_importer.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/integration/__init__.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/integration/conftest.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/integration/test_dsn.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/integration/test_duckdb.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/integration/test_mysql.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/integration/test_postgres.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/integration/test_sqlite.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/metacommands/__init__.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/metacommands/test_assert.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/metacommands/test_breakpoint.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/metacommands/test_connect.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/metacommands/test_io_export.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/metacommands/test_io_import.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/metacommands/test_metacommands.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/metacommands/test_metacommands_connect.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/metacommands/test_metacommands_data.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/metacommands/test_metacommands_extended.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/metacommands/test_metacommands_fileops_extra.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/metacommands/test_metacommands_io.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/metacommands/test_metacommands_io_write_extra.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/metacommands/test_metacommands_script_ext.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/metacommands/test_metacommands_system.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/metacommands/test_metacommands_system_extra.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/metacommands/test_row_count.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/test_config.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/test_config_data.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/test_constants.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/test_engine.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/test_error_messages.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/test_exceptions.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/test_format.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/test_mail.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/test_models.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/test_package.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/test_parser.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/test_registry.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/test_script.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/test_state.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/test_types.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/utils/__init__.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/utils/test_auth.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/utils/test_auth_extra.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/utils/test_crypto.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/utils/test_datetime.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/utils/test_errors.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/utils/test_errors_extra.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/utils/test_fileio.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/utils/test_fileio_extra.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/utils/test_numeric.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/utils/test_regex.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/utils/test_strings.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/utils/test_timer.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/tests/utils/test_timer_extra.py +0 -0
- {execsql2-2.12.2 → execsql2-2.12.5}/zensical.toml +0 -0
|
@@ -13,6 +13,29 @@ ______________________________________________________________________
|
|
|
13
13
|
|
|
14
14
|
______________________________________________________________________
|
|
15
15
|
|
|
16
|
+
## [2.12.5] - 2026-04-03
|
|
17
|
+
|
|
18
|
+
______________________________________________________________________
|
|
19
|
+
|
|
20
|
+
## [2.12.4] - 2026-04-03
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- New `PG_UPSERT` metacommand for QA-checked, FK-dependency-ordered upserts from a staging schema to a base schema on PostgreSQL. Integrates [pg-upsert](https://pg-upsert.readthedocs.io/) as an optional dependency (`pip install execsql2[upsert]`). Three modes: full pipeline (`PG_UPSERT FROM ... TO ... TABLES ...`), QA-only (`PG_UPSERT QA ...`), and schema check (`PG_UPSERT CHECK ...`). Supports `METHOD`, `COMMIT`, `INTERACTIVE`, `COMPACT`, `EXCLUDE`, `EXCLUDE_NULL`, and `LOGFILE` keywords. Sets 12 `$PG_UPSERT_*` substitution variables after execution.
|
|
25
|
+
|
|
26
|
+
______________________________________________________________________
|
|
27
|
+
|
|
28
|
+
## [2.12.3] - 2026-04-02
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- Performance: split `set_system_vars()` into static (once per script + on CONNECT/CHDIR) and dynamic (per statement) — eliminates ~14 redundant `add_substitution` calls and 2 `Path.resolve()` filesystem syscalls per statement.
|
|
33
|
+
- Performance: `$RANDOM` and `$UUID` are now lazy — computed only when actually referenced in a statement, not generated unconditionally for every statement.
|
|
34
|
+
- Performance: `LineDelimiter.delimited()` caches `quote_all_text` at construction time instead of reading `_state.conf` via module proxy on every row during export.
|
|
35
|
+
- Performance: CSV/TSV import uses Python's `csv` module as a fast path for standard delimited formats (comma, tab, semicolon, pipe) with doubled-quote escaping. Falls back to the character-at-a-time parser for non-standard formats (space-delimiter collapsing, escape characters).
|
|
36
|
+
|
|
37
|
+
______________________________________________________________________
|
|
38
|
+
|
|
16
39
|
## [2.12.2] - 2026-04-02
|
|
17
40
|
|
|
18
41
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: execsql2
|
|
3
|
-
Version: 2.12.
|
|
3
|
+
Version: 2.12.5
|
|
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,6 +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.17.0; extra == 'all'
|
|
54
55
|
Requires-Dist: polars; extra == 'all'
|
|
55
56
|
Requires-Dist: psycopg2-binary; extra == 'all'
|
|
56
57
|
Requires-Dist: pymysql; extra == 'all'
|
|
@@ -107,6 +108,8 @@ Provides-Extra: oracle
|
|
|
107
108
|
Requires-Dist: oracledb; extra == 'oracle'
|
|
108
109
|
Provides-Extra: postgres
|
|
109
110
|
Requires-Dist: psycopg2-binary; extra == 'postgres'
|
|
111
|
+
Provides-Extra: upsert
|
|
112
|
+
Requires-Dist: pg-upsert>=1.17.0; extra == 'upsert'
|
|
110
113
|
Description-Content-Type: text/markdown
|
|
111
114
|
|
|
112
115
|
> [!NOTE]
|
|
@@ -41,12 +41,13 @@ ______________________________________________________________________
|
|
|
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.
|
|
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. |
|
|
50
51
|
|
|
51
52
|
### Conditional Tests
|
|
52
53
|
|
|
@@ -163,6 +164,12 @@ All 33 mutable runtime globals in `state.py` have been consolidated into a `Runt
|
|
|
163
164
|
|
|
164
165
|
- **Cycle detection** — `substitute_vars()` raises an error after 100 iterations to prevent infinite loops when variables reference each other cyclically. Upstream had no protection.
|
|
165
166
|
- **O(1) substitution** — Variable substitution uses a single combined regex and dict lookup instead of O(V) per-variable regex passes. Behavior is identical; performance is improved.
|
|
167
|
+
- **Lazy `$RANDOM`/`$UUID`** — These system variables are now computed on first access rather than generated unconditionally for every statement. Behavior is identical when referenced; scripts that never reference them skip the computation entirely.
|
|
168
|
+
- **Static/dynamic system var split** — System substitution variables are split into static (set once per script and refreshed on CONNECT/CHDIR) and dynamic (refreshed per statement). Eliminates redundant `Path.resolve()` syscalls and database pool lookups per statement.
|
|
169
|
+
|
|
170
|
+
### CSV Import
|
|
171
|
+
|
|
172
|
+
- **Fast-path CSV reader** — Standard delimited imports (comma, tab, semicolon, pipe with doubled-quote escaping) now use Python's `csv` module. Non-standard formats (space-delimiter collapsing, escape characters) fall back to the original character-at-a-time parser.
|
|
166
173
|
|
|
167
174
|
### Database Adapters
|
|
168
175
|
|
|
@@ -25,6 +25,7 @@ pip install "execsql2[duckdb]" # DuckDB
|
|
|
25
25
|
pip install "execsql2[mssql]" # MS SQL Server / ODBC
|
|
26
26
|
pip install "execsql2[formats]" # ODS, Excel, Jinja2, Feather, Parquet, HDF5
|
|
27
27
|
pip install "execsql2[auth]" # OS keyring integration
|
|
28
|
+
pip install "execsql2[upsert]" # PG_UPSERT metacommand (pg-upsert)
|
|
28
29
|
pip install "execsql2[all-db]" # All database drivers
|
|
29
30
|
pip install "execsql2[all]" # Everything
|
|
30
31
|
```
|
|
@@ -2066,6 +2066,122 @@ If the "HALT" action is taken, either as a result of user input or as a result o
|
|
|
2066
2066
|
Double quotes (as shown above), apostrophes, or square brackets can be used to delimit the text.
|
|
2067
2067
|
|
|
2068
2068
|
|
|
2069
|
+
## PG_UPSERT { #pg_upsert }
|
|
2070
|
+
|
|
2071
|
+
```
|
|
2072
|
+
PG_UPSERT FROM <staging_schema> TO <base_schema> TABLES <table1>, <table2> [options]
|
|
2073
|
+
PG_UPSERT QA FROM <staging_schema> TO <base_schema> TABLES <table1>, <table2> [options]
|
|
2074
|
+
PG_UPSERT CHECK FROM <staging_schema> TO <base_schema> TABLES <table1>, <table2>
|
|
2075
|
+
```
|
|
2076
|
+
|
|
2077
|
+
Performs QA-checked, FK-dependency-ordered upserts from a staging schema to a base schema on PostgreSQL. Integrates [pg-upsert](https://pg-upsert.readthedocs.io/) as an optional dependency.
|
|
2078
|
+
|
|
2079
|
+
**Requires:** `pip install execsql2[upsert]`
|
|
2080
|
+
|
|
2081
|
+
**Requires:** A PostgreSQL connection. Raises an error if the current DBMS is not PostgreSQL.
|
|
2082
|
+
|
|
2083
|
+
### Modes
|
|
2084
|
+
|
|
2085
|
+
- **Full pipeline** (`PG_UPSERT FROM ... TO ... TABLES ...`): Runs all QA checks, then upserts data (update + insert by default), with optional commit.
|
|
2086
|
+
- **QA only** (`PG_UPSERT QA FROM ... TO ... TABLES ...`): Runs all QA checks without upserting. Never commits.
|
|
2087
|
+
- **Schema check** (`PG_UPSERT CHECK FROM ... TO ... TABLES ...`): Checks column existence and type compatibility only. Never commits.
|
|
2088
|
+
|
|
2089
|
+
### Optional keywords
|
|
2090
|
+
|
|
2091
|
+
Keywords can appear in any order after the table list.
|
|
2092
|
+
|
|
2093
|
+
| Keyword | Description |
|
|
2094
|
+
|---------|-------------|
|
|
2095
|
+
| `METHOD upsert\|update\|insert` | Upsert strategy. `upsert` (default) updates existing + inserts new rows. `update` only updates. `insert` only inserts. Full mode only. |
|
|
2096
|
+
| `COMMIT` | Commit changes if QA passes. Without it, changes are rolled back (dry-run). Full mode only. |
|
|
2097
|
+
| `EXCLUDE col1, col2` | Columns to skip during upsert. |
|
|
2098
|
+
| `EXCLUDE_NULL col1, col2` | Columns to skip in null QA checks. |
|
|
2099
|
+
| `INTERACTIVE` | Enable pg-upsert's interactive UI dialogs (tkinter or textual) for reviewing QA failures. Without it, runs non-interactively. |
|
|
2100
|
+
| `COMPACT` | Use compact grid format for QA summary instead of detailed per-table panels. |
|
|
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
|
+
|
|
2103
|
+
### Substitution variables
|
|
2104
|
+
|
|
2105
|
+
Set after every `PG_UPSERT` execution:
|
|
2106
|
+
|
|
2107
|
+
| Variable | Type | Description |
|
|
2108
|
+
|----------|------|-------------|
|
|
2109
|
+
| `$PG_UPSERT_QA_PASSED` | TRUE/FALSE | Did all QA checks pass? |
|
|
2110
|
+
| `$PG_UPSERT_ROWS_UPDATED` | integer | Total rows updated across all tables |
|
|
2111
|
+
| `$PG_UPSERT_ROWS_INSERTED` | integer | Total rows inserted across all tables |
|
|
2112
|
+
| `$PG_UPSERT_COMMITTED` | TRUE/FALSE | Were changes committed? |
|
|
2113
|
+
| `$PG_UPSERT_STAGING_SCHEMA` | string | Staging schema name used |
|
|
2114
|
+
| `$PG_UPSERT_BASE_SCHEMA` | string | Base schema name used |
|
|
2115
|
+
| `$PG_UPSERT_TABLES` | string | Comma-separated list of table names processed |
|
|
2116
|
+
| `$PG_UPSERT_METHOD` | string | Upsert method used |
|
|
2117
|
+
| `$PG_UPSERT_DURATION` | float | Elapsed time in seconds |
|
|
2118
|
+
| `$PG_UPSERT_STARTED_AT` | string | ISO 8601 start timestamp |
|
|
2119
|
+
| `$PG_UPSERT_FINISHED_AT` | string | ISO 8601 end timestamp |
|
|
2120
|
+
| `$PG_UPSERT_RESULT_JSON` | JSON string | Full result for detailed inspection |
|
|
2121
|
+
|
|
2122
|
+
!!! note "Using `$PG_UPSERT_RESULT_JSON` with WRITE"
|
|
2123
|
+
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:
|
|
2124
|
+
|
|
2125
|
+
```sql
|
|
2126
|
+
-- !x! WRITE ~!!$PG_UPSERT_RESULT_JSON!!~
|
|
2127
|
+
-- !x! WRITE `!!$PG_UPSERT_RESULT_JSON!!`
|
|
2128
|
+
```
|
|
2129
|
+
|
|
2130
|
+
### Temporary objects
|
|
2131
|
+
|
|
2132
|
+
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.
|
|
2133
|
+
|
|
2134
|
+
For the full list of temporary objects and their schemas, see the [pg-upsert Temporary Objects Reference](https://pg-upsert.readthedocs.io/en/latest/architecture/#temporary-objects-reference).
|
|
2135
|
+
|
|
2136
|
+
### Examples
|
|
2137
|
+
|
|
2138
|
+
```sql
|
|
2139
|
+
-- Full pipeline: QA + upsert + commit
|
|
2140
|
+
-- !x! PG_UPSERT FROM staging TO public TABLES books, authors COMMIT
|
|
2141
|
+
|
|
2142
|
+
-- Dry run (no commit) with update-only method
|
|
2143
|
+
-- !x! PG_UPSERT FROM staging TO public TABLES books METHOD update
|
|
2144
|
+
|
|
2145
|
+
-- QA only with interactive review
|
|
2146
|
+
-- !x! PG_UPSERT QA FROM staging TO public TABLES books, authors INTERACTIVE
|
|
2147
|
+
|
|
2148
|
+
-- Schema check only
|
|
2149
|
+
-- !x! PG_UPSERT CHECK FROM staging TO public TABLES books, authors
|
|
2150
|
+
|
|
2151
|
+
-- Log pg-upsert output to a file
|
|
2152
|
+
-- !x! PG_UPSERT FROM staging TO public TABLES books, authors LOGFILE "upsert.log" COMMIT
|
|
2153
|
+
|
|
2154
|
+
-- Use substitution variables after upsert
|
|
2155
|
+
-- !x! PG_UPSERT FROM staging TO public TABLES books COMMIT
|
|
2156
|
+
-- !x! WRITE "Updated !!$PG_UPSERT_ROWS_UPDATED!! rows, inserted !!$PG_UPSERT_ROWS_INSERTED!! in !!$PG_UPSERT_DURATION!! seconds"
|
|
2157
|
+
|
|
2158
|
+
-- Inspect pg-upsert's control table after a run
|
|
2159
|
+
-- !x! PG_UPSERT FROM staging TO public TABLES books, authors, genres COMMIT
|
|
2160
|
+
-- !x! EXPORT ups_control TO stdout AS txt
|
|
2161
|
+
|
|
2162
|
+
-- Export QA results to a CSV file for review
|
|
2163
|
+
-- !x! PG_UPSERT QA FROM staging TO public TABLES books, authors
|
|
2164
|
+
-- !x! EXPORT ups_control TO "qa_results.csv" AS csv
|
|
2165
|
+
|
|
2166
|
+
-- Check for type mismatches between schemas and display them
|
|
2167
|
+
-- !x! PG_UPSERT CHECK FROM staging TO public TABLES books, authors
|
|
2168
|
+
-- !x! EXPORT ups_type_mismatches TO stdout AS txt
|
|
2169
|
+
|
|
2170
|
+
-- Conditional logic based on QA results
|
|
2171
|
+
-- !x! PG_UPSERT FROM staging TO public TABLES books, authors
|
|
2172
|
+
-- !x! IF !!$PG_UPSERT_QA_PASSED!! = TRUE
|
|
2173
|
+
-- !x! WRITE "All QA checks passed — proceeding with commit"
|
|
2174
|
+
-- !x! ELSE
|
|
2175
|
+
-- !x! WRITE "QA failed — inspect ups_control for details"
|
|
2176
|
+
-- !x! EXPORT ups_control TO stdout AS txt
|
|
2177
|
+
-- !x! ENDIF
|
|
2178
|
+
|
|
2179
|
+
-- Write the full JSON result using tilde delimiters (JSON contains " and [])
|
|
2180
|
+
-- !x! PG_UPSERT FROM staging TO public TABLES books COMMIT
|
|
2181
|
+
-- !x! WRITE ~!!$PG_UPSERT_RESULT_JSON!!~
|
|
2182
|
+
```
|
|
2183
|
+
|
|
2184
|
+
|
|
2069
2185
|
## PG_VACUUM
|
|
2070
2186
|
|
|
2071
2187
|
```
|
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
},
|
|
94
94
|
"action-keywords": {
|
|
95
95
|
"comment": "sub, write, execute script, export, etc.",
|
|
96
|
-
"match": "(?i)\\b(reset\\s+dialog_canceled|write\\s+create_table|export_metadata|sub_querystring|execute\\s+script|append\\s+script|extend\\s+script|reset\\s+counter|export\\s+query|sub_tempfile|write\\s+script|import_file|set\\s+counter|sub_decrypt|sub_encrypt|autocommit|breakpoint|copy\\s+query|disconnect|select_sub|sub_append|system_cmd|pg_vacuum|sub_empty|sub_local|connect|include|max_int|rm_file|subdata|sub_add|sub_ini|assert|export|import|rm_sub|debug|email|serve|write|copy|log|run|sub|use|zip|cd)\\b",
|
|
96
|
+
"match": "(?i)\\b(reset\\s+dialog_canceled|write\\s+create_table|export_metadata|pg_upsert\\s+check|sub_querystring|execute\\s+script|append\\s+script|extend\\s+script|reset\\s+counter|export\\s+query|pg_upsert\\s+qa|sub_tempfile|write\\s+script|import_file|set\\s+counter|sub_decrypt|sub_encrypt|autocommit|breakpoint|copy\\s+query|disconnect|select_sub|sub_append|system_cmd|pg_upsert|pg_vacuum|sub_empty|sub_local|connect|include|max_int|rm_file|subdata|sub_add|sub_ini|assert|export|import|rm_sub|debug|email|serve|write|copy|log|run|sub|use|zip|cd)\\b",
|
|
97
97
|
"name": "keyword.other.execsql"
|
|
98
98
|
},
|
|
99
99
|
"config-event-keywords": {
|
|
@@ -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.5"
|
|
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,6 +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.17.0"]
|
|
61
62
|
# Convenience groups
|
|
62
63
|
all-db = [
|
|
63
64
|
"psycopg2-binary",
|
|
@@ -71,6 +72,7 @@ all = [
|
|
|
71
72
|
"execsql2[all-db]",
|
|
72
73
|
"execsql2[formats]",
|
|
73
74
|
"execsql2[auth]",
|
|
75
|
+
"execsql2[upsert]",
|
|
74
76
|
]
|
|
75
77
|
dev = [
|
|
76
78
|
"build>=1.2.2.post1",
|
|
@@ -159,7 +161,7 @@ skip-magic-trailing-comma = false
|
|
|
159
161
|
line-ending = "auto"
|
|
160
162
|
|
|
161
163
|
[tool.bumpversion]
|
|
162
|
-
current_version = "2.12.
|
|
164
|
+
current_version = "2.12.5"
|
|
163
165
|
commit = true
|
|
164
166
|
commit_args = "--no-verify"
|
|
165
167
|
tag = true
|
|
@@ -686,6 +686,13 @@ class DatabasePool:
|
|
|
686
686
|
)
|
|
687
687
|
self.pool[db_alias].close()
|
|
688
688
|
self.pool[db_alias] = db_obj
|
|
689
|
+
# Refresh static system vars so $DB_NAME, $DB_USER, etc. reflect the new connection.
|
|
690
|
+
try:
|
|
691
|
+
from execsql.script.engine import set_static_system_vars
|
|
692
|
+
|
|
693
|
+
set_static_system_vars()
|
|
694
|
+
except Exception:
|
|
695
|
+
pass # Engine not yet initialized (early startup).
|
|
689
696
|
|
|
690
697
|
def aliases(self) -> list[str]:
|
|
691
698
|
"""Return a list of all currently registered database aliases."""
|
|
@@ -40,6 +40,7 @@ class LineDelimiter:
|
|
|
40
40
|
self.delimiter = delim
|
|
41
41
|
self.joinchar = delim if delim else ""
|
|
42
42
|
self.quotechar = quote
|
|
43
|
+
self.quote_all_text = _state.conf.quote_all_text if _state.conf else False
|
|
43
44
|
if quote:
|
|
44
45
|
if escchar:
|
|
45
46
|
self.quotedquote = escchar + quote
|
|
@@ -50,13 +51,12 @@ class LineDelimiter:
|
|
|
50
51
|
|
|
51
52
|
def delimited(self, datarow: Any, add_newline: bool = True) -> str:
|
|
52
53
|
"""Format a sequence of values as a single delimited text line."""
|
|
53
|
-
conf = _state.conf
|
|
54
54
|
if self.quotechar:
|
|
55
55
|
d_row = []
|
|
56
56
|
for e in datarow:
|
|
57
57
|
if isinstance(e, str):
|
|
58
58
|
if (
|
|
59
|
-
|
|
59
|
+
self.quote_all_text
|
|
60
60
|
or (self.quotechar in e)
|
|
61
61
|
or (self.delimiter is not None and self.delimiter in e)
|
|
62
62
|
or ("\n" in e)
|
|
@@ -609,10 +609,52 @@ class CsvFile(EncodedFile):
|
|
|
609
609
|
raise ErrInfo("error", other_msg=", ".join(self.parse_errors))
|
|
610
610
|
return elements
|
|
611
611
|
|
|
612
|
+
def _can_use_fast_csv_reader(self) -> bool:
|
|
613
|
+
"""Return True if the detected format is compatible with Python's csv module."""
|
|
614
|
+
# The csv module handles comma/tab delimiters with doubled-quote escaping.
|
|
615
|
+
# It cannot handle: space-delimiter collapsing, escape chars, or no delimiter.
|
|
616
|
+
if self.delimiter is None or self.delimiter == " ":
|
|
617
|
+
return False
|
|
618
|
+
return self.escapechar is None
|
|
619
|
+
|
|
612
620
|
def reader(self) -> Any:
|
|
613
621
|
"""Yield parsed rows from the file as lists of field values."""
|
|
614
622
|
conf = _state.conf
|
|
615
623
|
self.evaluate_line_format()
|
|
624
|
+
if self._can_use_fast_csv_reader():
|
|
625
|
+
yield from self._fast_reader(conf)
|
|
626
|
+
else:
|
|
627
|
+
yield from self._slow_reader(conf)
|
|
628
|
+
|
|
629
|
+
def _fast_reader(self, conf: Any) -> Any:
|
|
630
|
+
"""Read using Python's csv module (fast path for standard delimited formats)."""
|
|
631
|
+
import csv
|
|
632
|
+
|
|
633
|
+
f = self.openclean("rt")
|
|
634
|
+
try:
|
|
635
|
+
csv_reader = csv.reader(
|
|
636
|
+
f,
|
|
637
|
+
delimiter=self.delimiter,
|
|
638
|
+
quotechar=self.quotechar,
|
|
639
|
+
doublequote=True,
|
|
640
|
+
strict=False,
|
|
641
|
+
)
|
|
642
|
+
for elements in csv_reader:
|
|
643
|
+
if len(elements) == 0:
|
|
644
|
+
break
|
|
645
|
+
# Normalize empty strings to None for parity with the slow reader.
|
|
646
|
+
elements = [e if e != "" else None for e in elements]
|
|
647
|
+
if conf.del_empty_cols and len(self.blank_cols) > 0:
|
|
648
|
+
blanks = copy.copy(self.blank_cols)
|
|
649
|
+
while len(blanks) > 0:
|
|
650
|
+
b = blanks.pop()
|
|
651
|
+
del elements[b]
|
|
652
|
+
yield elements
|
|
653
|
+
finally:
|
|
654
|
+
f.close()
|
|
655
|
+
|
|
656
|
+
def _slow_reader(self, conf: Any) -> Any:
|
|
657
|
+
"""Read using the character-at-a-time state machine (fallback for non-standard formats)."""
|
|
616
658
|
f = self.openclean("rt")
|
|
617
659
|
line_no = 0
|
|
618
660
|
try:
|
|
@@ -168,6 +168,11 @@ from execsql.metacommands.script_ext import (
|
|
|
168
168
|
x_extendscript_sql,
|
|
169
169
|
x_executescript,
|
|
170
170
|
)
|
|
171
|
+
from execsql.metacommands.upsert import (
|
|
172
|
+
x_pg_upsert,
|
|
173
|
+
x_pg_upsert_check,
|
|
174
|
+
x_pg_upsert_qa,
|
|
175
|
+
)
|
|
171
176
|
from execsql.metacommands.system import (
|
|
172
177
|
x_system_cmd,
|
|
173
178
|
x_email,
|
|
@@ -401,6 +406,10 @@ __all__ = [
|
|
|
401
406
|
"x_write_warnings",
|
|
402
407
|
"x_gui_level",
|
|
403
408
|
"x_execute",
|
|
409
|
+
# upsert handlers
|
|
410
|
+
"x_pg_upsert",
|
|
411
|
+
"x_pg_upsert_check",
|
|
412
|
+
"x_pg_upsert_qa",
|
|
404
413
|
# regex helpers
|
|
405
414
|
"ins_rxs",
|
|
406
415
|
"ins_quoted_rx",
|
|
@@ -168,6 +168,11 @@ from execsql.metacommands.script_ext import (
|
|
|
168
168
|
x_extendscript_metacommand,
|
|
169
169
|
x_extendscript_sql,
|
|
170
170
|
)
|
|
171
|
+
from execsql.metacommands.upsert import (
|
|
172
|
+
x_pg_upsert,
|
|
173
|
+
x_pg_upsert_check,
|
|
174
|
+
x_pg_upsert_qa,
|
|
175
|
+
)
|
|
171
176
|
from execsql.metacommands.system import (
|
|
172
177
|
x_cancel_halt,
|
|
173
178
|
x_cancel_halt_email,
|
|
@@ -2142,6 +2147,31 @@ def build_dispatch_table() -> MetaCommandList:
|
|
|
2142
2147
|
x_write,
|
|
2143
2148
|
)
|
|
2144
2149
|
|
|
2150
|
+
# ------------------------------------------------------------------
|
|
2151
|
+
# PG_UPSERT — pg-upsert integration (optional dependency)
|
|
2152
|
+
# ------------------------------------------------------------------
|
|
2153
|
+
# Order matters: CHECK and QA patterns must precede the general pattern
|
|
2154
|
+
# so that "PG_UPSERT CHECK ..." and "PG_UPSERT QA ..." are matched
|
|
2155
|
+
# before "PG_UPSERT FROM ...".
|
|
2156
|
+
mcl.add(
|
|
2157
|
+
r"^\s*PG_UPSERT\s+CHECK\s+FROM\s+(?P<staging_schema>\S+)\s+TO\s+(?P<base_schema>\S+)\s+TABLES\s+(?P<tail>.+)$",
|
|
2158
|
+
x_pg_upsert_check,
|
|
2159
|
+
description="PG_UPSERT CHECK",
|
|
2160
|
+
category="action",
|
|
2161
|
+
)
|
|
2162
|
+
mcl.add(
|
|
2163
|
+
r"^\s*PG_UPSERT\s+QA\s+FROM\s+(?P<staging_schema>\S+)\s+TO\s+(?P<base_schema>\S+)\s+TABLES\s+(?P<tail>.+)$",
|
|
2164
|
+
x_pg_upsert_qa,
|
|
2165
|
+
description="PG_UPSERT QA",
|
|
2166
|
+
category="action",
|
|
2167
|
+
)
|
|
2168
|
+
mcl.add(
|
|
2169
|
+
r"^\s*PG_UPSERT\s+FROM\s+(?P<staging_schema>\S+)\s+TO\s+(?P<base_schema>\S+)\s+TABLES\s+(?P<tail>.+)$",
|
|
2170
|
+
x_pg_upsert,
|
|
2171
|
+
description="PG_UPSERT",
|
|
2172
|
+
category="action",
|
|
2173
|
+
)
|
|
2174
|
+
|
|
2145
2175
|
# ------------------------------------------------------------------
|
|
2146
2176
|
# SUB (top-level variable assignment — kept near end so more specific
|
|
2147
2177
|
# SUB_* patterns above take precedence)
|
|
@@ -241,6 +241,10 @@ def x_cd(**kwargs: Any) -> None:
|
|
|
241
241
|
os.chdir(new_dir)
|
|
242
242
|
script, lno = current_script_line()
|
|
243
243
|
_state.exec_log.log_status_info(f"Current directory changed to {new_dir} at line {lno} of {script}")
|
|
244
|
+
if _state.subvars is not None:
|
|
245
|
+
from execsql.script.engine import set_static_system_vars
|
|
246
|
+
|
|
247
|
+
set_static_system_vars()
|
|
244
248
|
return None
|
|
245
249
|
|
|
246
250
|
|