execsql2 2.31.2__tar.gz → 2.33.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.31.2 → execsql2-2.33.0}/CHANGELOG.md +27 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/PKG-INFO +2 -2
- {execsql2-2.31.2 → execsql2-2.33.0}/README.md +1 -1
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/about/divergence.md +12 -10
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/dev/adding_db_adapters.md +1 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/dev/architecture.md +1 -1
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/guides/formatter.md +1 -1
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/reference/lint.md +1 -1
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/reference/metacommands.md +6 -4
- {execsql2-2.31.2 → execsql2-2.33.0}/extras/vscode-execsql/package-lock.json +2 -2
- {execsql2-2.31.2 → execsql2-2.33.0}/extras/vscode-execsql/package.json +1 -1
- {execsql2-2.31.2 → execsql2-2.33.0}/pyproject.toml +2 -2
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/db/base.py +19 -1
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/db/duckdb.py +60 -1
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/exporters/duckdb.py +2 -10
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/metacommands/reference.py +10 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/metacommands/upsert.py +13 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/models.py +73 -48
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/script/engine.py +5 -7
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/script/executor.py +3 -1
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/types.py +14 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/utils/datetime.py +16 -2
- execsql2-2.33.0/tests/db/test_bulk_inserts.py +169 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_postgres_inprocess.py +58 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/integration/test_postgres.py +49 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/metacommands/test_pg_upsert.py +34 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_engine.py +22 -17
- execsql2-2.33.0/tests/test_metacommand_errors.py +98 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_models.py +22 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_script.py +3 -3
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/utils/test_datetime.py +31 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/uv.lock +1 -1
- {execsql2-2.31.2 → execsql2-2.33.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/.github/dependabot.yml +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/.github/workflows/ci-cd.yml +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/.gitignore +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/.pre-commit-config.yaml +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/.pre-commit-hooks.yaml +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/.python-version +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/.readthedocs.yaml +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/CONTRIBUTING.md +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/LICENSE.txt +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/NOTICE +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/SECURITY.md +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/about/contributors.md +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/about/copyright.md +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/api/cli.md +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/api/db.md +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/api/exporters.md +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/api/importers.md +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/api/index.md +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/api/metacommands.md +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/dev/adding_exporters.md +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/dev/adding_importers.md +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/dev/adding_metacommands.md +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/dev/releasing.md +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/getting-started/installation.md +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/getting-started/requirements.md +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/getting-started/syntax.md +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/guides/debugging.md +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/guides/documentation.md +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/guides/editors.md +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/guides/encoding.md +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/guides/examples.md +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/guides/logging.md +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/guides/sql_syntax.md +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/guides/usage.md +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/guides/using_scripts.md +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/images/Compare_planets.png +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/images/actions.png +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/images/actions2.png +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/images/checkboxes.png +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/images/connect.png +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/images/data_error1_screenshot.jpg +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/images/execsql_logo_01.png +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/images/fatals.png +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/images/pause_terminal_sm.png +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/images/set_build_commands.jpg +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/images/unit_conversions_029.png +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/images/unmatched.png +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/images/vim_execsql_highlight.png +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/index.md +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/reference/configuration.md +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/reference/security.md +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/reference/substitution_vars.md +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/templates/compare.md +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/templates/glossary.md +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/templates/index.md +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/docs/templates/upsert.md +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/extras/plugin-template/README.md +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/extras/plugin-template/pyproject.toml +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/extras/plugin-template/src/execsql_plugin_YOURNAME/__init__.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/extras/plugin-template/tests/test_plugin.py.example +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/extras/vscode-execsql/.vscodeignore +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/extras/vscode-execsql/LICENSE.txt +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/extras/vscode-execsql/README.md +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/extras/vscode-execsql/extension.js +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/extras/vscode-execsql/icon.png +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/extras/vscode-execsql/syntaxes/execsql.tmLanguage.json +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/justfile +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/scripts/benchmarks/README.md +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/scripts/benchmarks/results/.gitignore +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/scripts/benchmarks/run_benchmarks.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/scripts/generate_vscode_grammar.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/scripts/release_notes.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/__init__.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/__main__.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/api.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/cli/__init__.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/cli/application.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/cli/commands/__init__.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/cli/commands/config.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/cli/commands/format.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/cli/commands/init.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/cli/commands/lint.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/cli/commands/listing.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/cli/commands/lsp.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/cli/commands/run.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/cli/dispatch.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/cli/dsn.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/cli/help.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/cli/lint.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/cli/options.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/cli/run.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/config.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/config_validation.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/data/__init__.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/data/execsql.conf.template +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/data/script_header.sql +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/db/__init__.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/db/access.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/db/dsn.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/db/factory.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/db/firebird.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/db/mysql.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/db/odbc.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/db/oracle.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/db/postgres.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/db/sqlite.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/db/sqlserver.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/db/tiers.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/debug/__init__.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/debug/repl.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/exceptions.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/exporters/__init__.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/exporters/base.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/exporters/delimited.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/exporters/feather.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/exporters/html.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/exporters/json.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/exporters/latex.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/exporters/markdown.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/exporters/ods.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/exporters/parquet.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/exporters/pretty.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/exporters/protocol.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/exporters/raw.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/exporters/sqlite.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/exporters/templates.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/exporters/values.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/exporters/xls.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/exporters/xlsx.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/exporters/xml.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/exporters/yaml.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/exporters/zip.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/format.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/gui/__init__.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/gui/base.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/gui/console.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/gui/desktop.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/gui/tui.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/importers/__init__.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/importers/base.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/importers/csv.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/importers/feather.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/importers/json.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/importers/ods.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/importers/xls.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/interactive.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/lsp/__init__.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/lsp/_util.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/lsp/cache.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/lsp/code_actions.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/lsp/completion.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/lsp/diagnostics.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/lsp/document.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/lsp/folding.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/lsp/formatting.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/lsp/hover.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/lsp/navigation.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/lsp/rename.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/lsp/server.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/manifest.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/metacommands/__init__.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/metacommands/conditions.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/metacommands/connect.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/metacommands/control.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/metacommands/data.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/metacommands/debug.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/metacommands/dispatch.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/metacommands/effects.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/metacommands/io.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/metacommands/io_export.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/metacommands/io_fileops.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/metacommands/io_import.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/metacommands/io_write.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/metacommands/prompt.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/metacommands/script_ext.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/metacommands/system.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/parser.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/plugins.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/py.typed +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/script/__init__.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/script/ast.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/script/control.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/script/parser.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/script/variables.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/state.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/state.pyi +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/utils/__init__.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/utils/auth.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/utils/color.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/utils/crypto.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/utils/errors.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/utils/fileio.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/utils/gui.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/utils/mail.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/utils/numeric.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/utils/paths.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/utils/regex.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/utils/strings.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/utils/timer.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/templates/README.md +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/templates/config_settings.sqlite +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/templates/example_config_prompt.sql +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/templates/make_config_db.sql +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/templates/md_compare.sql +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/templates/md_glossary.sql +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/templates/md_upsert.sql +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/templates/pg_compare.sql +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/templates/pg_glossary.sql +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/templates/script_template.sql +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/templates/ss_compare.sql +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/templates/ss_glossary.sql +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/templates/ss_upsert.sql +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/__init__.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/cli/__init__.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/cli/test_cli.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/cli/test_cli_e2e.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/cli/test_cli_run.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/cli/test_cli_surface.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/cli/test_commands.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/cli/test_config_validate.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/cli/test_dispatch.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/cli/test_error_location.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/cli/test_init.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/cli/test_lint.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/cli/test_lint_fixtures.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/cli/test_log_exit_record.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/cli/test_manifest.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/cli/test_ping.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/cli/test_profile.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/cli/test_sigterm.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/cli/test_undeclared_imports.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/conftest.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/data/formatter_shapes/comment_anchoring.sql +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/data/formatter_shapes/escapes_and_literals.sql +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/data/formatter_shapes/substitution_and_metacommands.sql +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/data/formatter_shapes/unparsable_sql.sql +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/data/lint/conditions_and_blocks.sql +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/data/lint/does_not_parse.sql +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/data/lint/every_metacommand.sql +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/data/lint/invalid.sql +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/data/ods/libreoffice.fods +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/data/ods/libreoffice.ods +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/__init__.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/conftest.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_access_windows.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_base.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_copy_new.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_db_adapters_mocked.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_db_adapters_mocked_extra.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_dsn.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_duckdb.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_factory.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_factory_postgres_encoding.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_import_atomic.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_import_replacement_quoted.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_import_short_rows.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_import_timezones.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_mysql_case_folding.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_mysql_inprocess.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_mysql_trailing_blank_lines.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_new_table_checks.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_odbc_fresh_sessions.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_oracle_driver.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_postgres.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_quoted_substitution_roundtrip.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_replacement_missing_table.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_roundtrip_adapters.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_sqlite.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_sqlite_extra.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_sqlserver_driver18.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_sqlserver_inprocess.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_tiers.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_transactions.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/__init__.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/conftest.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_base.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_db.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_delimited.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_duckdb_exporter.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_exporters.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_feather.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_html_extended.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_html_latex.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_json.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_json_extended.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_latex_extended.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_markdown.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_ods.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_ods_export.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_parquet.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_pretty_extended.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_raw_extended.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_roundtrip_columnar.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_roundtrip_dbfile.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_roundtrip_delimited.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_roundtrip_json.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_roundtrip_latex_xml.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_roundtrip_markup.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_roundtrip_ods.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_roundtrip_pretty_raw.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_roundtrip_template.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_roundtrip_values.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_roundtrip_xlsx.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_sqlite_exporter.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_templates.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_templates_extended.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_values_extended.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_values_roundtrip.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_xls_xlsx.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_xlsx.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_xml.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_yaml.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_zip.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/gui/__init__.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/gui/test_backends.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/gui/test_backends_extended.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/gui/test_compare_stats.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/gui/test_compute_row_diffs.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/gui/test_desktop_dialogs.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/gui/test_format_table.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/gui/test_tui_pilot.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/gui/test_tui_pilot_complex.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/gui/test_utils_gui_extended.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/importers/__init__.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/importers/conftest.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/importers/test_base_extended.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/importers/test_csv_blank_lines.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/importers/test_csv_edge_cases.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/importers/test_csv_importer.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/importers/test_feather_importer.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/importers/test_json_importer.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/importers/test_ods_importer.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/importers/test_ods_values.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/importers/test_roundtrip_csv.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/importers/test_xls_importer.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/integration/__init__.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/integration/conftest.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/integration/test_dsn.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/integration/test_duckdb.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/integration/test_mysql.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/integration/test_sqlite.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/live_db.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/lsp/__init__.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/lsp/test_cache.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/lsp/test_code_actions.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/lsp/test_completion.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/lsp/test_diagnostics.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/lsp/test_document.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/lsp/test_folding.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/lsp/test_formatting.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/lsp/test_hover.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/lsp/test_lsp_command.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/lsp/test_navigation.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/lsp/test_rename.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/lsp/test_server.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/metacommands/__init__.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/metacommands/conftest.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/metacommands/test_assert.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/metacommands/test_breakpoint.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/metacommands/test_conditions_extra.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/metacommands/test_connect.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/metacommands/test_io_export.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/metacommands/test_io_import.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/metacommands/test_metacommands.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/metacommands/test_metacommands_connect.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/metacommands/test_metacommands_data.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/metacommands/test_metacommands_extended.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/metacommands/test_metacommands_fileops_extra.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/metacommands/test_metacommands_io.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/metacommands/test_metacommands_io_write_extra.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/metacommands/test_metacommands_script_ext.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/metacommands/test_metacommands_system.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/metacommands/test_metacommands_system_extra.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/metacommands/test_prompt.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/metacommands/test_row_count.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/metacommands/test_serve_output.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/metacommands/test_show_scripts.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/__init__.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/audit_lint_bad.sql +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/audit_smoke/sample.json +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/audit_smoke/sample.jsonl +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/audit_smoke.sql +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/config_runtime.sql +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/control_flow.sql +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/counters_and_locals.sql +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/error_handling.sql +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/import_options/has_comments.csv +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/import_options.sql +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/includes/helper.sql +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/includes.sql +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/io_formats.sql +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/io_roundtrip.sql +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/multi_db.sql +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/parquet_feather_roundtrip.sql +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/parse_only/parse_tree.sql +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/predicates.sql +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/scripts.sql +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/smoke.sql +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/subroutine_loops.sql +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/subvars_advanced.sql +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/template_export/greeting.tmpl +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/template_export.sql +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/timer.sql +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/transactions.sql +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/write_create_table.sql +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/xlsx_ods_roundtrip.sql +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/test_sql_scripts.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/security/__init__.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/security/test_env_detection.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/security/test_expansion_bomb.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/security/test_import_regex_hardening.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/security/test_log_redaction.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/security/test_log_secrets.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/security/test_network_paths.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/security/test_odbc_connection_strings.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/security/test_odbc_injection.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/security/test_path_containment.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/security/test_substitution_injection.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/security/test_zip_bomb.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/templates/__init__.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/templates/conftest.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/templates/test_compare.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/templates/test_glossary.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/templates/test_static.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/templates/test_upsert.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_api.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_api_errors.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_api_extended.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_api_filewriter.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_api_isolation.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_api_subprocess.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_ast.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_ast_parser.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_bundled_templates.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_config.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_config_data.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_config_extended.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_date_inference.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_debug_repl.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_error_messages.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_exceptions.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_executor.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_executor_inprocess.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_format.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_interactive.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_mail.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_package.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_parser.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_parser_params.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_plugins.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_reference.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_registry.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_release_notes.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_state.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_tooling_versions.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_types.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/utils/__init__.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/utils/test_auth.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/utils/test_auth_extra.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/utils/test_crypto.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/utils/test_encodedfile_context.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/utils/test_errors.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/utils/test_errors_extra.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/utils/test_fileio.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/utils/test_fileio_extra.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/utils/test_keyring_rules.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/utils/test_mail_tls.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/utils/test_numeric.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/utils/test_regex.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/utils/test_strings.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/utils/test_timer.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/tests/utils/test_timer_extra.py +0 -0
- {execsql2-2.31.2 → execsql2-2.33.0}/zensical.toml +0 -0
|
@@ -13,6 +13,33 @@ ______________________________________________________________________
|
|
|
13
13
|
|
|
14
14
|
______________________________________________________________________
|
|
15
15
|
|
|
16
|
+
## [2.33.0] - 2026-10-16
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- **`$PG_UPSERT_STATUS`** says how a `PG_UPSERT` ended (`committed`, `dry_run`, `no_changes`, `qa_failed`, `cancelled`, `db_error`), and **`$PG_UPSERT_ERROR`** holds the database error message. A run with nothing to load and a cancelled run used to look the same in the other variables.
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- **A failed metacommand sets `$LAST_ERROR` and `$ERROR_MESSAGE`** with `METACOMMAND_ERROR_HALT OFF`, so a script can tell what failed, not only that `METACOMMAND_ERROR()` is true; an `ON ERROR_HALT` handler (including `ON ERROR_HALT WRITE "…!{$LAST_ERROR}!"`) now gets `$LAST_ERROR` too. The error is also written to `execsql.log`.
|
|
25
|
+
- **`execsql.run()` reports a metacommand that failed under `METACOMMAND_ERROR_HALT OFF`** in `result.errors`, with `success` False, as it already did for a SQL error under `ERROR_HALT OFF`.
|
|
26
|
+
- **`PG_UPSERT` cancelled from a prompt** no longer leaves an earlier run's `$PG_UPSERT_*` values (such as `$PG_UPSERT_COMMITTED` `TRUE`) in place.
|
|
27
|
+
|
|
28
|
+
______________________________________________________________________
|
|
29
|
+
|
|
30
|
+
## [2.32.0] - 2026-10-16
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
- **Working out column types is about twice as fast** for `IMPORT`, `COPY` and the exports that create a table (`EXPORT … AS SQLITE`, `DUCKDB`, `JSON`, `FEATHER`); a CSV whose dates repeat scans close to three times as fast. The types chosen are the same.
|
|
35
|
+
- **`IMPORT`, `COPY` and `EXPORT … AS DUCKDB` into DuckDB are about five times as fast**: they insert many rows per statement. Copying 200,000 rows took 124 s and now takes 22 s.
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
|
|
39
|
+
- **A column of large integers**, such as epoch milliseconds, no longer makes `IMPORT`, `COPY` or those exports hang or run out of memory while execsql works out the column types.
|
|
40
|
+
|
|
41
|
+
______________________________________________________________________
|
|
42
|
+
|
|
16
43
|
## [2.31.2] - 2026-10-16
|
|
17
44
|
|
|
18
45
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: execsql2
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.33.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/en/stable/
|
|
6
6
|
Project-URL: Repository, https://github.com/geocoug/execsql
|
|
@@ -441,7 +441,7 @@ execsql format --no-sql --in-place scripts/
|
|
|
441
441
|
```yaml
|
|
442
442
|
repos:
|
|
443
443
|
- repo: https://github.com/geocoug/execsql
|
|
444
|
-
rev: v2.
|
|
444
|
+
rev: v2.33.0
|
|
445
445
|
hooks:
|
|
446
446
|
- id: execsql-format
|
|
447
447
|
- id: execsql-lint
|
|
@@ -48,15 +48,15 @@ ______________________________________________________________________
|
|
|
48
48
|
|
|
49
49
|
### Metacommands
|
|
50
50
|
|
|
51
|
-
| Metacommand | Description
|
|
52
|
-
| ----------------------- |
|
|
53
|
-
| `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.
|
|
54
|
-
| `BREAKPOINT` | Pause script execution and drop into an interactive debug REPL. See [Debugging](#debugging) below for full details.
|
|
55
|
-
| `CONFIG SHOW_PROGRESS` | Enable the Rich progress bar for IMPORT operations at runtime.
|
|
56
|
-
| `CONFIG LOG_SQL` | Enable SQL query audit logging — writes executed SQL to the log file.
|
|
57
|
-
| `PG_UPSERT` | QA-checked, FK-dependency-ordered upserts from staging to base schema on PostgreSQL via the optional [pg-upsert](https://pg-upsert.readthedocs.io/) dependency. Modes: `PG_UPSERT` (full pipeline), `PG_UPSERT QA` (checks only), `PG_UPSERT CHECK` (schema only). QA checks only the rows the given `METHOD` would write. QA failure does not raise — the result is reported via `$PG_UPSERT_QA_PASSED` so the caller's `IF` / `ASSERT` decides control flow. |
|
|
58
|
-
| `IMPORT … FROM JSON` | Import a JSON file (array of objects or [JSON Lines](https://jsonlines.org/)) into a database table. Nested objects are flattened with dot-separated column names; arrays are stored as JSON strings.
|
|
59
|
-
| `SHOW SCRIPTS [<name>]` | Without a name, lists all registered SCRIPT definitions with parameter signatures and source locations. With a name, shows detail including parameters (with defaults), source file/line range, and docstring.
|
|
51
|
+
| Metacommand | Description |
|
|
52
|
+
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
53
|
+
| `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. |
|
|
54
|
+
| `BREAKPOINT` | Pause script execution and drop into an interactive debug REPL. See [Debugging](#debugging) below for full details. |
|
|
55
|
+
| `CONFIG SHOW_PROGRESS` | Enable the Rich progress bar for IMPORT operations at runtime. |
|
|
56
|
+
| `CONFIG LOG_SQL` | Enable SQL query audit logging — writes executed SQL to the log file. |
|
|
57
|
+
| `PG_UPSERT` | QA-checked, FK-dependency-ordered upserts from staging to base schema on PostgreSQL via the optional [pg-upsert](https://pg-upsert.readthedocs.io/) dependency. Modes: `PG_UPSERT` (full pipeline), `PG_UPSERT QA` (checks only), `PG_UPSERT CHECK` (schema only). QA checks only the rows the given `METHOD` would write. QA failure does not raise — the result is reported via `$PG_UPSERT_QA_PASSED` so the caller's `IF` / `ASSERT` decides control flow; `$PG_UPSERT_STATUS` says how the run ended. |
|
|
58
|
+
| `IMPORT … FROM JSON` | Import a JSON file (array of objects or [JSON Lines](https://jsonlines.org/)) into a database table. Nested objects are flattened with dot-separated column names; arrays are stored as JSON strings. |
|
|
59
|
+
| `SHOW SCRIPTS [<name>]` | Without a name, lists all registered SCRIPT definitions with parameter signatures and source locations. With a name, shows detail including parameters (with defaults), source file/line range, and docstring. |
|
|
60
60
|
|
|
61
61
|
### SCRIPT Enhancements
|
|
62
62
|
|
|
@@ -264,6 +264,7 @@ All 33 mutable runtime globals in `state.py` have been consolidated into a `Runt
|
|
|
264
264
|
- **PostgreSQL driver is psycopg3** — the PostgreSQL adapter uses `psycopg` (psycopg3, installed via the `psycopg[binary]` extra) instead of upstream's `psycopg2`. The connection now passes the libpq `dbname` keyword (psycopg3 rejects psycopg2's `database`), `VACUUM` toggles `conn.autocommit` instead of the removed `set_session()`, server-side `COPY` import uses psycopg3's `cursor.copy()` context manager instead of `copy_expert()`, and binary `IMPORT` sends raw `bytes` instead of `psycopg2.Binary()`. This also makes `db.conn` directly usable by the `PG_UPSERT` metacommand's pg-upsert ≥1.26.0 dependency, which itself requires a psycopg3 connection. The connection sets `prepare_threshold=None` to disable psycopg3's automatic server-side prepared statements, which psycopg2 never used; without this, re-running a query (e.g. via `EXPORT`) against an object the script drops/recreates or alters raises `FeatureNotSupported: cached plan must not change result type`.
|
|
265
265
|
- **Oracle driver is python-oracledb** — the Oracle adapter imports `oracledb`, the driver the `oracle` extra installs, instead of upstream's `cx_Oracle` (which ships no wheels for Python 3.12+). The `makedsn` / `connect` calls are the same. `cx_Oracle` is still used when it is the only Oracle driver installed.
|
|
266
266
|
- **SQL Server tries ODBC Driver 18 first** — upstream's driver list stopped at Driver 17, so a machine with only Microsoft's current driver could not connect. Driver 18 encrypts the connection and verifies the server's certificate; the `trust_server_certificate` setting accepts a self-signed one.
|
|
267
|
+
- **DuckDB bulk inserts** — `IMPORT`, `COPY` and `EXPORT ... AS DUCKDB` insert many rows per `INSERT ... VALUES` statement instead of `executemany()`, which DuckDB runs one row at a time; the values stored are the same. While binding them, and only when pandas is not installed, `sys.modules["pandas"]` is `None`: the duckdb package looks pandas up twice per row, and without that entry each lookup searched `sys.path`.
|
|
267
268
|
- **Each pyodbc connection is a new session** — the SQL Server, Access and ODBC DSN adapters set `pyodbc.pooling = False` before they connect. With pyodbc's default pooling, unixODBC (macOS, Linux) handed a closed connection's session unchanged to the next connection to the same server, so temp tables and `SET` options from one `CONNECT`, or one `execsql.run()`, were still there in the next. pyodbc applies the setting at the process's first pyodbc connection, so it also covers other pyodbc connections the process makes, and has no effect if the program connected with pyodbc before execsql did.
|
|
268
269
|
- **`Database` is an ABC** — `open_db()` and `exec_cmd()` are abstract methods. Subclasses that omit them raise `TypeError` at instantiation instead of at call time.
|
|
269
270
|
- **Connection timeouts** — PostgreSQL and SQLite adapters accept a connection timeout parameter (default 30 seconds).
|
|
@@ -348,7 +349,7 @@ These are behavioral changes driven by security or correctness issues in the ups
|
|
|
348
349
|
| `WRITE` after `CD` | A relative `WRITE ... TO` path is resolved against the working directory at the time of the `WRITE`, so it follows an earlier `CD` the way `EXPORT` and `IMPORT` already did. Upstream wrote it relative to the directory execsql started in, because its writer subprocess never saw the `CD`. |
|
|
349
350
|
| Substitution variable cycles | 100-iteration limit prevents infinite loops on cyclic variable references. |
|
|
350
351
|
| Script location in error messages | `ErrInfo.script_file` and `script_line_no` are now populated via `stamp_errinfo()` so error output includes "Line N of script foo.sql" context — restoring behavior present in the monolith. |
|
|
351
|
-
| `$ERROR_MESSAGE` not updated | `$ERROR_MESSAGE` is now set on every error path: `exit_now()`, non-halting SQL errors, and non-halting metacommand errors. Previously it was initialized to `""` and never changed.
|
|
352
|
+
| `$ERROR_MESSAGE` not updated | `$ERROR_MESSAGE` is now set on every error path: `exit_now()`, non-halting SQL errors, and non-halting metacommand errors. Previously it was initialized to `""` and never changed. `$LAST_ERROR` also holds a failed metacommand, with `METACOMMAND_ERROR_HALT OFF` and in an `ON ERROR_HALT` handler, as the documentation describes; upstream set it only for SQL errors and unknown metacommands. |
|
|
352
353
|
| Metacommand error message lost | When `halt_on_metacommand_err` is `ON`, the original handler `ErrInfo` is now re-raised; the generic "Unknown metacommand" message no longer replaces the specific error from the handler. |
|
|
353
354
|
| Empty script name in error msg | `_execute_script_direct()` and `_execute_script_textual_console()` no longer append "in script , line 0" to uncaught-exception messages when `current_script_line()` returns an empty string. |
|
|
354
355
|
| `PROMPT COMPARE` diff comparison | Diff engine uses native Python equality instead of string comparison — numeric types, Decimals, and booleans compare correctly. `None` is distinguished from empty string. Columns are matched by name (not position), key columns are excluded from comparison, and duplicate PKs keep the first row. |
|
|
@@ -434,6 +435,7 @@ These are behavioral changes driven by security or correctness issues in the ups
|
|
|
434
435
|
| ODS export replaced newlines inside a value with spaces | String cells were written after `item.replace("\n", " ")`, so an exported value silently differed from the one in the database. An XML parser does normalise a newline inside an attribute, so `office:string-value` cannot carry one — but ODF represents a multi-line cell as one `<text:p>` per line, which is what LibreOffice writes and what execsql2 now writes. Single-line values still use the attribute. |
|
|
435
436
|
| XLSX export wrote `numeric` columns as text | `_cell_value()` passed `int` and `float` straight to openpyxl but let every other type fall through to `str()`. PostgreSQL, MySQL, and DuckDB all return `Decimal` for `numeric`/`decimal` columns, so measurement values arrived in Excel as text cells — invisible in a screenshot, useless in a formula, a sort, or a chart. openpyxl lists `Decimal` in its own `NUMERIC_TYPES` and stores it as a real numeric cell, so the conversion was never needed. Found by exporting from a live PostgreSQL container and checking `cell.data_type` rather than `cell.value`. |
|
|
436
437
|
| `EMAIL` SMTP login without a password | Upstream calls `smtplib.login()` with only a username when `[email] password` and `enc_password` are both unset, which raises `TypeError` from the stdlib — `login()` has no single-argument form. execsql2 reports a configuration error naming the missing password instead. |
|
|
438
|
+
| Type scan of large integers | Working out a column's type tested each integer against the binary type by building `bytearray(n)`, n zero bytes, so a column of large integers (epoch milliseconds: about a terabyte per value) hung or ran out of memory. The binary test now answers for an integer without building it; the inferred types are unchanged. |
|
|
437
439
|
|
|
438
440
|
______________________________________________________________________
|
|
439
441
|
|
|
@@ -148,6 +148,7 @@ These are the instance attributes and methods you must configure correctly:
|
|
|
148
148
|
| `schema_exists()` | method | **Must override.** Return `False` if schemas are not supported. |
|
|
149
149
|
| `drop_table()` | method | **Must override.** Drop a table (used by IMPORT when creating fresh). |
|
|
150
150
|
| `populate_table()` | method | **Must override.** Bulk-load rows from a generator (used by IMPORT). |
|
|
151
|
+
| `row_inserter()` | method | Override for a faster bulk path that stores exactly what `executemany()` stores. |
|
|
151
152
|
| `exec_cmd()` | method | Override if the DBMS can execute stored procedures or views as commands. |
|
|
152
153
|
|
|
153
154
|
Methods inherited from `Database` that you get for free include `execute()`, `cursor()`, `close()`, `commit()`, `rollback()`, `select_rowsource()`, `select_data()`, `schema_qualified_table_name()`, `quote_identifier()`, and `paramsubs()`.
|
|
@@ -297,7 +297,7 @@ ______________________________________________________________________
|
|
|
297
297
|
1. An `IMPORT` metacommand is dispatched to the appropriate handler.
|
|
298
298
|
1. The handler instantiates the correct importer (e.g., `importers/csv.py`, `importers/ods.py`) based on the file type.
|
|
299
299
|
1. The importer reads the file and yields rows.
|
|
300
|
-
1. `Database.populate_table()`
|
|
300
|
+
1. `Database.populate_table()` converts the rows and inserts them in batches of `import_row_buffer` rows, in one transaction, with optional progress bars. Each batch goes through the adapter's `row_inserter()`: `executemany()` by default, and multi-row `INSERT ... VALUES` on DuckDB, whose `executemany()` binds one row at a time.
|
|
301
301
|
|
|
302
302
|
### Export metadata
|
|
303
303
|
|
|
@@ -272,7 +272,7 @@ select id,name,created_at from users where active = true order by name;
|
|
|
272
272
|
```yaml
|
|
273
273
|
repos:
|
|
274
274
|
- repo: https://github.com/geocoug/execsql
|
|
275
|
-
rev: v2.
|
|
275
|
+
rev: v2.33.0
|
|
276
276
|
hooks:
|
|
277
277
|
- id: execsql-format
|
|
278
278
|
- id: execsql-lint # optional: fail the commit on lint errors
|
|
@@ -158,7 +158,7 @@ The `execsql-lint` [pre-commit](https://pre-commit.com/) hook runs `execsql lint
|
|
|
158
158
|
```yaml
|
|
159
159
|
repos:
|
|
160
160
|
- repo: https://github.com/geocoug/execsql
|
|
161
|
-
rev: v2.
|
|
161
|
+
rev: v2.33.0
|
|
162
162
|
hooks:
|
|
163
163
|
- id: execsql-lint
|
|
164
164
|
args: [--ignore, V002] # optional: any execsql lint option
|
|
@@ -1766,7 +1766,7 @@ Evaluates whether or not the specified value is equal to zero. If the value is n
|
|
|
1766
1766
|
METACOMMAND_ERROR()
|
|
1767
1767
|
```
|
|
1768
1768
|
|
|
1769
|
-
Evaluates whether the previous metacommand generated an error. This test for SQL errors will only be effective if the [METACOMMAND_ERROR_HALT OFF](#metacommanderrorhalt) metacommand has previously been issued. This conditional must be used in the first metacommand after any metacommand that might have encountered an error.
|
|
1769
|
+
Evaluates whether the previous metacommand generated an error. This test for SQL errors will only be effective if the [METACOMMAND_ERROR_HALT OFF](#metacommanderrorhalt) metacommand has previously been issued. This conditional must be used in the first metacommand after any metacommand that might have encountered an error: any metacommand that succeeds, even a WRITE, clears the flag. The error itself stays available until the next error: [$LAST_ERROR](substitution_vars.md#system_vars) holds the metacommand that failed and [$ERROR_MESSAGE](substitution_vars.md#system_vars) its message.
|
|
1770
1770
|
|
|
1771
1771
|
|
|
1772
1772
|
### *NEWER_DATE*
|
|
@@ -2109,7 +2109,7 @@ The [BREAK](#break) metacommand can be used to exit from a loop at any time.
|
|
|
2109
2109
|
METACOMMAND_ERROR_HALT ON|OFF
|
|
2110
2110
|
```
|
|
2111
2111
|
|
|
2112
|
-
When METACOMMAND_ERROR_HALT is set to ON, which is the default, any errors that occur during execution of a metacommand will cause an error message to be displayed immediately, and *execsql* to halt. When METACOMMAND_ERROR_HALT is set to OFF, then metacommand errors will be ignored, but can be evaluated with the [IF METACOMMAND_ERROR](#metacommanderror) conditional.
|
|
2112
|
+
When METACOMMAND_ERROR_HALT is set to ON, which is the default, any errors that occur during execution of a metacommand will cause an error message to be displayed immediately, and *execsql* to halt. When METACOMMAND_ERROR_HALT is set to OFF, then metacommand errors will be ignored, but can be evaluated with the [IF METACOMMAND_ERROR](#metacommanderror) conditional; `$LAST_ERROR` and `$ERROR_MESSAGE` hold the failed metacommand and its message.
|
|
2113
2113
|
|
|
2114
2114
|
The METACOMMAND_ERROR_HALT metacommand does not itself set or reset the internal flag that indicates whether a metacommand has encountered an error. This is so that a "METACOMMAND_ERROR_HALT Off" command can be used immediately after a potentially-failing metacommand and not alter the error flag that is set by the previous command. Specifically, constructions like this:
|
|
2115
2115
|
|
|
@@ -2356,6 +2356,8 @@ Set after every `PG_UPSERT` execution:
|
|
|
2356
2356
|
| `$PG_UPSERT_ROWS_UPDATED` | integer | Total rows updated across all tables |
|
|
2357
2357
|
| `$PG_UPSERT_ROWS_INSERTED` | integer | Total rows inserted across all tables |
|
|
2358
2358
|
| `$PG_UPSERT_COMMITTED` | TRUE/FALSE | Were changes committed? |
|
|
2359
|
+
| `$PG_UPSERT_STATUS` | string | How the run ended: `committed`, `dry_run` (changes made, then rolled back because `COMMIT` was not given), `no_changes` (QA passed, nothing to insert or update), `qa_failed`, `cancelled` or `db_error`. Empty after `PG_UPSERT QA` and `PG_UPSERT CHECK`, which do not upsert. |
|
|
2360
|
+
| `$PG_UPSERT_ERROR` | string | The database error message when `$PG_UPSERT_STATUS` is `db_error`; otherwise empty |
|
|
2359
2361
|
| `$PG_UPSERT_STAGING_SCHEMA` | string | Staging schema name used |
|
|
2360
2362
|
| `$PG_UPSERT_BASE_SCHEMA` | string | Base schema name used |
|
|
2361
2363
|
| `$PG_UPSERT_TABLES` | string | Comma-separated list of table names processed |
|
|
@@ -2380,7 +2382,7 @@ Set after every `PG_UPSERT` execution:
|
|
|
2380
2382
|
|
|
2381
2383
|
### QA failure behavior
|
|
2382
2384
|
|
|
2383
|
-
`PG_UPSERT`, `PG_UPSERT QA`, and `PG_UPSERT CHECK` do **not** raise an error when QA checks fail. The outcome is reported via `$PG_UPSERT_QA_PASSED` (and the per-table `$PG_UPSERT_TABLE_QA_PASSED`), so the script controls flow with `IF` or with execsql's standard error-halt configuration. When QA fails, the metacommand still runs `EXPORT_FAILURES`, populates all `$PG_UPSERT_*` substitution variables, and skips the upsert (no commit). Pair with `ASSERT` to halt explicitly:
|
|
2385
|
+
`PG_UPSERT`, `PG_UPSERT QA`, and `PG_UPSERT CHECK` do **not** raise an error when QA checks fail. The outcome is reported via `$PG_UPSERT_QA_PASSED` (and the per-table `$PG_UPSERT_TABLE_QA_PASSED`), so the script controls flow with `IF` or with execsql's standard error-halt configuration. When QA fails, the metacommand still runs `EXPORT_FAILURES`, populates all `$PG_UPSERT_*` substitution variables, and skips the upsert (no commit). To tell the ways a run can end apart, branch on `$PG_UPSERT_STATUS` rather than combining `$PG_UPSERT_QA_PASSED` with `$PG_UPSERT_COMMITTED`: a run with nothing to load (`no_changes`) and one the user cancelled (`cancelled`) both leave QA passed and nothing committed. Pair with `ASSERT` to halt explicitly:
|
|
2384
2386
|
|
|
2385
2387
|
```sql
|
|
2386
2388
|
-- Soft failure: branch on the result, keep running.
|
|
@@ -2398,7 +2400,7 @@ Set after every `PG_UPSERT` execution:
|
|
|
2398
2400
|
|
|
2399
2401
|
### Database errors
|
|
2400
2402
|
|
|
2401
|
-
A database error during the run (a deadlock, a lock timeout, a constraint or trigger error the QA checks did not catch) is different from a QA failure. pg-upsert rolls the run back, and `PG_UPSERT` fails like any other metacommand: the driver's message is in `$ERROR_MESSAGE`, and the script halts unless `METACOMMAND_ERROR_HALT` is off. The `$PG_UPSERT_*` variables describe the failed run (`$PG_UPSERT_COMMITTED` is `FALSE` and the row counts are `0`), not an earlier `PG_UPSERT`.
|
|
2403
|
+
A database error during the run (a deadlock, a lock timeout, a constraint or trigger error the QA checks did not catch) is different from a QA failure. pg-upsert rolls the run back, and `PG_UPSERT` fails like any other metacommand: the driver's message is in `$ERROR_MESSAGE`, and the script halts unless `METACOMMAND_ERROR_HALT` is off. The `$PG_UPSERT_*` variables describe the failed run (`$PG_UPSERT_STATUS` is `db_error`, `$PG_UPSERT_ERROR` holds the message, `$PG_UPSERT_COMMITTED` is `FALSE` and the row counts are `0`), not an earlier `PG_UPSERT`. Cancelling at one of pg-upsert's own prompts is not an error: `PG_UPSERT` returns, with `$PG_UPSERT_STATUS` `cancelled`.
|
|
2402
2404
|
|
|
2403
2405
|
The rollback ends the connection's transaction, so under `AUTOCOMMIT OFF` or inside `BEGIN BATCH`, statements run earlier on the same connection are rolled back with it.
|
|
2404
2406
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "execsql-syntax",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.33.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "execsql-syntax",
|
|
9
|
-
"version": "2.
|
|
9
|
+
"version": "2.33.0",
|
|
10
10
|
"license": "GPL-3.0-or-later",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"vscode-languageclient": "10.1.2"
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "execsql-syntax",
|
|
3
3
|
"displayName": "execsql",
|
|
4
4
|
"description": "Syntax highlighting, lint findings, completion, hover, navigation, quick fixes and formatting for execsql scripts.",
|
|
5
|
-
"version": "2.
|
|
5
|
+
"version": "2.33.0",
|
|
6
6
|
"publisher": "geocoug",
|
|
7
7
|
"license": "GPL-3.0-or-later",
|
|
8
8
|
"icon": "icon.png",
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "execsql2"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.33.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" }
|
|
@@ -188,7 +188,7 @@ line-ending = "auto"
|
|
|
188
188
|
exclude = ["*.md"]
|
|
189
189
|
|
|
190
190
|
[tool.bumpversion]
|
|
191
|
-
current_version = "2.
|
|
191
|
+
current_version = "2.33.0"
|
|
192
192
|
commit = true
|
|
193
193
|
tag = true
|
|
194
194
|
tag_name = "v{new_version}"
|
|
@@ -665,6 +665,23 @@ class Database(ABC):
|
|
|
665
665
|
self.execute(f"drop table if exists {tablename} cascade;")
|
|
666
666
|
self.commit()
|
|
667
667
|
|
|
668
|
+
def row_inserter(self, curs: Any, table: str, columns: list[str]) -> Callable[[list[list[Any]]], None]:
|
|
669
|
+
"""Return a function that inserts one batch of rows into *table* through *curs*.
|
|
670
|
+
|
|
671
|
+
*table* is schema-qualified and quoted; *columns* are the quoted
|
|
672
|
+
column names, in the order of each row's values. IMPORT and COPY call
|
|
673
|
+
the function once per batch, all inside one transaction. This one
|
|
674
|
+
runs ``executemany``. An adapter may override it with a faster bulk
|
|
675
|
+
path only if that path stores exactly what ``executemany`` stores, for
|
|
676
|
+
every value type and column type: a faster load must not change data.
|
|
677
|
+
"""
|
|
678
|
+
sql = f"insert into {table} ({','.join(columns)}) values ({self.paramsubs(len(columns))});"
|
|
679
|
+
|
|
680
|
+
def insert(rows: list[list[Any]]) -> None:
|
|
681
|
+
curs.executemany(sql, rows)
|
|
682
|
+
|
|
683
|
+
return insert
|
|
684
|
+
|
|
668
685
|
def populate_table(
|
|
669
686
|
self,
|
|
670
687
|
schema_name: str | None,
|
|
@@ -740,6 +757,7 @@ class Database(ABC):
|
|
|
740
757
|
|
|
741
758
|
def _import_loop(curs) -> int:
|
|
742
759
|
nonlocal eof, total_rows, task_id
|
|
760
|
+
insert = self.row_inserter(curs, sq_name, columns)
|
|
743
761
|
while True:
|
|
744
762
|
b = []
|
|
745
763
|
for _j in range(_state.conf.import_row_buffer):
|
|
@@ -809,7 +827,7 @@ class Database(ABC):
|
|
|
809
827
|
b.append(row)
|
|
810
828
|
if len(b) > 0:
|
|
811
829
|
try:
|
|
812
|
-
|
|
830
|
+
insert(b)
|
|
813
831
|
except ErrInfo:
|
|
814
832
|
raise
|
|
815
833
|
except Exception as e:
|
|
@@ -8,8 +8,12 @@ analytics databases via the ``duckdb`` package. Corresponds to ``-t k``
|
|
|
8
8
|
on the CLI.
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
+
import importlib.util
|
|
11
12
|
import re
|
|
13
|
+
import sys
|
|
12
14
|
from collections import deque
|
|
15
|
+
from collections.abc import Callable, Iterator
|
|
16
|
+
from contextlib import contextmanager
|
|
13
17
|
from pathlib import Path
|
|
14
18
|
from typing import Any
|
|
15
19
|
|
|
@@ -18,7 +22,7 @@ from execsql.exceptions import ErrInfo
|
|
|
18
22
|
from execsql.utils.errors import exception_desc, fatal_error, install_hint
|
|
19
23
|
import execsql.state as _state
|
|
20
24
|
|
|
21
|
-
__all__ = ["DuckDBDatabase"]
|
|
25
|
+
__all__ = ["DuckDBDatabase", "insert_rows"]
|
|
22
26
|
|
|
23
27
|
# Statements that return rows; any other statement DuckDB answers with a
|
|
24
28
|
# one-column "Count" result (rows inserted, updated or deleted).
|
|
@@ -28,6 +32,53 @@ _QUERY_RX = re.compile(r"(?:SELECT|WITH|VALUES|FROM|TABLE|SHOW|DESCRIBE|SUMMARIZ
|
|
|
28
32
|
_TRANSACTION_RX = re.compile(r"(?P<begin>BEGIN|START\s+TRANSACTION)\b|(?P<end>COMMIT|END|ROLLBACK|ABORT)\b", re.I)
|
|
29
33
|
|
|
30
34
|
|
|
35
|
+
# Parameters bound by one INSERT ... VALUES statement in insert_rows(); a
|
|
36
|
+
# wide table gets fewer rows per statement.
|
|
37
|
+
_PARAMS_PER_STATEMENT = 10_000
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@contextmanager
|
|
41
|
+
def _pandas_lookup_skipped() -> Iterator[None]:
|
|
42
|
+
"""Make DuckDB's check for pandas fail at once while it binds rows.
|
|
43
|
+
|
|
44
|
+
The duckdb package asks Python's import system for ``pandas`` twice for
|
|
45
|
+
every row it binds, and when pandas is not installed nothing remembers
|
|
46
|
+
the answer, so each row searches ``sys.path`` again: about three
|
|
47
|
+
quarters of an INSERT's time. A ``None`` in ``sys.modules`` makes that
|
|
48
|
+
import fail without the search. It is set only when pandas is not
|
|
49
|
+
installed, so no import that could have succeeded fails, and it is
|
|
50
|
+
removed afterwards.
|
|
51
|
+
"""
|
|
52
|
+
if "pandas" in sys.modules or importlib.util.find_spec("pandas") is not None:
|
|
53
|
+
yield
|
|
54
|
+
return
|
|
55
|
+
sys.modules["pandas"] = None # type: ignore[assignment]
|
|
56
|
+
try:
|
|
57
|
+
yield
|
|
58
|
+
finally:
|
|
59
|
+
if "pandas" in sys.modules and sys.modules["pandas"] is None:
|
|
60
|
+
del sys.modules["pandas"]
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def insert_rows(curs: Any, table: str, columns: list[str], rows: list[Any]) -> None:
|
|
64
|
+
"""Insert *rows* into *table* with multi-row ``INSERT ... VALUES`` statements.
|
|
65
|
+
|
|
66
|
+
*table* is quoted (and schema-qualified as needed); *columns* are the
|
|
67
|
+
quoted column names, in the order of each row's values. DuckDB's
|
|
68
|
+
``executemany`` runs one row at a time; binding many rows in one
|
|
69
|
+
statement is several times faster and stores the same values.
|
|
70
|
+
"""
|
|
71
|
+
if not rows:
|
|
72
|
+
return
|
|
73
|
+
per_statement = max(1, _PARAMS_PER_STATEMENT // max(1, len(columns)))
|
|
74
|
+
head = f"insert into {table} ({','.join(columns)}) values "
|
|
75
|
+
row_marks = "(" + ",".join("?" * len(columns)) + ")"
|
|
76
|
+
with _pandas_lookup_skipped():
|
|
77
|
+
for start in range(0, len(rows), per_statement):
|
|
78
|
+
chunk = rows[start : start + per_statement]
|
|
79
|
+
curs.execute(head + ",".join([row_marks] * len(chunk)), [value for row in chunk for value in row])
|
|
80
|
+
|
|
81
|
+
|
|
31
82
|
class _SharedCursor:
|
|
32
83
|
"""A cursor that runs on the database's one connection.
|
|
33
84
|
|
|
@@ -261,6 +312,14 @@ class DuckDBDatabase(Database):
|
|
|
261
312
|
curs.execute(sql, params)
|
|
262
313
|
return len(curs.fetchall()) > 0
|
|
263
314
|
|
|
315
|
+
def row_inserter(self, curs: Any, table: str, columns: list[str]) -> Callable[[list[list[Any]]], None]:
|
|
316
|
+
"""Insert each batch with multi-row ``INSERT ... VALUES`` (:func:`insert_rows`)."""
|
|
317
|
+
|
|
318
|
+
def insert(rows: list[list[Any]]) -> None:
|
|
319
|
+
insert_rows(curs, table, columns, rows)
|
|
320
|
+
|
|
321
|
+
return insert
|
|
322
|
+
|
|
264
323
|
def view_exists(self, view_name: str, schema_name: str | None = None) -> bool:
|
|
265
324
|
"""Return True if the named view exists in the DuckDB database."""
|
|
266
325
|
# DuckDB information_schema has no 'views' table; views are listed in 'tables'
|
|
@@ -10,7 +10,6 @@ Requires the ``execsql2[duckdb]`` extra.
|
|
|
10
10
|
|
|
11
11
|
import datetime as _datetime
|
|
12
12
|
import decimal as _decimal
|
|
13
|
-
import math
|
|
14
13
|
from pathlib import Path
|
|
15
14
|
from typing import Any
|
|
16
15
|
|
|
@@ -38,9 +37,9 @@ def export_duckdb(
|
|
|
38
37
|
fatal_error(f"The duckdb module is required to export data in that format. {install_hint('duckdb')}")
|
|
39
38
|
return
|
|
40
39
|
|
|
40
|
+
from execsql.db.duckdb import insert_rows
|
|
41
41
|
from execsql.models import DataTable
|
|
42
42
|
|
|
43
|
-
chunksize = 10000
|
|
44
43
|
# B07a/F026: tablename and catalog are substituted from user input.
|
|
45
44
|
# Identifier-quote the table-name interpolation site and use
|
|
46
45
|
# placeholders for the information_schema literal-value query.
|
|
@@ -70,15 +69,8 @@ def export_duckdb(
|
|
|
70
69
|
curs.execute(sql)
|
|
71
70
|
# Export all rows of data
|
|
72
71
|
columns = [dbt_duckdb.quoted(col) for col in hdrs]
|
|
73
|
-
colspec = ",".join(columns)
|
|
74
|
-
paramspec = ",".join(("?",) * len(columns))
|
|
75
|
-
sql = f"insert into {qtable} ({colspec}) values ({paramspec});"
|
|
76
|
-
n_chunks = math.ceil(len(rowdata) / chunksize)
|
|
77
72
|
curs.execute("BEGIN TRANSACTION;")
|
|
78
|
-
|
|
79
|
-
start = i * chunksize
|
|
80
|
-
end = start + chunksize
|
|
81
|
-
curs.executemany(sql, rowdata[start:end])
|
|
73
|
+
insert_rows(curs, qtable, columns, rowdata)
|
|
82
74
|
curs.execute("COMMIT;")
|
|
83
75
|
curs.close()
|
|
84
76
|
ddb.close()
|
|
@@ -1364,6 +1364,16 @@ _SYSTEM_VARIABLES: tuple[SystemVariable, ...] = (
|
|
|
1364
1364
|
"TRUE or FALSE: whether the changes were committed.",
|
|
1365
1365
|
"metacommands.md#pg_upsert",
|
|
1366
1366
|
),
|
|
1367
|
+
SystemVariable(
|
|
1368
|
+
"$PG_UPSERT_STATUS",
|
|
1369
|
+
"How the run ended: committed, dry_run, no_changes, qa_failed, cancelled or db_error.",
|
|
1370
|
+
"metacommands.md#pg_upsert",
|
|
1371
|
+
),
|
|
1372
|
+
SystemVariable(
|
|
1373
|
+
"$PG_UPSERT_ERROR",
|
|
1374
|
+
"The database error message when $PG_UPSERT_STATUS is db_error; otherwise empty.",
|
|
1375
|
+
"metacommands.md#pg_upsert",
|
|
1376
|
+
),
|
|
1367
1377
|
SystemVariable("$PG_UPSERT_STAGING_SCHEMA", "Staging schema name used.", "metacommands.md#pg_upsert"),
|
|
1368
1378
|
SystemVariable("$PG_UPSERT_BASE_SCHEMA", "Base schema name used.", "metacommands.md#pg_upsert"),
|
|
1369
1379
|
SystemVariable("$PG_UPSERT_TABLES", "Comma-separated list of table names processed.", "metacommands.md#pg_upsert"),
|
|
@@ -185,6 +185,11 @@ def _set_subvars(result: Any) -> None:
|
|
|
185
185
|
sv("$PG_UPSERT_ROWS_UPDATED", str(result.total_updated))
|
|
186
186
|
sv("$PG_UPSERT_ROWS_INSERTED", str(result.total_inserted))
|
|
187
187
|
sv("$PG_UPSERT_COMMITTED", str(result.committed).upper())
|
|
188
|
+
# How the run ended: committed, dry_run, no_changes, qa_failed, cancelled
|
|
189
|
+
# or db_error. Empty for QA and CHECK, which do not run an upsert.
|
|
190
|
+
status = getattr(result, "status", None)
|
|
191
|
+
sv("$PG_UPSERT_STATUS", status.value if status is not None else "")
|
|
192
|
+
sv("$PG_UPSERT_ERROR", getattr(result, "error", None) or "")
|
|
188
193
|
sv("$PG_UPSERT_STAGING_SCHEMA", result.staging_schema)
|
|
189
194
|
sv("$PG_UPSERT_BASE_SCHEMA", result.base_schema)
|
|
190
195
|
sv("$PG_UPSERT_TABLES", ", ".join(t.table_name for t in result.tables))
|
|
@@ -426,6 +431,14 @@ def _handle_pg_upsert_errors(fn: Any, metacommandline: str | None) -> Any:
|
|
|
426
431
|
try:
|
|
427
432
|
return fn()
|
|
428
433
|
except UserCancelledError as exc:
|
|
434
|
+
# No result describes a run cancelled outside pg-upsert's own run();
|
|
435
|
+
# say what is known rather than leave an earlier run's values.
|
|
436
|
+
sv = _state.subvars.add_substitution
|
|
437
|
+
sv("$PG_UPSERT_STATUS", "cancelled")
|
|
438
|
+
sv("$PG_UPSERT_ERROR", "")
|
|
439
|
+
sv("$PG_UPSERT_COMMITTED", "FALSE")
|
|
440
|
+
sv("$PG_UPSERT_ROWS_UPDATED", "0")
|
|
441
|
+
sv("$PG_UPSERT_ROWS_INSERTED", "0")
|
|
429
442
|
raise ErrInfo(
|
|
430
443
|
"cmd",
|
|
431
444
|
command_text=metacommandline,
|