execsql2 2.13.2__tar.gz → 2.15.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {execsql2-2.13.2 → execsql2-2.15.0}/.claude/project_context.md +18 -2
- {execsql2-2.13.2 → execsql2-2.15.0}/CHANGELOG.md +60 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/PKG-INFO +3 -3
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/about/divergence.md +21 -16
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/reference/metacommands.md +38 -10
- {execsql2-2.13.2 → execsql2-2.15.0}/pyproject.toml +3 -3
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/gui/base.py +52 -1
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/gui/console.py +86 -9
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/gui/desktop.py +261 -39
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/gui/tui.py +325 -51
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/metacommands/connect.py +5 -1
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/metacommands/dispatch.py +49 -6
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/metacommands/io_export.py +2 -2
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/metacommands/prompt.py +6 -11
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/metacommands/upsert.py +125 -17
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/utils/gui.py +2 -2
- execsql2-2.15.0/tests/exporters/test_html_extended.py +295 -0
- execsql2-2.15.0/tests/exporters/test_json_extended.py +168 -0
- execsql2-2.15.0/tests/exporters/test_latex_extended.py +173 -0
- execsql2-2.15.0/tests/exporters/test_pretty_extended.py +100 -0
- execsql2-2.15.0/tests/exporters/test_raw_extended.py +71 -0
- execsql2-2.15.0/tests/exporters/test_templates_extended.py +181 -0
- execsql2-2.15.0/tests/exporters/test_values_extended.py +91 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/gui/test_backends.py +78 -12
- execsql2-2.15.0/tests/gui/test_compare_stats.py +396 -0
- execsql2-2.15.0/tests/importers/test_base_extended.py +231 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/metacommands/test_pg_upsert.py +486 -0
- execsql2-2.15.0/tests/test_config_extended.py +981 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/uv.lock +5 -5
- {execsql2-2.13.2 → execsql2-2.15.0}/.claude/agents/dba.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/.claude/agents/herald.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/.claude/agents/inspector.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/.claude/agents/liaison.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/.claude/agents/oracle.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/.claude/agents/patcher.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/.claude/agents/qa.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/.claude/agents/scribe.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/.claude/commands/code-oracle.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/.claude/commands/migrate.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/.claude/commands/review-changes.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/.claude/commands/test-module.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/.claude/commands/update-changelog.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/.claude/commands/where-is.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/.claude/state/status.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/.github/workflows/ci-cd.yml +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/.gitignore +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/.pre-commit-config.yaml +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/.pre-commit-hooks.yaml +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/.python-version +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/.readthedocs.yaml +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/CLAUDE.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/CONTRIBUTING.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/LICENSE.txt +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/NOTICE +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/README.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/SECURITY.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/about/contributors.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/about/copyright.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/api/cli.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/api/db.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/api/exporters.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/api/importers.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/api/index.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/api/metacommands.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/dev/adding_db_adapters.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/dev/adding_exporters.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/dev/adding_importers.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/dev/adding_metacommands.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/dev/architecture.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/getting-started/installation.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/getting-started/requirements.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/getting-started/syntax.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/guides/debugging.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/guides/documentation.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/guides/encoding.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/guides/examples.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/guides/formatter.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/guides/logging.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/guides/sql_syntax.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/guides/usage.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/guides/using_scripts.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/images/Compare_planets.png +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/images/actions.png +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/images/actions2.png +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/images/checkboxes.png +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/images/connect.b64 +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/images/connect.png +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/images/create_conf.png +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/images/data_error1_screenshot.jpg +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/images/entry_form.png +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/images/execsql_console.png +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/images/execsql_logo_01.png +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/images/fatals.png +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/images/logo_small.png +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/images/pause_terminal.png +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/images/pause_terminal_sm.b64 +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/images/pause_terminal_sm.png +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/images/prompt_compare.png +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/images/set_build_commands.jpg +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/images/unit_conversions.b64 +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/images/unit_conversions_029.png +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/images/unmatched.png +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/images/vim_execsql_highlight.png +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/index.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/reference/configuration.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/reference/security.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/docs/reference/substitution_vars.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/extras/vscode-execsql/README.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/extras/vscode-execsql/package.json +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/extras/vscode-execsql/syntaxes/execsql.tmLanguage.json +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/justfile +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/scripts/generate_vscode_grammar.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/__init__.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/__main__.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/cli/__init__.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/cli/dsn.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/cli/help.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/cli/lint.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/cli/run.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/config.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/constants.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/db/__init__.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/db/access.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/db/base.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/db/dsn.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/db/duckdb.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/db/factory.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/db/firebird.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/db/mysql.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/db/oracle.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/db/postgres.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/db/sqlite.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/db/sqlserver.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/debug/__init__.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/debug/repl.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/exceptions.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/exporters/__init__.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/exporters/base.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/exporters/delimited.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/exporters/duckdb.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/exporters/feather.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/exporters/html.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/exporters/json.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/exporters/latex.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/exporters/markdown.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/exporters/ods.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/exporters/parquet.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/exporters/pretty.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/exporters/protocol.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/exporters/raw.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/exporters/sqlite.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/exporters/templates.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/exporters/values.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/exporters/xls.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/exporters/xlsx.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/exporters/xml.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/exporters/yaml.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/exporters/zip.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/format.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/gui/__init__.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/importers/__init__.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/importers/base.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/importers/csv.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/importers/feather.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/importers/json.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/importers/ods.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/importers/xls.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/metacommands/__init__.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/metacommands/conditions.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/metacommands/control.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/metacommands/data.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/metacommands/debug.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/metacommands/io.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/metacommands/io_fileops.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/metacommands/io_import.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/metacommands/io_write.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/metacommands/script_ext.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/metacommands/system.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/models.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/parser.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/py.typed +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/script/__init__.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/script/control.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/script/engine.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/script/variables.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/state.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/types.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/utils/__init__.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/utils/auth.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/utils/crypto.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/utils/datetime.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/utils/errors.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/utils/fileio.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/utils/mail.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/utils/numeric.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/utils/regex.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/utils/strings.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/src/execsql/utils/timer.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/templates/README.md +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/templates/config_settings.sqlite +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/templates/example_config_prompt.sql +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/templates/execsql.conf +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/templates/make_config_db.sql +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/templates/md_compare.sql +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/templates/md_glossary.sql +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/templates/md_upsert.sql +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/templates/pg_compare.sql +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/templates/pg_glossary.sql +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/templates/pg_upsert.sql +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/templates/script_template.sql +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/templates/ss_compare.sql +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/templates/ss_glossary.sql +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/templates/ss_upsert.sql +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/__init__.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/cli/__init__.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/cli/test_cli.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/cli/test_cli_e2e.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/cli/test_cli_run.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/cli/test_lint.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/cli/test_ping.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/cli/test_profile.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/conftest.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/db/__init__.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/db/test_base.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/db/test_duckdb.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/db/test_factory.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/db/test_postgres.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/db/test_sqlite.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/db/test_sqlite_extra.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/exporters/__init__.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/exporters/test_base.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/exporters/test_db.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/exporters/test_delimited.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/exporters/test_duckdb_exporter.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/exporters/test_exporters.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/exporters/test_feather.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/exporters/test_html_latex.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/exporters/test_json.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/exporters/test_markdown.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/exporters/test_ods.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/exporters/test_parquet.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/exporters/test_sqlite_exporter.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/exporters/test_templates.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/exporters/test_xls_xlsx.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/exporters/test_xlsx.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/exporters/test_xml.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/exporters/test_yaml.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/exporters/test_zip.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/gui/__init__.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/importers/__init__.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/importers/test_csv_importer.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/importers/test_feather_importer.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/importers/test_json_importer.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/importers/test_ods_importer.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/importers/test_xls_importer.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/integration/__init__.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/integration/conftest.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/integration/test_dsn.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/integration/test_duckdb.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/integration/test_mysql.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/integration/test_postgres.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/integration/test_sqlite.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/metacommands/__init__.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/metacommands/test_assert.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/metacommands/test_breakpoint.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/metacommands/test_connect.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/metacommands/test_io_export.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/metacommands/test_io_import.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/metacommands/test_metacommands.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/metacommands/test_metacommands_connect.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/metacommands/test_metacommands_data.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/metacommands/test_metacommands_extended.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/metacommands/test_metacommands_fileops_extra.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/metacommands/test_metacommands_io.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/metacommands/test_metacommands_io_write_extra.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/metacommands/test_metacommands_script_ext.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/metacommands/test_metacommands_system.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/metacommands/test_metacommands_system_extra.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/metacommands/test_row_count.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/test_config.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/test_config_data.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/test_constants.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/test_engine.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/test_error_messages.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/test_exceptions.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/test_format.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/test_mail.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/test_models.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/test_package.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/test_parser.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/test_registry.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/test_script.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/test_state.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/test_types.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/utils/__init__.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/utils/test_auth.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/utils/test_auth_extra.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/utils/test_crypto.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/utils/test_datetime.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/utils/test_errors.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/utils/test_errors_extra.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/utils/test_fileio.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/utils/test_fileio_extra.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/utils/test_numeric.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/utils/test_regex.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/utils/test_strings.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/utils/test_timer.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/tests/utils/test_timer_extra.py +0 -0
- {execsql2-2.13.2 → execsql2-2.15.0}/zensical.toml +0 -0
|
@@ -153,7 +153,7 @@ ______________________________________________________________________
|
|
|
153
153
|
| `pre-commit` | Git hooks | gitleaks, uv-lock, ruff, mdformat, markdownlint, typos, validate-pyproject |
|
|
154
154
|
| `zensical` | Docs builder | MkDocs-compatible, Material theme, configured via `zensical.toml` |
|
|
155
155
|
| `mkdocstrings` | API docs from docstrings | Wired up — `docs/api/` pages auto-generate from source |
|
|
156
|
-
| `pytest-cov` | Coverage | `--cov-fail-under=
|
|
156
|
+
| `pytest-cov` | Coverage | `--cov-fail-under=90` enforced (raised from 80% in v2.12) |
|
|
157
157
|
|
|
158
158
|
## Package Layout Decision
|
|
159
159
|
|
|
@@ -192,7 +192,7 @@ Triggered on: push to `main`, any tag `v*.*.*`, pull requests.
|
|
|
192
192
|
|
|
193
193
|
## Versioning
|
|
194
194
|
|
|
195
|
-
`bump-my-version` manages versions. Current: `2.
|
|
195
|
+
`bump-my-version` manages versions. Current: `2.13.2`. Bump commands:
|
|
196
196
|
|
|
197
197
|
- `just bump-patch` → 2.11.0 → 2.11.1
|
|
198
198
|
- `just bump-minor` → 2.11.0 → 2.12.0
|
|
@@ -271,6 +271,16 @@ the foreseeable future.
|
|
|
271
271
|
| `BREAKPOINT` debug REPL with step mode | 2.10.0 | 2026-04 |
|
|
272
272
|
| Error messages restored (script location, command) | 2.11.0 | 2026-04 |
|
|
273
273
|
| 16-item codebase analysis fix sweep | 2.11.x | 2026-04 |
|
|
274
|
+
| pg-upsert integration (`PG_UPSERT` metacommand) | 2.12.0 | 2026-04 |
|
|
275
|
+
| Coverage floor raised to 90% (3,600+ tests) | 2.12.x | 2026-04 |
|
|
276
|
+
| `--debug` step-through mode | 2.12.x | 2026-04 |
|
|
277
|
+
| `--lint` two-pass variables, EXECUTE SCRIPT flow | 2.13.x | 2026-04 |
|
|
278
|
+
| GUI: row counts, help URL button, compare diffs | unreleased | 2026-04 |
|
|
279
|
+
| GUI: PROMPT ENTRY_FORM all entry_type values | unreleased | 2026-04 |
|
|
280
|
+
| GUI: PROMPT COMPARE AND/BESIDE orientation fix | unreleased | 2026-04 |
|
|
281
|
+
| GUI: highlight diffs toggle (Tkinter + Textual) | unreleased | 2026-04 |
|
|
282
|
+
| Metacommand audit: regex/doc/handler bug fixes | unreleased | 2026-04 |
|
|
283
|
+
| Removed FREE keyword from PROMPT DISPLAY | unreleased | 2026-04 |
|
|
274
284
|
|
|
275
285
|
______________________________________________________________________
|
|
276
286
|
|
|
@@ -298,6 +308,11 @@ ______________________________________________________________________
|
|
|
298
308
|
|
|
299
309
|
### Candidate Features (unscheduled — pick and assign to milestones)
|
|
300
310
|
|
|
311
|
+
#### GUI — Essential
|
|
312
|
+
|
|
313
|
+
- [ ] **Textual console (`CONSOLE ON`)** — The Textual TUI backend has a stub `console_on()` that sets a flag but provides no actual GUI console. Tkinter has a full console window (ConsoleWindow) with output area, status bar, and progress bar. Textual needs an equivalent: a persistent RichLog-based panel that receives WRITE output, shows status, and progress. **This is a gap — users on `--gui-framework textual` with `CONFIG GUI_LEVEL 3` or `CONSOLE ON` get no console.**
|
|
314
|
+
- [ ] **Form validation** — `EntrySpec` has `validation_regex` and `validation_key_regex` fields but no backend enforces them. Tkinter should validate on focus-loss (validation_regex) and per-keystroke (validation_key_regex). Textual should validate on submit. Console should validate before accepting.
|
|
315
|
+
|
|
301
316
|
#### Quick Wins
|
|
302
317
|
|
|
303
318
|
- [x] **`--ping`** — test database connectivity and exit with a status message. Useful for CI health checks and connection debugging. (shipped v2.8.x)
|
|
@@ -359,6 +374,7 @@ ______________________________________________________________________
|
|
|
359
374
|
### Ongoing / No-milestone
|
|
360
375
|
|
|
361
376
|
- Textual TUI polish
|
|
377
|
+
- Coverage target: raise from 90% → 93% (in progress)
|
|
362
378
|
|
|
363
379
|
______________________________________________________________________
|
|
364
380
|
|
|
@@ -13,6 +13,66 @@ ______________________________________________________________________
|
|
|
13
13
|
|
|
14
14
|
______________________________________________________________________
|
|
15
15
|
|
|
16
|
+
## [2.15.0] - 2026-04-09
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- `PG_UPSERT` metacommand: new `EXPORT_FAILURES <dir>`, `EXPORT_FORMAT csv|json|xlsx`, and `EXPORT_MAX_ROWS <n>` keywords that write a "fix sheet" of failing QA rows — one row per unique violating staging row with a consolidated `_issues` column — to CSV, JSON, or XLSX. Works in all three modes (full pipeline, QA-only, schema check) and runs even when QA fails. New `$PG_UPSERT_EXPORT_PATH` substitution variable holds the directory written. A user-visible message reporting the export directory and format is emitted to both the console and the execsql log after every export.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- `[upsert]` extra now requires `pg-upsert>=1.21.0` (up from `>=1.20.0`) for the fix-sheet export feature.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- `PROMPT MESSAGE ... CREDENTIALS <user_var> <pw_var>` no longer crashes in console-fallback mode with `TypeError: get_password() missing 2 required positional arguments: 'database_name' and 'user_name'`. The fallback now uses `getpass.getpass()` to read the password, matching the intent (keyring-aware `auth.get_password()` is for CONNECT, not for bare credential prompts).
|
|
29
|
+
|
|
30
|
+
______________________________________________________________________
|
|
31
|
+
|
|
32
|
+
## [2.14.1] - 2026-04-07
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
|
|
36
|
+
- Fix Windows CI: use `zf.namelist()[0]` instead of path for zip entry lookup.
|
|
37
|
+
|
|
38
|
+
______________________________________________________________________
|
|
39
|
+
|
|
40
|
+
## [2.14.0] - 2026-04-07
|
|
41
|
+
|
|
42
|
+
### Added
|
|
43
|
+
|
|
44
|
+
- Row count footer displayed below every table in GUI dialogs (Textual TUI, Tkinter desktop, and console fallback). Shows format like "3 rows" or "1 row" with comma-separated thousands for large counts.
|
|
45
|
+
- Help URL button in all GUI dialogs that support the `HELP` keyword. Clicking the button opens the URL in the system browser. Console fallback prints the URL.
|
|
46
|
+
- Diff summary line in compare dialogs showing matching, differing, and table-exclusive row counts (e.g., "3 matching | 1 differing | 2 only in Table 1").
|
|
47
|
+
- `PROMPT ENTRY_FORM` now enforces `validation_regex` (on submit) and `validation_key_regex` (per-keystroke) validation across all GUI backends. Required fields are also validated on submit. Tkinter shows a messagebox on validation failure; Textual shows a notification; Console re-prompts.
|
|
48
|
+
- "Highlight Diffs" toggle button in compare dialogs (Textual and Tkinter) that color-codes rows: green for matching, yellow for changed, red for rows only in one table.
|
|
49
|
+
|
|
50
|
+
### Fixed
|
|
51
|
+
|
|
52
|
+
- `CONFIG GUI_LEVEL` now accepts value `3` (open GUI console on start), matching the `gui_level` configuration file setting.
|
|
53
|
+
- `PROMPT COMPARE` now respects the `AND` vs `BESIDE` keyword: `AND` stacks tables vertically, `BESIDE` displays them side-by-side. Previously both orientations displayed side-by-side.
|
|
54
|
+
- `PROMPT ENTRY_FORM` now renders all documented `entry_type` values: `listbox` (multi-select list), `radiobuttons` (radio button group), `textarea` (multi-line text area), `inputfile` and `outputfile` (text field with file browser button). Previously only `checkbox` and `dropdown`/`select` were implemented; all others fell through to a plain text input.
|
|
55
|
+
- `PROMPT ENTER_SUB` HELP URL regex typo: quoted HELP URLs containing `+` characters now match correctly (was using `[^+]` instead of `[^"]`).
|
|
56
|
+
- PostgreSQL and DSN `CONNECT` handlers now unquote the PASSWORD parameter consistently with all other database handlers.
|
|
57
|
+
- SQL Server `CONNECT` handler now uses consistent keyword argument `user_name=` in all code paths.
|
|
58
|
+
|
|
59
|
+
### Changed
|
|
60
|
+
|
|
61
|
+
- Documentation: added `CONFIG LOG_SQL` and `CONFIG SHOW_PROGRESS` sections to metacommands reference (were implemented but undocumented).
|
|
62
|
+
- Documentation: DuckDB `CONNECT` syntax now shows the `NEW` keyword (was supported but undocumented).
|
|
63
|
+
- Documentation: `EXPORT QUERY` format list now explicitly mentions PARQUET, FEATHER, YAML, MARKDOWN support.
|
|
64
|
+
- Documentation: added alias notes for `EXEC SCRIPT` / `RUN SCRIPT` and `APPEND SCRIPT`.
|
|
65
|
+
- Documentation: `RM_SUB` now documents `~` prefix for deleting local variables.
|
|
66
|
+
- Documentation: fixed missing bracket in `WRITE CREATE_TABLE FROM EXCEL` syntax.
|
|
67
|
+
|
|
68
|
+
### Removed
|
|
69
|
+
|
|
70
|
+
- `FREE` keyword from `PROMPT DISPLAY` metacommand. The non-blocking display behavior was only implemented in the console backend; Textual and Tkinter backends ignored it.
|
|
71
|
+
- Tkinter dialog buttons are now right-aligned (matching the Textual TUI layout) instead of centered.
|
|
72
|
+
- Tkinter dialog message text is now left-aligned instead of center-justified.
|
|
73
|
+
|
|
74
|
+
______________________________________________________________________
|
|
75
|
+
|
|
16
76
|
## [2.13.2] - 2026-04-06
|
|
17
77
|
|
|
18
78
|
### Changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: execsql2
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.15.0
|
|
4
4
|
Summary: Runs a SQL script against a PostgreSQL, SQLite, MariaDB/MySQL, DuckDB, Firebird, MS-Access, MS-SQL-Server, or Oracle database, or an ODBC DSN. Provides metacommands to import and export data, copy data between databases, conditionally execute SQL and metacommands, and dynamically alter SQL and metacommands with substitution variables.
|
|
5
5
|
Project-URL: Homepage, https://execsql2.readthedocs.io
|
|
6
6
|
Project-URL: Repository, https://github.com/geocoug/execsql
|
|
@@ -51,7 +51,7 @@ Requires-Dist: keyring; extra == 'all'
|
|
|
51
51
|
Requires-Dist: odfpy; extra == 'all'
|
|
52
52
|
Requires-Dist: openpyxl; extra == 'all'
|
|
53
53
|
Requires-Dist: oracledb; extra == 'all'
|
|
54
|
-
Requires-Dist: pg-upsert>=1.
|
|
54
|
+
Requires-Dist: pg-upsert>=1.21.0; extra == 'all'
|
|
55
55
|
Requires-Dist: polars; extra == 'all'
|
|
56
56
|
Requires-Dist: psycopg2-binary; extra == 'all'
|
|
57
57
|
Requires-Dist: pymysql; extra == 'all'
|
|
@@ -109,7 +109,7 @@ Requires-Dist: oracledb; extra == 'oracle'
|
|
|
109
109
|
Provides-Extra: postgres
|
|
110
110
|
Requires-Dist: psycopg2-binary; extra == 'postgres'
|
|
111
111
|
Provides-Extra: upsert
|
|
112
|
-
Requires-Dist: pg-upsert>=1.
|
|
112
|
+
Requires-Dist: pg-upsert>=1.21.0; extra == 'upsert'
|
|
113
113
|
Description-Content-Type: text/markdown
|
|
114
114
|
|
|
115
115
|
> [!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. |
|
|
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`, and `EXPORT_MAX_ROWS` keywords to write a "fix sheet" of failing QA rows to CSV/JSON/XLSX (requires `pg-upsert>=1.21.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
|
|
|
@@ -79,10 +79,14 @@ New options in `execsql.conf`:
|
|
|
79
79
|
|
|
80
80
|
### GUI
|
|
81
81
|
|
|
82
|
-
| Feature
|
|
83
|
-
|
|
|
84
|
-
| Textual TUI backend
|
|
85
|
-
| Console fallback
|
|
82
|
+
| Feature | Description |
|
|
83
|
+
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
84
|
+
| Textual TUI backend | Full terminal-UI backend via the `textual` library. Provides all dialog types (password, pause, message, entry, compare, action, etc.) in the terminal. |
|
|
85
|
+
| Console fallback | Text-only backend that handles GUI calls in headless environments by printing to stdout. |
|
|
86
|
+
| Table row counts | All GUI backends (Tkinter, Textual, Console) display a row count footer below every table widget (e.g. "3 rows", "1 row"). |
|
|
87
|
+
| Help URL button | Dialogs that accept the `HELP` keyword display a clickable Help button that opens the URL in the system browser. |
|
|
88
|
+
| Compare diff summary | The compare dialog shows a one-line summary of matching, differing, and table-exclusive rows when key columns are specified. |
|
|
89
|
+
| Form validation | `PROMPT ENTRY_FORM` enforces `validation_regex` (on submit) and `validation_key_regex` (per-keystroke) across all backends. Required fields validated. |
|
|
86
90
|
|
|
87
91
|
### Authentication
|
|
88
92
|
|
|
@@ -235,7 +239,8 @@ ______________________________________________________________________
|
|
|
235
239
|
|
|
236
240
|
## Removed Features
|
|
237
241
|
|
|
238
|
-
| Feature
|
|
239
|
-
|
|
|
240
|
-
| Airspeed template processor
|
|
241
|
-
| Python 2 compatibility
|
|
242
|
+
| Feature | Reason |
|
|
243
|
+
| ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
244
|
+
| Airspeed template processor | The `airspeed` library (Velocity clone) is unmaintained since ~2018. Use `FORMAT jinja` instead. The `airspeed` value for `template_processor` in `execsql.conf` is no longer accepted. |
|
|
245
|
+
| Python 2 compatibility | All Python 2 constructs (`stringtypes`, `u""` literals, `optparse`, etc.) have been removed. execsql2 requires Python 3.10+. |
|
|
246
|
+
| `FREE` keyword on `PROMPT DISPLAY` | The non-blocking display behavior was only implemented in the console backend; the Textual and Tkinter GUI backends ignored it. Removed rather than partially supported. |
|
|
@@ -417,13 +417,14 @@ This setting is also applied to the conversion of spreadsheet names to table nam
|
|
|
417
417
|
CONFIG GUI_LEVEL <n>
|
|
418
418
|
```
|
|
419
419
|
|
|
420
|
-
The level of interaction with the user that should be carried out using GUI dialogs. The numeric value *n* must be 0, 1, or
|
|
420
|
+
The level of interaction with the user that should be carried out using GUI dialogs. The numeric value *n* must be 0, 1, 2, or 3. The meanings of these values are:
|
|
421
421
|
|
|
422
422
|
> - 0: Do not use any optional GUI dialogs.
|
|
423
423
|
> - 1: Use GUI dialogs for password prompts and for the [PAUSE](#pause) metacommand.
|
|
424
424
|
> - 2: Also use a GUI dialog if a message is included with the [HALT](#halt) metacommand, and prompt for the initial database to use if no database connection parameters are specified in a configuration file or on the command line.
|
|
425
|
+
> - 3: Additionally, open a GUI console when *execsql* starts.
|
|
425
426
|
|
|
426
|
-
This is equivalent to the `gui_level` [configuration setting](configuration.md#gui_level)
|
|
427
|
+
This is equivalent to the `gui_level` [configuration setting](configuration.md#gui_level).
|
|
427
428
|
|
|
428
429
|
|
|
429
430
|
<a id="config_hdf5"></a>
|
|
@@ -457,6 +458,13 @@ CONFIG LOG_DATAVARS YES|NO
|
|
|
457
458
|
Controls whether data variables that are created by the [SELECT_SUB](#select_sub), [PROMPT SELECT_SUB](#prompt_selsub) and [PROMPT ACTION](#prompt_action) metacommands are written to *execsql*'s [log file](../guides/logging.md#logging). By default, all data variable assignments are logged. The performance of scripts that make extensive use of these metacommands (e.g., [Example 27](../guides/examples.md#example27)) can be improved by changing this setting to 'No'.
|
|
458
459
|
|
|
459
460
|
|
|
461
|
+
```
|
|
462
|
+
CONFIG LOG_SQL YES|NO
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
Enable or disable SQL query audit logging at runtime. When enabled, each SQL statement executed against the database is written to *execsql*'s [log file](../guides/logging.md#logging) as a `sql` record containing the database name, source line number, and query text. This is equivalent to the `log_sql` [configuration setting](configuration.md#config_logging). The default value is "No".
|
|
466
|
+
|
|
467
|
+
|
|
460
468
|
```
|
|
461
469
|
CONFIG LOG_WRITE_MESSAGES YES|NO
|
|
462
470
|
```
|
|
@@ -506,6 +514,13 @@ CONFIG SCAN_LINES <n>
|
|
|
506
514
|
The number of lines of a data file to scan during [IMPORT](#import) to determine the quoting character and delimiter character used. This is equivalent to the "-s" command-line option and the `scan_lines` [configuration setting](configuration.md#scan_lines).
|
|
507
515
|
|
|
508
516
|
|
|
517
|
+
```
|
|
518
|
+
CONFIG SHOW_PROGRESS YES|NO
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
Enable or disable the Rich progress bar for [IMPORT](#import) operations at runtime. When enabled, a progress bar is displayed showing the number of rows imported. This is equivalent to the `show_progress` [configuration setting](configuration.md#config_input) and the `--progress` CLI option. The default value is "No".
|
|
522
|
+
|
|
523
|
+
|
|
509
524
|
```
|
|
510
525
|
CONFIG TRIM_COLUMN_HEADERS NONE|BOTH|LEFT|RIGHT
|
|
511
526
|
```
|
|
@@ -635,7 +650,7 @@ CONNECT USER TO MARIADB(SERVER=<server_name>, DB=<database_name>
|
|
|
635
650
|
For DuckDB:
|
|
636
651
|
|
|
637
652
|
```
|
|
638
|
-
CONNECT TO DUCKDB(FILE=<database_file>) AS <alias_name>
|
|
653
|
+
CONNECT TO DUCKDB(FILE=<database_file> [, NEW]) AS <alias_name>
|
|
639
654
|
```
|
|
640
655
|
|
|
641
656
|
For Firebird:
|
|
@@ -686,7 +701,7 @@ If the command form with the "USER" keyword is used, the user name that is used
|
|
|
686
701
|
|
|
687
702
|
The alias name that is specified in this command can be used to refer to this database in the [USE](#use) and [COPY](#copy) metacommands. Alias names can consist only of letters, digits, and underscores, and must start with a letter. The alias name "initial" is reserved for the database that is used when *execsql* starts script processing, and cannot be used with the [CONNECT](#connect) metacommand. If you re-use an alias name, the connection to the database to which that name was previously assigned will be closed, and the database will no longer be available. Using the same alias for two different databases allows for mistakes wherein script statements are run on the wrong database, and so is not recommended.
|
|
688
703
|
|
|
689
|
-
If the "NEW" keyword is used with PostgreSQL or
|
|
704
|
+
If the "NEW" keyword is used with PostgreSQL, SQLite, or DuckDB, a new database of the given name will be created. There must be no existing database of that name, and (for Postgres) you must have permissions assigned that allow you to create databases.
|
|
690
705
|
|
|
691
706
|
|
|
692
707
|
## CONSOLE
|
|
@@ -894,6 +909,8 @@ EXECUTE SCRIPT [IF EXISTS] <script_name>
|
|
|
894
909
|
```
|
|
895
910
|
|
|
896
911
|
|
|
912
|
+
`EXEC SCRIPT` and `RUN SCRIPT` are accepted as aliases for `EXECUTE SCRIPT`.
|
|
913
|
+
|
|
897
914
|
This metacommand will execute the set of SQL statements and metacommands that was previously defined and named using the [BEGIN/END SCRIPT](#beginscript) metacommands.
|
|
898
915
|
|
|
899
916
|
If the IF EXISTS clause is included, the script will be executed only if it has been defined.
|
|
@@ -1200,7 +1217,7 @@ EXPORT QUERY <<query>> [TEE] [APPEND] TO <filename>|stdout
|
|
|
1200
1217
|
|
|
1201
1218
|
Exports data in the same manner as the [EXPORT](#export) metacommand, except that the data source is a SQL query statement that is contained in the metacommand rather than a database table or view. The SQL query statement must be terminated with a semicolon and enclosed in double angle brackets (i.e., literally "`<<`" and "`>>`").
|
|
1202
1219
|
|
|
1203
|
-
The EXPORT QUERY metacommand does not support export to XML or
|
|
1220
|
+
The EXPORT QUERY metacommand does not support export to XML, HDF5, SQLITE, or DUCKDB because there is no table name specified. It does support all other formats including PARQUET, FEATHER, YAML, and MARKDOWN. Export to ODS supports only a single query, rather than a list as for the [EXPORT](#export) metacommand.
|
|
1204
1221
|
|
|
1205
1222
|
Like all metacommands, this metacommand must appear on a single line, although the SQL statement may be quite long. To facilitate readability, the SQL statement may be saved in a [substitution
|
|
1206
1223
|
variable](substitution_vars.md#substitution_vars) and that substitution variable referenced in the EXPORT QUERY metacommand.
|
|
@@ -1234,6 +1251,8 @@ EXTEND SCRIPT <script_1> WITH SCRIPT <script_2>
|
|
|
1234
1251
|
|
|
1235
1252
|
Merges two scripts, appending the lines of *script_2* to the end of *script_1*. Both scripts must have already been defined using the [BEGIN SCRIPT](#beginscript) metacommand. Parameters for *script_2* are also added to *script_1*.
|
|
1236
1253
|
|
|
1254
|
+
The alternative syntax `APPEND SCRIPT <script_2> TO <script_1>` is also accepted and performs the same operation.
|
|
1255
|
+
|
|
1237
1256
|
|
|
1238
1257
|
## HALT
|
|
1239
1258
|
|
|
@@ -2108,6 +2127,9 @@ Keywords can appear in any order after the table list.
|
|
|
2108
2127
|
| `COMPACT` | Use compact grid format for QA summary instead of detailed per-table panels. |
|
|
2109
2128
|
| `LOGFILE <path>` | Append pg-upsert's plain-text log output to the given file. Supports quoted paths for spaces: `LOGFILE "path/to/log.txt"`. |
|
|
2110
2129
|
| `CLEANUP` | Drop all `ups_*` temporary tables and views after execution. Without it, temp objects persist for inspection (default). |
|
|
2130
|
+
| `EXPORT_FAILURES <dir>` | Write a "fix sheet" of failing QA rows into `<dir>` (directory is created if missing). One row per unique violating staging row, with an `_issues` column summarizing every problem on that row. Supports quoted paths for spaces. Export runs even when QA fails — that is the whole point. A confirmation message (`PG_UPSERT: exported QA failures to <dir> (<format>)`) is written to the terminal, the execsql log, and the `LOGFILE` target if one is given. |
|
|
2131
|
+
| `EXPORT_FORMAT csv\|json\|xlsx` | Fix sheet format when `EXPORT_FAILURES` is given. `csv` (default) writes one file per table; `json` writes a single nested file; `xlsx` writes a single workbook with one sheet per table (requires `openpyxl`). |
|
|
2132
|
+
| `EXPORT_MAX_ROWS <n>` | Maximum rows to capture per check per table for the fix sheet. Default `1000`. Only meaningful with `EXPORT_FAILURES`. |
|
|
2111
2133
|
|
|
2112
2134
|
### Substitution variables
|
|
2113
2135
|
|
|
@@ -2131,6 +2153,7 @@ Set after every `PG_UPSERT` execution:
|
|
|
2131
2153
|
| `$PG_UPSERT_TABLE_QA_PASSED` | TRUE/FALSE | QA result for the current table (updated per table) |
|
|
2132
2154
|
| `$PG_UPSERT_TABLE_ROWS_UPDATED` | integer | Rows updated for the current table (updated per table) |
|
|
2133
2155
|
| `$PG_UPSERT_TABLE_ROWS_INSERTED` | integer | Rows inserted for the current table (updated per table) |
|
|
2156
|
+
| `$PG_UPSERT_EXPORT_PATH` | string | Directory path that the QA fix sheet was written to, or empty if `EXPORT_FAILURES` was not given or nothing was exported. |
|
|
2134
2157
|
|
|
2135
2158
|
!!! note "Using `$PG_UPSERT_RESULT_JSON` with WRITE"
|
|
2136
2159
|
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:
|
|
@@ -2192,6 +2215,13 @@ For the full list of temporary objects and their schemas, see the [pg-upsert Tem
|
|
|
2192
2215
|
-- Write the full JSON result using tilde delimiters (JSON contains " and [])
|
|
2193
2216
|
-- !x! PG_UPSERT FROM staging TO public TABLES books COMMIT
|
|
2194
2217
|
-- !x! WRITE ~!!$PG_UPSERT_RESULT_JSON!!~
|
|
2218
|
+
|
|
2219
|
+
-- Export failing QA rows to an Excel fix sheet (one sheet per table)
|
|
2220
|
+
-- !x! PG_UPSERT QA FROM staging TO public TABLES books, authors EXPORT_FAILURES "qa_failures/" EXPORT_FORMAT xlsx
|
|
2221
|
+
|
|
2222
|
+
-- Full pipeline with a CSV fix sheet cap of 50 rows per check per table
|
|
2223
|
+
-- !x! PG_UPSERT FROM staging TO public TABLES books, authors EXPORT_FAILURES /tmp/fix EXPORT_MAX_ROWS 50 COMMIT
|
|
2224
|
+
-- !x! WRITE "Fix sheet: !!$PG_UPSERT_EXPORT_PATH!!"
|
|
2195
2225
|
```
|
|
2196
2226
|
|
|
2197
2227
|
|
|
@@ -2367,7 +2397,7 @@ The selection is [logged](../guides/logging.md#logging).
|
|
|
2367
2397
|
|
|
2368
2398
|
```
|
|
2369
2399
|
PROMPT DISPLAY <table_or_view_name> MESSAGE "<text>"
|
|
2370
|
-
[HELP <url>]
|
|
2400
|
+
[HELP <url>]
|
|
2371
2401
|
```
|
|
2372
2402
|
|
|
2373
2403
|
Displays the selected view or table in a window with the specified message and both 'Continue' and 'Cancel' buttons. If the 'Continue' button is selected, the script will continue to run. If the 'Cancel' button is selected, the script will immediately halt unless [CANCEL_HALT](#cancel_halt) is set to OFF. The Enter key also carries out the action of the 'Continue' button, and the Escape key carries out the action of the 'Cancel' button.
|
|
@@ -2384,8 +2414,6 @@ If [CANCEL_HALT](#cancel_halt) is set to OFF, the [DIALOG_CANCELED](#dialog_canc
|
|
|
2384
2414
|
|
|
2385
2415
|
If a URL is provided with the HELP keyword, the dialog box will include a button that will open that URL when clicked. The URL must be double-quoted if it contains spaces.
|
|
2386
2416
|
|
|
2387
|
-
if the FREE keyword is used, the script will keep running when the data table is displayed. The display will have only a "Close" button. If the display is not closed manually, it will be closed automatically when *execsql* finishes processing the script.
|
|
2388
|
-
|
|
2389
2417
|
|
|
2390
2418
|
## PROMPT ENTER_SUB { #prompt_enter }
|
|
2391
2419
|
|
|
@@ -2627,7 +2655,7 @@ The file name provided may include wildcards to delete multiple files.
|
|
|
2627
2655
|
RM_SUB <match_string>
|
|
2628
2656
|
```
|
|
2629
2657
|
|
|
2630
|
-
Deletes the specified user-created substitution variable.
|
|
2658
|
+
Deletes the specified user-created substitution variable. If the match string is prefixed with `~`, the local variable with that name is deleted from the current script scope instead of the global substitution variable set.
|
|
2631
2659
|
|
|
2632
2660
|
|
|
2633
2661
|
## SELECT_SUB
|
|
@@ -2884,7 +2912,7 @@ For data in an Excel spreadsheet:
|
|
|
2884
2912
|
|
|
2885
2913
|
```
|
|
2886
2914
|
WRITE CREATE_TABLE <table_name> FROM EXCEL <file_name>
|
|
2887
|
-
SHEET <sheet_name> [SKIP <rows>] ENCODING <encoding>]
|
|
2915
|
+
SHEET <sheet_name> [SKIP <rows>] [ENCODING <encoding>]
|
|
2888
2916
|
[COMMENT "<comment_text>"] [TO <output>]
|
|
2889
2917
|
```
|
|
2890
2918
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "execsql2"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.15.0"
|
|
8
8
|
description = "Runs a SQL script against a PostgreSQL, SQLite, MariaDB/MySQL, DuckDB, Firebird, MS-Access, MS-SQL-Server, or Oracle database, or an ODBC DSN. Provides metacommands to import and export data, copy data between databases, conditionally execute SQL and metacommands, and dynamically alter SQL and metacommands with substitution variables."
|
|
9
9
|
readme = { file = "README.md", content-type = "text/markdown" }
|
|
10
10
|
license = { file = "LICENSE.txt" }
|
|
@@ -58,7 +58,7 @@ odbc = ["pyodbc"]
|
|
|
58
58
|
# Feature bundles
|
|
59
59
|
formats = ["odfpy", "xlrd", "openpyxl", "Jinja2", "polars", "tables", "PyYAML"]
|
|
60
60
|
auth = ["keyring"]
|
|
61
|
-
upsert = ["pg-upsert>=1.
|
|
61
|
+
upsert = ["pg-upsert>=1.21.0"]
|
|
62
62
|
# Convenience groups
|
|
63
63
|
all-db = [
|
|
64
64
|
"psycopg2-binary",
|
|
@@ -161,7 +161,7 @@ skip-magic-trailing-comma = false
|
|
|
161
161
|
line-ending = "auto"
|
|
162
162
|
|
|
163
163
|
[tool.bumpversion]
|
|
164
|
-
current_version = "2.
|
|
164
|
+
current_version = "2.15.0"
|
|
165
165
|
commit = true
|
|
166
166
|
commit_args = "--no-verify"
|
|
167
167
|
tag = true
|
|
@@ -8,7 +8,58 @@ from typing import TYPE_CHECKING, Any
|
|
|
8
8
|
if TYPE_CHECKING:
|
|
9
9
|
pass
|
|
10
10
|
|
|
11
|
-
__all__ = ["GuiBackend"]
|
|
11
|
+
__all__ = ["GuiBackend", "compare_stats"]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def compare_stats(
|
|
15
|
+
headers1: list,
|
|
16
|
+
rows1: list,
|
|
17
|
+
headers2: list,
|
|
18
|
+
rows2: list,
|
|
19
|
+
keylist: list,
|
|
20
|
+
) -> str:
|
|
21
|
+
"""Return a one-line diff summary for compare dialogs.
|
|
22
|
+
|
|
23
|
+
Computes matching rows, differing rows, and rows only in one table
|
|
24
|
+
based on the given key columns. Returns an empty string when *keylist*
|
|
25
|
+
is empty (stats cannot be computed without keys).
|
|
26
|
+
"""
|
|
27
|
+
if not keylist:
|
|
28
|
+
return ""
|
|
29
|
+
key_idx1 = [i for i, h in enumerate(headers1) if str(h) in keylist]
|
|
30
|
+
key_idx2 = [i for i, h in enumerate(headers2) if str(h) in keylist]
|
|
31
|
+
if not key_idx1 or not key_idx2:
|
|
32
|
+
return ""
|
|
33
|
+
|
|
34
|
+
def _kv(row: list | tuple, idxs: list) -> tuple:
|
|
35
|
+
return tuple(str(row[i]) if row[i] is not None else "" for i in idxs)
|
|
36
|
+
|
|
37
|
+
keys1 = {_kv(r, key_idx1) for r in rows1}
|
|
38
|
+
keys2 = {_kv(r, key_idx2) for r in rows2}
|
|
39
|
+
only1 = len(keys1 - keys2)
|
|
40
|
+
only2 = len(keys2 - keys1)
|
|
41
|
+
common_keys = keys1 & keys2
|
|
42
|
+
row_map1 = {_kv(r, key_idx1): r for r in rows1}
|
|
43
|
+
row_map2 = {_kv(r, key_idx2): r for r in rows2}
|
|
44
|
+
matching = 0
|
|
45
|
+
differing = 0
|
|
46
|
+
for k in common_keys:
|
|
47
|
+
r1 = [str(v) if v is not None else "" for v in row_map1[k]]
|
|
48
|
+
r2 = [str(v) if v is not None else "" for v in row_map2[k]]
|
|
49
|
+
if r1 == r2:
|
|
50
|
+
matching += 1
|
|
51
|
+
else:
|
|
52
|
+
differing += 1
|
|
53
|
+
parts: list[str] = []
|
|
54
|
+
if matching:
|
|
55
|
+
parts.append(f"{matching:,} matching")
|
|
56
|
+
if differing:
|
|
57
|
+
parts.append(f"{differing:,} differing")
|
|
58
|
+
if only1:
|
|
59
|
+
parts.append(f"{only1:,} only in Table 1")
|
|
60
|
+
if only2:
|
|
61
|
+
parts.append(f"{only2:,} only in Table 2")
|
|
62
|
+
return " | ".join(parts) if parts else "Tables are identical"
|
|
12
63
|
|
|
13
64
|
|
|
14
65
|
class GuiBackend(ABC):
|