execsql2 2.20.0__tar.gz → 2.21.1__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.20.0 → execsql2-2.21.1}/.github/workflows/ci-cd.yml +2 -2
- {execsql2-2.20.0 → execsql2-2.21.1}/CHANGELOG.md +16 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/PKG-INFO +7 -7
- {execsql2-2.20.0 → execsql2-2.21.1}/README.md +1 -1
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/about/divergence.md +1 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/dev/adding_db_adapters.md +2 -2
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/getting-started/requirements.md +10 -10
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/guides/formatter.md +1 -1
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/guides/usage.md +1 -1
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/reference/metacommands.md +1 -1
- {execsql2-2.20.0 → execsql2-2.21.1}/pyproject.toml +5 -5
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/db/factory.py +1 -1
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/db/postgres.py +28 -18
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/db/test_postgres.py +23 -16
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/db/test_postgres_inprocess.py +5 -5
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/integration/test_postgres.py +6 -6
- {execsql2-2.20.0 → execsql2-2.21.1}/uv.lock +97 -71
- {execsql2-2.20.0 → execsql2-2.21.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/.github/dependabot.yml +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/.gitignore +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/.pre-commit-config.yaml +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/.pre-commit-hooks.yaml +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/.python-version +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/.readthedocs.yaml +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/CONTRIBUTING.md +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/LICENSE.txt +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/NOTICE +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/SECURITY.md +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/about/contributors.md +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/about/copyright.md +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/api/cli.md +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/api/db.md +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/api/exporters.md +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/api/importers.md +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/api/index.md +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/api/metacommands.md +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/dev/adding_exporters.md +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/dev/adding_importers.md +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/dev/adding_metacommands.md +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/dev/architecture.md +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/dev/releasing.md +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/getting-started/installation.md +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/getting-started/syntax.md +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/guides/debugging.md +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/guides/documentation.md +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/guides/encoding.md +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/guides/examples.md +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/guides/logging.md +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/guides/sql_syntax.md +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/guides/using_scripts.md +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/images/Compare_planets.png +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/images/actions.png +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/images/actions2.png +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/images/checkboxes.png +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/images/connect.b64 +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/images/connect.png +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/images/create_conf.png +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/images/data_error1_screenshot.jpg +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/images/entry_form.png +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/images/execsql_console.png +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/images/execsql_logo_01.png +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/images/fatals.png +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/images/logo_small.png +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/images/pause_terminal.png +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/images/pause_terminal_sm.b64 +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/images/pause_terminal_sm.png +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/images/prompt_compare.png +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/images/set_build_commands.jpg +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/images/unit_conversions.b64 +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/images/unit_conversions_029.png +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/images/unmatched.png +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/images/vim_execsql_highlight.png +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/index.md +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/reference/configuration.md +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/reference/security.md +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/docs/reference/substitution_vars.md +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/extras/plugin-template/README.md +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/extras/plugin-template/pyproject.toml +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/extras/plugin-template/src/execsql_plugin_YOURNAME/__init__.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/extras/plugin-template/tests/test_plugin.py.example +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/extras/vscode-execsql/README.md +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/extras/vscode-execsql/package.json +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/extras/vscode-execsql/syntaxes/execsql.tmLanguage.json +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/justfile +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/scripts/generate_vscode_grammar.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/__init__.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/__main__.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/api.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/cli/__init__.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/cli/dsn.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/cli/help.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/cli/lint.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/cli/run.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/config.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/data/__init__.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/data/execsql.conf.template +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/db/__init__.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/db/access.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/db/base.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/db/dsn.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/db/duckdb.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/db/firebird.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/db/mysql.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/db/oracle.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/db/sqlite.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/db/sqlserver.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/debug/__init__.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/debug/repl.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/exceptions.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/exporters/__init__.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/exporters/base.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/exporters/delimited.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/exporters/duckdb.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/exporters/feather.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/exporters/html.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/exporters/json.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/exporters/latex.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/exporters/markdown.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/exporters/ods.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/exporters/parquet.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/exporters/pretty.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/exporters/protocol.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/exporters/raw.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/exporters/sqlite.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/exporters/templates.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/exporters/values.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/exporters/xls.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/exporters/xlsx.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/exporters/xml.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/exporters/yaml.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/exporters/zip.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/format.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/gui/__init__.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/gui/base.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/gui/console.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/gui/desktop.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/gui/tui.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/importers/__init__.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/importers/base.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/importers/csv.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/importers/feather.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/importers/json.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/importers/ods.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/importers/xls.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/metacommands/__init__.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/metacommands/conditions.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/metacommands/connect.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/metacommands/control.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/metacommands/data.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/metacommands/debug.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/metacommands/dispatch.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/metacommands/io.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/metacommands/io_export.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/metacommands/io_fileops.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/metacommands/io_import.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/metacommands/io_write.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/metacommands/prompt.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/metacommands/script_ext.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/metacommands/system.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/metacommands/upsert.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/models.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/parser.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/plugins.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/py.typed +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/script/__init__.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/script/ast.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/script/control.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/script/engine.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/script/executor.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/script/parser.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/script/variables.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/state.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/types.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/utils/__init__.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/utils/auth.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/utils/crypto.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/utils/datetime.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/utils/errors.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/utils/fileio.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/utils/gui.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/utils/mail.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/utils/numeric.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/utils/regex.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/utils/strings.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/src/execsql/utils/timer.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/templates/README.md +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/templates/config_settings.sqlite +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/templates/example_config_prompt.sql +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/templates/make_config_db.sql +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/templates/md_compare.sql +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/templates/md_glossary.sql +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/templates/md_upsert.sql +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/templates/pg_compare.sql +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/templates/pg_glossary.sql +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/templates/pg_upsert.sql +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/templates/script_template.sql +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/templates/ss_compare.sql +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/templates/ss_glossary.sql +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/templates/ss_upsert.sql +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/__init__.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/cli/__init__.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/cli/test_cli.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/cli/test_cli_e2e.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/cli/test_cli_run.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/cli/test_lint.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/cli/test_ping.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/cli/test_profile.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/conftest.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/db/__init__.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/db/test_access_windows.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/db/test_base.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/db/test_db_adapters_mocked.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/db/test_db_adapters_mocked_extra.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/db/test_dsn.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/db/test_duckdb.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/db/test_factory.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/db/test_mysql_case_folding.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/db/test_mysql_inprocess.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/db/test_sqlite.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/db/test_sqlite_extra.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/db/test_sqlserver_inprocess.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/__init__.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_base.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_db.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_delimited.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_duckdb_exporter.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_exporters.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_feather.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_html_extended.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_html_latex.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_json.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_json_extended.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_latex_extended.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_markdown.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_ods.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_ods_export.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_parquet.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_pretty_extended.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_raw_extended.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_sqlite_exporter.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_templates.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_templates_extended.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_values_extended.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_xls_xlsx.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_xlsx.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_xml.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_yaml.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/exporters/test_zip.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/gui/__init__.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/gui/test_backends.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/gui/test_backends_extended.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/gui/test_compare_stats.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/gui/test_compute_row_diffs.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/gui/test_desktop_dialogs.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/gui/test_tui_pilot.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/gui/test_tui_pilot_complex.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/gui/test_utils_gui_extended.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/importers/__init__.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/importers/test_base_extended.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/importers/test_csv_edge_cases.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/importers/test_csv_importer.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/importers/test_feather_importer.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/importers/test_json_importer.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/importers/test_ods_importer.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/importers/test_xls_importer.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/integration/__init__.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/integration/conftest.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/integration/test_dsn.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/integration/test_duckdb.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/integration/test_mysql.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/integration/test_sqlite.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/metacommands/__init__.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/metacommands/test_assert.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/metacommands/test_breakpoint.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/metacommands/test_conditions_extra.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/metacommands/test_connect.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/metacommands/test_io_export.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/metacommands/test_io_import.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/metacommands/test_metacommands.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/metacommands/test_metacommands_connect.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/metacommands/test_metacommands_data.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/metacommands/test_metacommands_extended.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/metacommands/test_metacommands_fileops_extra.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/metacommands/test_metacommands_io.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/metacommands/test_metacommands_io_write_extra.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/metacommands/test_metacommands_script_ext.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/metacommands/test_metacommands_system.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/metacommands/test_metacommands_system_extra.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/metacommands/test_pg_upsert.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/metacommands/test_prompt.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/metacommands/test_row_count.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/metacommands/test_show_scripts.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/__init__.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/audit_lint_bad.sql +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/audit_smoke/sample.json +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/audit_smoke/sample.jsonl +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/audit_smoke.sql +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/config_runtime.sql +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/control_flow.sql +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/counters_and_locals.sql +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/error_handling.sql +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/import_options/has_comments.csv +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/import_options.sql +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/includes/helper.sql +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/includes.sql +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/io_formats.sql +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/io_roundtrip.sql +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/multi_db.sql +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/parquet_feather_roundtrip.sql +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/parse_only/parse_tree.sql +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/predicates.sql +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/scripts.sql +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/smoke.sql +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/subroutine_loops.sql +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/subvars_advanced.sql +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/template_export/greeting.tmpl +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/template_export.sql +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/timer.sql +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/transactions.sql +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/write_create_table.sql +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/fixtures/xlsx_ods_roundtrip.sql +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/scripts/test_sql_scripts.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/security/__init__.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/security/test_env_detection.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/security/test_expansion_bomb.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/security/test_import_regex_hardening.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/security/test_log_redaction.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/security/test_odbc_injection.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/security/test_path_containment.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/security/test_substitution_injection.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/security/test_zip_bomb.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_api.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_api_extended.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_ast.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_ast_parser.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_bundled_templates.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_config.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_config_data.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_config_extended.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_debug_repl.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_engine.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_error_messages.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_exceptions.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_executor.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_executor_inprocess.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_format.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_mail.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_models.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_package.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_parser.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_parser_params.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_plugins.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_registry.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_script.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_state.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/test_types.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/utils/__init__.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/utils/test_auth.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/utils/test_auth_extra.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/utils/test_crypto.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/utils/test_datetime.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/utils/test_encodedfile_context.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/utils/test_errors.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/utils/test_errors_extra.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/utils/test_fileio.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/utils/test_fileio_extra.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/utils/test_numeric.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/utils/test_regex.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/utils/test_strings.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/utils/test_timer.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/tests/utils/test_timer_extra.py +0 -0
- {execsql2-2.20.0 → execsql2-2.21.1}/zensical.toml +0 -0
|
@@ -86,7 +86,7 @@ jobs:
|
|
|
86
86
|
tox -e py
|
|
87
87
|
- name: Upload coverage to Codecov
|
|
88
88
|
if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.13'
|
|
89
|
-
uses: codecov/codecov-action@
|
|
89
|
+
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
|
90
90
|
with:
|
|
91
91
|
token: ${{ secrets.CODECOV_TOKEN }}
|
|
92
92
|
files: coverage.xml
|
|
@@ -167,7 +167,7 @@ jobs:
|
|
|
167
167
|
run: |
|
|
168
168
|
python -m pytest --cov=execsql --cov-branch --cov-report=xml --cov-fail-under=86
|
|
169
169
|
- name: Upload coverage to Codecov
|
|
170
|
-
uses: codecov/codecov-action@
|
|
170
|
+
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
|
171
171
|
with:
|
|
172
172
|
token: ${{ secrets.CODECOV_TOKEN }}
|
|
173
173
|
files: coverage.xml
|
|
@@ -13,6 +13,22 @@ ______________________________________________________________________
|
|
|
13
13
|
|
|
14
14
|
______________________________________________________________________
|
|
15
15
|
|
|
16
|
+
## [2.21.1] - 2026-06-24
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- PostgreSQL: `EXPORT` and other repeated queries against objects a script drops/recreates or alters no longer fail with `FeatureNotSupported: cached plan must not change result type`. psycopg3's automatic server-side prepared statements are now disabled (`prepare_threshold=None`), restoring the psycopg2 behavior.
|
|
21
|
+
|
|
22
|
+
______________________________________________________________________
|
|
23
|
+
|
|
24
|
+
## [2.21.0] - 2026-06-24
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- PostgreSQL connections now use psycopg3 (install the `postgres` extra, which pulls `psycopg[binary]`) instead of psycopg2. The `PG_UPSERT` metacommand now requires `pg-upsert>=1.23.0`.
|
|
29
|
+
|
|
30
|
+
______________________________________________________________________
|
|
31
|
+
|
|
16
32
|
## [2.20.0] - 2026-06-11
|
|
17
33
|
|
|
18
34
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: execsql2
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.21.1
|
|
4
4
|
Summary: Runs a SQL script against a PostgreSQL, SQLite, MariaDB/MySQL, DuckDB, Firebird, MS-Access, MS-SQL-Server, or Oracle database, or an ODBC DSN. Provides metacommands to import and export data, copy data between databases, conditionally execute SQL and metacommands, and dynamically alter SQL and metacommands with substitution variables.
|
|
5
5
|
Project-URL: Homepage, https://execsql2.readthedocs.io
|
|
6
6
|
Project-URL: Repository, https://github.com/geocoug/execsql
|
|
@@ -52,9 +52,9 @@ Requires-Dist: keyring>=25.0; extra == 'all'
|
|
|
52
52
|
Requires-Dist: odfpy>=1.4; extra == 'all'
|
|
53
53
|
Requires-Dist: openpyxl>=3.1; extra == 'all'
|
|
54
54
|
Requires-Dist: oracledb>=3.0; extra == 'all'
|
|
55
|
-
Requires-Dist: pg-upsert>=1.
|
|
55
|
+
Requires-Dist: pg-upsert>=1.23.0; extra == 'all'
|
|
56
56
|
Requires-Dist: polars>=1.0; extra == 'all'
|
|
57
|
-
Requires-Dist:
|
|
57
|
+
Requires-Dist: psycopg[binary]<4,>=3.1; extra == 'all'
|
|
58
58
|
Requires-Dist: pymysql>=1.1; extra == 'all'
|
|
59
59
|
Requires-Dist: pyodbc>=5.0; extra == 'all'
|
|
60
60
|
Requires-Dist: pyyaml>=6.0; extra == 'all'
|
|
@@ -66,7 +66,7 @@ Provides-Extra: all-db
|
|
|
66
66
|
Requires-Dist: duckdb>=1.0; extra == 'all-db'
|
|
67
67
|
Requires-Dist: firebird-driver>=1.10; extra == 'all-db'
|
|
68
68
|
Requires-Dist: oracledb>=3.0; extra == 'all-db'
|
|
69
|
-
Requires-Dist:
|
|
69
|
+
Requires-Dist: psycopg[binary]<4,>=3.1; extra == 'all-db'
|
|
70
70
|
Requires-Dist: pymysql>=1.1; extra == 'all-db'
|
|
71
71
|
Requires-Dist: pyodbc>=5.0; extra == 'all-db'
|
|
72
72
|
Provides-Extra: auth
|
|
@@ -124,9 +124,9 @@ Requires-Dist: pyodbc>=5.0; extra == 'odbc'
|
|
|
124
124
|
Provides-Extra: oracle
|
|
125
125
|
Requires-Dist: oracledb>=3.0; extra == 'oracle'
|
|
126
126
|
Provides-Extra: postgres
|
|
127
|
-
Requires-Dist:
|
|
127
|
+
Requires-Dist: psycopg[binary]<4,>=3.1; extra == 'postgres'
|
|
128
128
|
Provides-Extra: upsert
|
|
129
|
-
Requires-Dist: pg-upsert>=1.
|
|
129
|
+
Requires-Dist: pg-upsert>=1.23.0; extra == 'upsert'
|
|
130
130
|
Description-Content-Type: text/markdown
|
|
131
131
|
|
|
132
132
|
> [!NOTE]
|
|
@@ -403,7 +403,7 @@ execsql-format --no-sql --in-place scripts/
|
|
|
403
403
|
```yaml
|
|
404
404
|
repos:
|
|
405
405
|
- repo: https://github.com/geocoug/execsql
|
|
406
|
-
rev: v2.
|
|
406
|
+
rev: v2.21.1
|
|
407
407
|
hooks:
|
|
408
408
|
- id: execsql-format
|
|
409
409
|
```
|
|
@@ -210,6 +210,7 @@ All 33 mutable runtime globals in `state.py` have been consolidated into a `Runt
|
|
|
210
210
|
|
|
211
211
|
### Database Adapters
|
|
212
212
|
|
|
213
|
+
- **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.23.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`.
|
|
213
214
|
- **`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.
|
|
214
215
|
- **Connection timeouts** — PostgreSQL and SQLite adapters accept a connection timeout parameter (default 30 seconds).
|
|
215
216
|
- **DuckDB temporal types** — `TIMESTAMPTZ`, `TIMESTAMP`, `DATE`, `TIME` now map to native DuckDB types instead of `TEXT`.
|
|
@@ -59,7 +59,7 @@ class MyDBDatabase(Database):
|
|
|
59
59
|
self.need_passwd = False
|
|
60
60
|
self.encoding = "UTF-8"
|
|
61
61
|
self.encode_commands = False
|
|
62
|
-
self.paramstr = "?" # placeholder style: "?" for most drivers, "%s" for
|
|
62
|
+
self.paramstr = "?" # placeholder style: "?" for most drivers, "%s" for psycopg
|
|
63
63
|
self.conn = None
|
|
64
64
|
self.autocommit = True
|
|
65
65
|
self.open_db()
|
|
@@ -137,7 +137,7 @@ These are the instance attributes and methods you must configure correctly:
|
|
|
137
137
|
| Attribute / Method | Type | Purpose |
|
|
138
138
|
| ---------------------- | ----------------- | --------------------------------------------------------------------------------- |
|
|
139
139
|
| `self.type` | `DbType` | DBMS type token (e.g., `dbt_sqlite`). Controls quoting and type-mapping. |
|
|
140
|
-
| `self.paramstr` | `str` | SQL parameter placeholder: `"?"` (most drivers) or `"%s"` (
|
|
140
|
+
| `self.paramstr` | `str` | SQL parameter placeholder: `"?"` (most drivers) or `"%s"` (psycopg, PyMySQL). |
|
|
141
141
|
| `self.encoding` | `str` | Database character encoding. Detect from the database on connect if possible. |
|
|
142
142
|
| `self.encode_commands` | `bool` | `True` if SQL strings should be encoded before passing to the driver. |
|
|
143
143
|
| `self.autocommit` | `bool` | `True` means the driver commits automatically; `False` requires explicit commits. |
|
|
@@ -38,16 +38,16 @@ The specific libraries installed by each extra are:
|
|
|
38
38
|
|
|
39
39
|
### Database drivers
|
|
40
40
|
|
|
41
|
-
| Database / Format | Extra | Library
|
|
42
|
-
| ----------------- | ---------- |
|
|
43
|
-
| PostgreSQL | `postgres` | [
|
|
44
|
-
| MySQL / MariaDB | `mysql` | [pymysql](https://pypi.org/project/PyMySQL/)
|
|
45
|
-
| MS SQL Server | `mssql` | [pyodbc](https://pypi.org/project/pyodbc/)
|
|
46
|
-
| DuckDB | `duckdb` | [duckdb](https://pypi.org/project/duckdb/)
|
|
47
|
-
| Firebird | `firebird` | [firebird-driver](https://pypi.org/project/firebird-driver/)
|
|
48
|
-
| Oracle | `oracle` | [oracledb](https://pypi.org/project/oracledb/)
|
|
49
|
-
| ODBC DSN | `odbc` | [pyodbc](https://pypi.org/project/pyodbc/)
|
|
50
|
-
| SQLite | — | Built-in (`sqlite3` standard library)
|
|
41
|
+
| Database / Format | Extra | Library |
|
|
42
|
+
| ----------------- | ---------- | --------------------------------------------------------------- |
|
|
43
|
+
| PostgreSQL | `postgres` | [psycopg[binary]](https://pypi.org/project/psycopg/) (psycopg3) |
|
|
44
|
+
| MySQL / MariaDB | `mysql` | [pymysql](https://pypi.org/project/PyMySQL/) |
|
|
45
|
+
| MS SQL Server | `mssql` | [pyodbc](https://pypi.org/project/pyodbc/) |
|
|
46
|
+
| DuckDB | `duckdb` | [duckdb](https://pypi.org/project/duckdb/) |
|
|
47
|
+
| Firebird | `firebird` | [firebird-driver](https://pypi.org/project/firebird-driver/) |
|
|
48
|
+
| Oracle | `oracle` | [oracledb](https://pypi.org/project/oracledb/) |
|
|
49
|
+
| ODBC DSN | `odbc` | [pyodbc](https://pypi.org/project/pyodbc/) |
|
|
50
|
+
| SQLite | — | Built-in (`sqlite3` standard library) |
|
|
51
51
|
|
|
52
52
|
### `formats` bundle
|
|
53
53
|
|
|
@@ -82,7 +82,7 @@ When a DSN is used as a data source, *execsql* has no information about the feat
|
|
|
82
82
|
|
|
83
83
|
## PostgreSQL-Compatible Databases
|
|
84
84
|
|
|
85
|
-
Amazon [Redshift](https://docs.aws.amazon.com/redshift/index.html) is built on PostgreSQL 8.0.3 and [CockroachDB](https://www.cockroachlabs.com/) uses the *
|
|
85
|
+
Amazon [Redshift](https://docs.aws.amazon.com/redshift/index.html) is built on PostgreSQL 8.0.3 and [CockroachDB](https://www.cockroachlabs.com/) uses the *psycopg* library for connections from Python. Both of these databases can therefore potentially be used with *execsql* by specifying the database type as Postgres (i.e., db_type=p).
|
|
86
86
|
|
|
87
87
|
The following Postgres-specific features that are used by *execsql* may function differently or not at all in other Postgres-compatible databases:
|
|
88
88
|
|
|
@@ -2368,7 +2368,7 @@ For the full list of temporary objects and their schemas, see the [pg-upsert Tem
|
|
|
2368
2368
|
PG_VACUUM <vacuum arguments>
|
|
2369
2369
|
```
|
|
2370
2370
|
|
|
2371
|
-
Runs the 'vacuum' command on the current database if the current DBMS is Postgres. The 'vacuum' command will not execute successfully as a SQL command because it requires a change in the configuration of the (
|
|
2371
|
+
Runs the 'vacuum' command on the current database if the current DBMS is Postgres. The 'vacuum' command will not execute successfully as a SQL command because it requires a change in the configuration of the (psycopg) connection. This metacommand makes that change, runs the 'vacuum' metacommand, and restores the connection configuration to its default setting.
|
|
2372
2372
|
|
|
2373
2373
|
This metacommand has no effect if the current DBMS is not PostgreSQL.
|
|
2374
2374
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "execsql2"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.21.1"
|
|
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" }
|
|
@@ -48,7 +48,7 @@ dependencies = [
|
|
|
48
48
|
|
|
49
49
|
[project.optional-dependencies]
|
|
50
50
|
# Database drivers — conservative floors matching uv.lock; patches allowed.
|
|
51
|
-
postgres = ["
|
|
51
|
+
postgres = ["psycopg[binary]>=3.1,<4"]
|
|
52
52
|
mysql = ["pymysql>=1.1"]
|
|
53
53
|
mssql = ["pyodbc>=5.0"]
|
|
54
54
|
duckdb = ["duckdb>=1.0"]
|
|
@@ -69,12 +69,12 @@ formats = [
|
|
|
69
69
|
auth = ["keyring>=25.0"]
|
|
70
70
|
auth-plaintext = ["keyring>=25.0", "keyrings.alt>=5.0"]
|
|
71
71
|
auth-encrypted = ["keyring>=25.0", "keyrings.alt>=5.0", "pycryptodome>=3.20"]
|
|
72
|
-
upsert = ["pg-upsert>=1.
|
|
72
|
+
upsert = ["pg-upsert>=1.23.0"]
|
|
73
73
|
map = ["tkintermapview>=1.29"]
|
|
74
74
|
formatter = ["sqlglot>=25.0"]
|
|
75
75
|
# Convenience groups
|
|
76
76
|
all-db = [
|
|
77
|
-
"
|
|
77
|
+
"psycopg[binary]>=3.1,<4",
|
|
78
78
|
"pymysql>=1.1",
|
|
79
79
|
"pyodbc>=5.0",
|
|
80
80
|
"duckdb>=1.0",
|
|
@@ -178,7 +178,7 @@ skip-magic-trailing-comma = false
|
|
|
178
178
|
line-ending = "auto"
|
|
179
179
|
|
|
180
180
|
[tool.bumpversion]
|
|
181
|
-
current_version = "2.
|
|
181
|
+
current_version = "2.21.1"
|
|
182
182
|
commit = true
|
|
183
183
|
tag = true
|
|
184
184
|
tag_name = "v{new_version}"
|
|
@@ -63,7 +63,7 @@ def db_Postgres(
|
|
|
63
63
|
new_db: bool = False,
|
|
64
64
|
password: str | None = None,
|
|
65
65
|
) -> PostgresDatabase:
|
|
66
|
-
"""Open a new PostgreSQL connection via
|
|
66
|
+
"""Open a new PostgreSQL connection via psycopg (psycopg3)."""
|
|
67
67
|
return PostgresDatabase(server_name, database_name, user, pw_needed, port, new_db=new_db, password=password)
|
|
68
68
|
|
|
69
69
|
|
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
"""
|
|
4
4
|
PostgreSQL database adapter for execsql.
|
|
5
5
|
|
|
6
|
-
Implements :class:`PostgresDatabase`. Uses ``
|
|
6
|
+
Implements :class:`PostgresDatabase`. Uses ``psycopg`` (psycopg3) for the
|
|
7
7
|
connection, supports schema-qualified tables, server-side ``COPY`` for
|
|
8
8
|
fast IMPORT, ``CREATE DATABASE`` when ``new_db=True``, ``ROLE_EXISTS``,
|
|
9
9
|
and the ``PG_VACUUM`` metacommand (``vacuum()`` method). Corresponds to
|
|
@@ -26,7 +26,7 @@ DEFAULT_CONNECT_TIMEOUT = 30 # seconds
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
class PostgresDatabase(Database):
|
|
29
|
-
"""PostgreSQL adapter using
|
|
29
|
+
"""PostgreSQL adapter using psycopg (psycopg3), with schema support, server-side COPY, and keyring auth."""
|
|
30
30
|
|
|
31
31
|
def __init__(
|
|
32
32
|
self,
|
|
@@ -41,10 +41,11 @@ class PostgresDatabase(Database):
|
|
|
41
41
|
connect_timeout: int = DEFAULT_CONNECT_TIMEOUT,
|
|
42
42
|
) -> None:
|
|
43
43
|
try:
|
|
44
|
-
import
|
|
44
|
+
import psycopg # noqa: F401
|
|
45
45
|
except Exception:
|
|
46
46
|
fatal_error(
|
|
47
|
-
"The
|
|
47
|
+
"The psycopg module (psycopg3) is required to connect to PostgreSQL. "
|
|
48
|
+
"See https://www.psycopg.org/psycopg3/",
|
|
48
49
|
)
|
|
49
50
|
from execsql.types import dbt_postgres
|
|
50
51
|
|
|
@@ -73,25 +74,33 @@ class PostgresDatabase(Database):
|
|
|
73
74
|
|
|
74
75
|
def open_db(self) -> None:
|
|
75
76
|
"""Open a connection to the PostgreSQL database."""
|
|
76
|
-
import
|
|
77
|
+
import psycopg
|
|
77
78
|
|
|
78
79
|
def db_conn(db: PostgresDatabase, db_name: str):
|
|
79
80
|
try:
|
|
81
|
+
# prepare_threshold=None disables psycopg3's automatic server-side
|
|
82
|
+
# prepared statements. execsql re-runs the same query text (e.g. via
|
|
83
|
+
# EXPORT) against objects that scripts may drop/recreate or alter
|
|
84
|
+
# between runs; a cached plan whose result type then changes triggers
|
|
85
|
+
# PostgreSQL's "cached plan must not change result type" error. psycopg2
|
|
86
|
+
# never auto-prepared, so this preserves backwards-compatible behavior.
|
|
80
87
|
if db.user and db.password:
|
|
81
|
-
return
|
|
88
|
+
return psycopg.connect(
|
|
82
89
|
host=str(db.server_name),
|
|
83
|
-
|
|
90
|
+
dbname=str(db_name),
|
|
84
91
|
port=db.port,
|
|
85
92
|
user=db.user,
|
|
86
93
|
password=db.password,
|
|
87
94
|
connect_timeout=db.connect_timeout,
|
|
95
|
+
prepare_threshold=None,
|
|
88
96
|
)
|
|
89
97
|
else:
|
|
90
|
-
return
|
|
98
|
+
return psycopg.connect(
|
|
91
99
|
host=str(db.server_name),
|
|
92
|
-
|
|
100
|
+
dbname=db_name,
|
|
93
101
|
port=db.port,
|
|
94
102
|
connect_timeout=db.connect_timeout,
|
|
103
|
+
prepare_threshold=None,
|
|
95
104
|
)
|
|
96
105
|
except Exception as e:
|
|
97
106
|
msg = (
|
|
@@ -148,7 +157,7 @@ class PostgresDatabase(Database):
|
|
|
148
157
|
msg = f"Failed to open PostgreSQL database {self.db_name} on {self.server_name}"
|
|
149
158
|
raise ErrInfo(type="exception", exception_msg=exception_desc(), other_msg=msg) from e
|
|
150
159
|
# (Re)set the encoding to match the database.
|
|
151
|
-
self.encoding = self.conn.encoding
|
|
160
|
+
self.encoding = self.conn.info.encoding
|
|
152
161
|
|
|
153
162
|
def exec_cmd(self, querycommand: str) -> None:
|
|
154
163
|
"""Execute a stored function by name."""
|
|
@@ -242,13 +251,13 @@ class PostgresDatabase(Database):
|
|
|
242
251
|
but should not be exposed to untrusted input.
|
|
243
252
|
"""
|
|
244
253
|
self.commit()
|
|
245
|
-
self.conn.
|
|
254
|
+
self.conn.autocommit = True
|
|
246
255
|
curs = self.conn.cursor()
|
|
247
256
|
try:
|
|
248
257
|
curs.execute(f"VACUUM {argstring};")
|
|
249
258
|
finally:
|
|
250
259
|
curs.close()
|
|
251
|
-
self.conn.
|
|
260
|
+
self.conn.autocommit = False
|
|
252
261
|
|
|
253
262
|
def import_tabular_file(
|
|
254
263
|
self,
|
|
@@ -290,7 +299,7 @@ class PostgresDatabase(Database):
|
|
|
290
299
|
import_cols = [self.type.quoted(col) for col in import_cols]
|
|
291
300
|
csv_file_cols_q = [self.type.quoted(col) for col in csv_file_cols]
|
|
292
301
|
input_col_list = ",".join(import_cols)
|
|
293
|
-
# If encodings match, use
|
|
302
|
+
# If encodings match, use server-side COPY.
|
|
294
303
|
# If encodings don't match, and the file encoding isn't recognized by CSV, read as CSV.
|
|
295
304
|
enc_xlates = {
|
|
296
305
|
"cp1252": "win1252",
|
|
@@ -319,7 +328,7 @@ class PostgresDatabase(Database):
|
|
|
319
328
|
and not _state.conf.trim_strings
|
|
320
329
|
and not _state.conf.replace_newlines
|
|
321
330
|
):
|
|
322
|
-
# Use Postgres' COPY FROM method via
|
|
331
|
+
# Use Postgres' COPY FROM method via psycopg3's cursor.copy() context manager.
|
|
323
332
|
rf = csv_file_obj.open("rt")
|
|
324
333
|
if skipheader:
|
|
325
334
|
next(rf)
|
|
@@ -346,7 +355,9 @@ class PostgresDatabase(Database):
|
|
|
346
355
|
)
|
|
347
356
|
with self._cursor() as curs:
|
|
348
357
|
try:
|
|
349
|
-
curs.
|
|
358
|
+
with curs.copy(copy_cmd) as copy:
|
|
359
|
+
while chunk := rf.read(_state.conf.import_buffer):
|
|
360
|
+
copy.write(chunk)
|
|
350
361
|
except ErrInfo:
|
|
351
362
|
raise
|
|
352
363
|
except Exception as e:
|
|
@@ -461,12 +472,11 @@ class PostgresDatabase(Database):
|
|
|
461
472
|
file_name: str,
|
|
462
473
|
) -> None:
|
|
463
474
|
"""Import an entire binary file into a single column of a table."""
|
|
464
|
-
import psycopg2
|
|
465
|
-
|
|
466
475
|
with open(file_name, "rb") as f:
|
|
467
476
|
filedata = f.read()
|
|
468
477
|
sq_name = self.schema_qualified_table_name(schema_name, table_name)
|
|
469
478
|
quoted_col = self.quote_identifier(column_name)
|
|
470
479
|
sql = f"insert into {sq_name} ({quoted_col}) values ({self.paramsubs(1)});"
|
|
471
480
|
with self._cursor() as curs:
|
|
472
|
-
|
|
481
|
+
# psycopg3 sends ``bytes`` to a ``bytea`` column directly; no Binary() wrapper.
|
|
482
|
+
curs.execute(sql, (filedata,))
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Tests for execsql.db.postgres — PostgresDatabase adapter.
|
|
3
3
|
|
|
4
|
-
These tests mock
|
|
5
|
-
server or the
|
|
6
|
-
connect_timeout parameter is correctly threaded through to
|
|
4
|
+
These tests mock psycopg (psycopg3) entirely so they can run without a
|
|
5
|
+
PostgreSQL server or the psycopg package installed. They verify that the
|
|
6
|
+
connect_timeout parameter is correctly threaded through to psycopg.connect(),
|
|
7
|
+
and that the libpq ``dbname`` keyword (not psycopg2's ``database``) is used.
|
|
7
8
|
"""
|
|
8
9
|
|
|
9
10
|
from __future__ import annotations
|
|
@@ -19,11 +20,11 @@ from execsql.db.postgres import DEFAULT_CONNECT_TIMEOUT
|
|
|
19
20
|
# ---------------------------------------------------------------------------
|
|
20
21
|
|
|
21
22
|
|
|
22
|
-
def
|
|
23
|
-
"""Return a mock
|
|
23
|
+
def _make_mock_psycopg():
|
|
24
|
+
"""Return a mock psycopg module whose connect() returns a usable conn."""
|
|
24
25
|
mock_mod = MagicMock()
|
|
25
26
|
mock_conn = MagicMock()
|
|
26
|
-
mock_conn.encoding = "UTF8"
|
|
27
|
+
mock_conn.info.encoding = "UTF8"
|
|
27
28
|
mock_mod.connect.return_value = mock_conn
|
|
28
29
|
return mock_mod
|
|
29
30
|
|
|
@@ -34,11 +35,11 @@ def _make_mock_psycopg2():
|
|
|
34
35
|
|
|
35
36
|
|
|
36
37
|
class TestPostgresConnectTimeout:
|
|
37
|
-
@patch.dict("sys.modules", {"
|
|
38
|
+
@patch.dict("sys.modules", {"psycopg": _make_mock_psycopg()})
|
|
38
39
|
def test_default_connect_timeout(self):
|
|
39
40
|
import sys
|
|
40
41
|
|
|
41
|
-
|
|
42
|
+
mock_psycopg = sys.modules["psycopg"]
|
|
42
43
|
from execsql.db.postgres import PostgresDatabase
|
|
43
44
|
|
|
44
45
|
db = PostgresDatabase(
|
|
@@ -48,15 +49,18 @@ class TestPostgresConnectTimeout:
|
|
|
48
49
|
password="pass",
|
|
49
50
|
)
|
|
50
51
|
assert db.connect_timeout == DEFAULT_CONNECT_TIMEOUT
|
|
51
|
-
# Verify
|
|
52
|
-
call_kwargs =
|
|
52
|
+
# Verify psycopg.connect was called with connect_timeout=30
|
|
53
|
+
call_kwargs = mock_psycopg.connect.call_args[1]
|
|
53
54
|
assert call_kwargs["connect_timeout"] == DEFAULT_CONNECT_TIMEOUT
|
|
55
|
+
# psycopg3 requires the libpq ``dbname`` keyword, not psycopg2's ``database``.
|
|
56
|
+
assert call_kwargs["dbname"] == "testdb"
|
|
57
|
+
assert "database" not in call_kwargs
|
|
54
58
|
|
|
55
|
-
@patch.dict("sys.modules", {"
|
|
59
|
+
@patch.dict("sys.modules", {"psycopg": _make_mock_psycopg()})
|
|
56
60
|
def test_custom_connect_timeout(self):
|
|
57
61
|
import sys
|
|
58
62
|
|
|
59
|
-
|
|
63
|
+
mock_psycopg = sys.modules["psycopg"]
|
|
60
64
|
from execsql.db.postgres import PostgresDatabase
|
|
61
65
|
|
|
62
66
|
db = PostgresDatabase(
|
|
@@ -67,14 +71,14 @@ class TestPostgresConnectTimeout:
|
|
|
67
71
|
connect_timeout=10,
|
|
68
72
|
)
|
|
69
73
|
assert db.connect_timeout == 10
|
|
70
|
-
call_kwargs =
|
|
74
|
+
call_kwargs = mock_psycopg.connect.call_args[1]
|
|
71
75
|
assert call_kwargs["connect_timeout"] == 10
|
|
72
76
|
|
|
73
|
-
@patch.dict("sys.modules", {"
|
|
77
|
+
@patch.dict("sys.modules", {"psycopg": _make_mock_psycopg()})
|
|
74
78
|
def test_connect_timeout_without_credentials(self):
|
|
75
79
|
import sys
|
|
76
80
|
|
|
77
|
-
|
|
81
|
+
mock_psycopg = sys.modules["psycopg"]
|
|
78
82
|
from execsql.db.postgres import PostgresDatabase
|
|
79
83
|
|
|
80
84
|
db = PostgresDatabase(
|
|
@@ -84,5 +88,8 @@ class TestPostgresConnectTimeout:
|
|
|
84
88
|
connect_timeout=5,
|
|
85
89
|
)
|
|
86
90
|
assert db.connect_timeout == 5
|
|
87
|
-
call_kwargs =
|
|
91
|
+
call_kwargs = mock_psycopg.connect.call_args[1]
|
|
88
92
|
assert call_kwargs["connect_timeout"] == 5
|
|
93
|
+
# Credential-less path still uses ``dbname``.
|
|
94
|
+
assert call_kwargs["dbname"] == "testdb"
|
|
95
|
+
assert "database" not in call_kwargs
|
|
@@ -7,7 +7,7 @@ subprocess* — subprocess execution yields no coverage credit, so those
|
|
|
7
7
|
~199 missed lines stay uncovered.
|
|
8
8
|
|
|
9
9
|
The module is skipped when:
|
|
10
|
-
-
|
|
10
|
+
- psycopg (psycopg3) is not installed, or
|
|
11
11
|
- the test PostgreSQL instance (localhost:5432, database=execsql_test,
|
|
12
12
|
user=execsql, password=execsql) is not reachable.
|
|
13
13
|
|
|
@@ -30,7 +30,7 @@ import execsql.state as _state
|
|
|
30
30
|
# Skip the entire module when the driver or server is unavailable
|
|
31
31
|
# ---------------------------------------------------------------------------
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
psycopg = pytest.importorskip("psycopg", reason="psycopg (psycopg3) not installed")
|
|
34
34
|
|
|
35
35
|
_PG_KW: dict = {
|
|
36
36
|
"host": os.environ.get("EXECSQL_PG_HOST", "localhost"),
|
|
@@ -44,7 +44,7 @@ _PG_KW: dict = {
|
|
|
44
44
|
|
|
45
45
|
def _pg_reachable() -> bool:
|
|
46
46
|
try:
|
|
47
|
-
|
|
47
|
+
psycopg.connect(**_PG_KW).close()
|
|
48
48
|
return True
|
|
49
49
|
except Exception:
|
|
50
50
|
return False
|
|
@@ -306,7 +306,7 @@ class TestDataAccess:
|
|
|
306
306
|
def test_select_data_bad_sql_raises(self, db):
|
|
307
307
|
# base.Database.select_data() re-raises the driver exception directly
|
|
308
308
|
# after rolling back; it does not wrap in ErrInfo.
|
|
309
|
-
with pytest.raises(
|
|
309
|
+
with pytest.raises(psycopg.Error):
|
|
310
310
|
db.select_data("SELECT * FROM execsql_no_such_table_xyz;")
|
|
311
311
|
|
|
312
312
|
def test_select_rowsource_iterates(self, db, fresh_table):
|
|
@@ -357,7 +357,7 @@ class TestExecCmd:
|
|
|
357
357
|
db.commit()
|
|
358
358
|
|
|
359
359
|
def test_exec_cmd_unknown_function_rolls_back(self, db):
|
|
360
|
-
with pytest.raises(
|
|
360
|
+
with pytest.raises(psycopg.Error):
|
|
361
361
|
db.exec_cmd("execsql_no_such_function_xyz")
|
|
362
362
|
|
|
363
363
|
|
|
@@ -6,7 +6,7 @@ the backend (db_type = p). Each test writes a minimal execsql.conf, creates a
|
|
|
6
6
|
outcomes (tables created, data inserted, files exported, etc.).
|
|
7
7
|
|
|
8
8
|
The entire module is skipped when:
|
|
9
|
-
-
|
|
9
|
+
- psycopg (psycopg3) is not installed, OR
|
|
10
10
|
- the test PostgreSQL instance (localhost:5432, database=execsql_test,
|
|
11
11
|
user=execsql, password=execsql) is not reachable.
|
|
12
12
|
"""
|
|
@@ -23,10 +23,10 @@ import pytest
|
|
|
23
23
|
from tests.integration.conftest import write_script
|
|
24
24
|
|
|
25
25
|
# ---------------------------------------------------------------------------
|
|
26
|
-
# Module-level skip:
|
|
26
|
+
# Module-level skip: psycopg (psycopg3) availability + server reachability
|
|
27
27
|
# ---------------------------------------------------------------------------
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
psycopg = pytest.importorskip("psycopg", reason="psycopg (psycopg3) package required")
|
|
30
30
|
|
|
31
31
|
_PG_CONNECT_KWARGS: dict = {
|
|
32
32
|
"host": os.environ.get("EXECSQL_PG_HOST", "localhost"),
|
|
@@ -41,7 +41,7 @@ _PG_CONNECT_KWARGS: dict = {
|
|
|
41
41
|
def _pg_is_reachable() -> bool:
|
|
42
42
|
"""Return True if the test PostgreSQL instance is connectable."""
|
|
43
43
|
try:
|
|
44
|
-
conn =
|
|
44
|
+
conn = psycopg.connect(**_PG_CONNECT_KWARGS)
|
|
45
45
|
conn.close()
|
|
46
46
|
return True
|
|
47
47
|
except Exception: # noqa: BLE001
|
|
@@ -101,7 +101,7 @@ def _run_execsql_pg(tmp_path, script_path, extra_args=None, timeout=30):
|
|
|
101
101
|
|
|
102
102
|
def _query_pg(sql: str, params=None):
|
|
103
103
|
"""Open a connection to the test PostgreSQL database, run *sql*, and return all rows."""
|
|
104
|
-
conn =
|
|
104
|
+
conn = psycopg.connect(**_PG_CONNECT_KWARGS)
|
|
105
105
|
try:
|
|
106
106
|
cur = conn.cursor()
|
|
107
107
|
cur.execute(sql, params)
|
|
@@ -112,7 +112,7 @@ def _query_pg(sql: str, params=None):
|
|
|
112
112
|
|
|
113
113
|
def _exec_pg(sql: str, params=None):
|
|
114
114
|
"""Execute a non-SELECT statement against the test PostgreSQL database."""
|
|
115
|
-
conn =
|
|
115
|
+
conn = psycopg.connect(**_PG_CONNECT_KWARGS)
|
|
116
116
|
try:
|
|
117
117
|
conn.autocommit = True
|
|
118
118
|
cur = conn.cursor()
|