execsql2 2.15.5__tar.gz → 2.15.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {execsql2-2.15.5 → execsql2-2.15.7}/CHANGELOG.md +32 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/PKG-INFO +1 -1
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/about/divergence.md +10 -1
- {execsql2-2.15.5 → execsql2-2.15.7}/pyproject.toml +2 -2
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/config.py +3 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/db/access.py +52 -51
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/db/base.py +8 -8
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/db/dsn.py +2 -1
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/db/duckdb.py +1 -1
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/db/firebird.py +56 -59
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/db/mysql.py +99 -97
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/db/oracle.py +63 -68
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/db/postgres.py +141 -134
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/db/sqlite.py +1 -1
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/db/sqlserver.py +11 -12
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/exceptions.py +3 -3
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/exporters/html.py +5 -3
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/exporters/json.py +7 -2
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/importers/csv.py +2 -2
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/metacommands/system.py +1 -2
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/models.py +0 -1
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/script/engine.py +2 -1
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/script/variables.py +45 -1
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/state.py +4 -1
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/utils/fileio.py +3 -2
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/db/test_duckdb.py +1 -1
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/db/test_sqlite_extra.py +7 -7
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/test_script.py +91 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/uv.lock +1 -1
- {execsql2-2.15.5 → execsql2-2.15.7}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/.github/workflows/ci-cd.yml +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/.gitignore +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/.pre-commit-config.yaml +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/.pre-commit-hooks.yaml +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/.python-version +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/.readthedocs.yaml +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/CONTRIBUTING.md +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/LICENSE.txt +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/NOTICE +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/README.md +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/SECURITY.md +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/about/contributors.md +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/about/copyright.md +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/api/cli.md +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/api/db.md +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/api/exporters.md +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/api/importers.md +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/api/index.md +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/api/metacommands.md +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/dev/adding_db_adapters.md +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/dev/adding_exporters.md +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/dev/adding_importers.md +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/dev/adding_metacommands.md +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/dev/architecture.md +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/getting-started/installation.md +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/getting-started/requirements.md +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/getting-started/syntax.md +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/guides/debugging.md +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/guides/documentation.md +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/guides/encoding.md +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/guides/examples.md +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/guides/formatter.md +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/guides/logging.md +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/guides/sql_syntax.md +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/guides/usage.md +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/guides/using_scripts.md +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/images/Compare_planets.png +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/images/actions.png +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/images/actions2.png +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/images/checkboxes.png +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/images/connect.b64 +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/images/connect.png +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/images/create_conf.png +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/images/data_error1_screenshot.jpg +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/images/entry_form.png +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/images/execsql_console.png +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/images/execsql_logo_01.png +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/images/fatals.png +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/images/logo_small.png +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/images/pause_terminal.png +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/images/pause_terminal_sm.b64 +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/images/pause_terminal_sm.png +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/images/prompt_compare.png +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/images/set_build_commands.jpg +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/images/unit_conversions.b64 +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/images/unit_conversions_029.png +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/images/unmatched.png +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/images/vim_execsql_highlight.png +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/index.md +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/reference/configuration.md +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/reference/metacommands.md +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/reference/security.md +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/docs/reference/substitution_vars.md +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/extras/vscode-execsql/README.md +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/extras/vscode-execsql/package.json +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/extras/vscode-execsql/syntaxes/execsql.tmLanguage.json +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/justfile +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/scripts/generate_vscode_grammar.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/__init__.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/__main__.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/cli/__init__.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/cli/dsn.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/cli/help.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/cli/lint.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/cli/run.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/db/__init__.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/db/factory.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/debug/__init__.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/debug/repl.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/exporters/__init__.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/exporters/base.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/exporters/delimited.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/exporters/duckdb.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/exporters/feather.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/exporters/latex.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/exporters/markdown.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/exporters/ods.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/exporters/parquet.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/exporters/pretty.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/exporters/protocol.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/exporters/raw.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/exporters/sqlite.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/exporters/templates.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/exporters/values.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/exporters/xls.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/exporters/xlsx.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/exporters/xml.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/exporters/yaml.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/exporters/zip.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/format.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/gui/__init__.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/gui/base.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/gui/console.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/gui/desktop.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/gui/tui.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/importers/__init__.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/importers/base.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/importers/feather.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/importers/json.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/importers/ods.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/importers/xls.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/metacommands/__init__.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/metacommands/conditions.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/metacommands/connect.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/metacommands/control.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/metacommands/data.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/metacommands/debug.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/metacommands/dispatch.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/metacommands/io.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/metacommands/io_export.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/metacommands/io_fileops.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/metacommands/io_import.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/metacommands/io_write.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/metacommands/prompt.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/metacommands/script_ext.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/metacommands/upsert.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/parser.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/py.typed +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/script/__init__.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/script/control.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/types.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/utils/__init__.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/utils/auth.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/utils/crypto.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/utils/datetime.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/utils/errors.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/utils/gui.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/utils/mail.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/utils/numeric.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/utils/regex.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/utils/strings.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/src/execsql/utils/timer.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/templates/README.md +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/templates/config_settings.sqlite +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/templates/example_config_prompt.sql +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/templates/execsql.conf +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/templates/make_config_db.sql +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/templates/md_compare.sql +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/templates/md_glossary.sql +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/templates/md_upsert.sql +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/templates/pg_compare.sql +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/templates/pg_glossary.sql +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/templates/pg_upsert.sql +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/templates/script_template.sql +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/templates/ss_compare.sql +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/templates/ss_glossary.sql +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/templates/ss_upsert.sql +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/__init__.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/cli/__init__.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/cli/test_cli.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/cli/test_cli_e2e.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/cli/test_cli_run.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/cli/test_lint.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/cli/test_ping.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/cli/test_profile.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/conftest.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/db/__init__.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/db/test_base.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/db/test_factory.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/db/test_postgres.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/db/test_sqlite.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/exporters/__init__.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/exporters/test_base.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/exporters/test_db.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/exporters/test_delimited.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/exporters/test_duckdb_exporter.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/exporters/test_exporters.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/exporters/test_feather.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/exporters/test_html_extended.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/exporters/test_html_latex.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/exporters/test_json.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/exporters/test_json_extended.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/exporters/test_latex_extended.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/exporters/test_markdown.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/exporters/test_ods.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/exporters/test_parquet.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/exporters/test_pretty_extended.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/exporters/test_raw_extended.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/exporters/test_sqlite_exporter.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/exporters/test_templates.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/exporters/test_templates_extended.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/exporters/test_values_extended.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/exporters/test_xls_xlsx.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/exporters/test_xlsx.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/exporters/test_xml.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/exporters/test_yaml.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/exporters/test_zip.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/gui/__init__.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/gui/test_backends.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/gui/test_compare_stats.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/gui/test_compute_row_diffs.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/importers/__init__.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/importers/test_base_extended.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/importers/test_csv_edge_cases.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/importers/test_csv_importer.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/importers/test_feather_importer.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/importers/test_json_importer.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/importers/test_ods_importer.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/importers/test_xls_importer.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/integration/__init__.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/integration/conftest.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/integration/test_dsn.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/integration/test_duckdb.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/integration/test_mysql.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/integration/test_postgres.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/integration/test_sqlite.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/metacommands/__init__.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/metacommands/test_assert.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/metacommands/test_breakpoint.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/metacommands/test_connect.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/metacommands/test_io_export.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/metacommands/test_io_import.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/metacommands/test_metacommands.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/metacommands/test_metacommands_connect.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/metacommands/test_metacommands_data.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/metacommands/test_metacommands_extended.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/metacommands/test_metacommands_fileops_extra.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/metacommands/test_metacommands_io.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/metacommands/test_metacommands_io_write_extra.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/metacommands/test_metacommands_script_ext.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/metacommands/test_metacommands_system.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/metacommands/test_metacommands_system_extra.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/metacommands/test_pg_upsert.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/metacommands/test_row_count.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/test_config.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/test_config_data.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/test_config_extended.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/test_debug_repl.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/test_engine.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/test_error_messages.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/test_exceptions.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/test_format.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/test_mail.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/test_models.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/test_package.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/test_parser.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/test_registry.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/test_state.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/test_types.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/utils/__init__.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/utils/test_auth.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/utils/test_auth_extra.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/utils/test_crypto.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/utils/test_datetime.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/utils/test_errors.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/utils/test_errors_extra.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/utils/test_fileio.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/utils/test_fileio_extra.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/utils/test_numeric.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/utils/test_regex.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/utils/test_strings.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/utils/test_timer.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/tests/utils/test_timer_extra.py +0 -0
- {execsql2-2.15.5 → execsql2-2.15.7}/zensical.toml +0 -0
|
@@ -13,6 +13,38 @@ ______________________________________________________________________
|
|
|
13
13
|
|
|
14
14
|
______________________________________________________________________
|
|
15
15
|
|
|
16
|
+
## [2.15.7] - 2026-04-20
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- `CounterVars.substitute` now correctly searches the full string. `re.I` was mistakenly passed as the `pos` argument to `re.search`, causing the first two characters of every string to be skipped when looking for counter variable references.
|
|
21
|
+
- `DataTypeError`, `DbTypeError`, and `DatabaseNotImplementedError` now call `super().__init__()` instead of bypassing the MRO with `Exception.__init__(self, ...)`, fixing `repr()` crashes for these exception types.
|
|
22
|
+
- SQL injection in MySQL `LOAD DATA INFILE`: file path, field delimiter, and quote character are now escaped before being interpolated into the import SQL statement.
|
|
23
|
+
- SQLite and DuckDB `exec_cmd` no longer encodes the SQL string to bytes before passing it to `execute()`, which always raised `TypeError` in Python 3.
|
|
24
|
+
- Substitution variable token matching in `_substitute_nested` now uses `str.find()` on a lower-cased copy of the string instead of compiling a new regex per variable per call, eliminating unnecessary regex compilation on every substitution.
|
|
25
|
+
- Cursor leaks across all database adapters (PostgreSQL, MySQL, SQLite, DuckDB, Firebird, Access, SQL Server, Oracle) — call sites that manually opened cursors now use the `with self._cursor()` context manager so cursors are always closed, including on exceptions.
|
|
26
|
+
- `__delattr__` in `state.py` now instantiates a fresh `RuntimeContext()` when resetting an attribute to its default, rather than reading from a cached `_DEFAULT_CTX` instance. This prevents mutable defaults (lists, dicts) from being shared across resets.
|
|
27
|
+
- `cmds_run` counter no longer overcounts: the `StopIteration` branch in `runscripts()` now calls `continue` after popping the command list stack, preventing the increment that follows from executing.
|
|
28
|
+
- Config file chaining is now capped at 20 files to prevent an infinite loop when `config_file` entries form a cycle.
|
|
29
|
+
- Temp file creation in `TempFileMgr` now uses `tempfile.mkstemp()` instead of `tempfile.NamedTemporaryFile().name`, eliminating the TOCTOU race where another process could claim the name between creation and use.
|
|
30
|
+
- JSON export now serializes column names with `json.dumps()` instead of bare f-string interpolation, preventing malformed JSON when column names contain quotes, backslashes, or other special characters.
|
|
31
|
+
- PostgreSQL `VACUUM` autocommit session state is now restored in a `finally` block, ensuring the connection returns to non-autocommit mode even if the vacuum statement raises an exception.
|
|
32
|
+
- HTML export now HTML-escapes the description, author, and CSS href meta tag values, preventing malformed HTML when these values contain `<`, `>`, `"`, or `&` characters.
|
|
33
|
+
- `shlex.split` on Windows is now called with `posix=False` instead of pre-escaping backslashes, which produced incorrect splits for paths with consecutive backslashes.
|
|
34
|
+
- Duplicate `JsonDatatype.integer = "integer"` assignment in `models.py` removed; `JsonDatatype.number` is the correct attribute and was already present.
|
|
35
|
+
- MySQL adapter constructor no longer coerces `None` arguments to the string `"None"` for `server_name`, `db_name`, and `user_name`; `None` values are now preserved as `None`.
|
|
36
|
+
- `importfile()` parameter renamed from `columname` to `column_name`, matching the internal variable name used throughout the function body.
|
|
37
|
+
|
|
38
|
+
______________________________________________________________________
|
|
39
|
+
|
|
40
|
+
## [2.15.6] - 2026-04-16
|
|
41
|
+
|
|
42
|
+
### Fixed
|
|
43
|
+
|
|
44
|
+
- Nested substitution variable names (e.g., `!!N_!!CHECK_GROUP!!_CHECKS!!`) now resolve correctly, matching original execsql behavior. The single-pass token regex introduced in 2.15.0 could not find inner `!!var!!` tokens embedded within an outer variable name; a per-variable substring fallback now handles this edge case.
|
|
45
|
+
|
|
46
|
+
______________________________________________________________________
|
|
47
|
+
|
|
16
48
|
## [2.15.5] - 2026-04-15
|
|
17
49
|
|
|
18
50
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: execsql2
|
|
3
|
-
Version: 2.15.
|
|
3
|
+
Version: 2.15.7
|
|
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
|
|
@@ -210,6 +210,7 @@ These are behavioral changes driven by security or correctness issues in the ups
|
|
|
210
210
|
| Database metadata queries | `schema_exists()`, `table_exists()`, `column_exists()`, `table_columns()`, `view_exists()`, `role_exists()` across all 9 adapters now use parameterized queries. Upstream used string interpolation. |
|
|
211
211
|
| `import_entire_file()` | Column names are quoted with `quote_identifier()` instead of interpolated into INSERT statements. |
|
|
212
212
|
| PostgreSQL `CREATE DATABASE` | Database name and encoding are quoted. COPY delimiter and quote character are validated. |
|
|
213
|
+
| MySQL `LOAD DATA INFILE` | File path, delimiter, and quotechar are now escaped with `replace("'", "''")` before interpolation into the SQL statement. |
|
|
213
214
|
| `$SHEETS_TABLES_VALUES` | Sheet names from ODS/XLS imports are escaped before embedding in SQL. |
|
|
214
215
|
| HTTP `Content-Disposition` | Filename is sanitized to prevent HTTP response splitting in SERVE. |
|
|
215
216
|
|
|
@@ -220,7 +221,7 @@ These are behavioral changes driven by security or correctness issues in the ups
|
|
|
220
221
|
| Jinja2 sandboxing | Templates run in `SandboxedEnvironment` instead of the default `jinja2.Template`. |
|
|
221
222
|
| HTML export | Column headers and cell values are escaped with `html.escape()` to prevent XSS. |
|
|
222
223
|
| XML export | Values are escaped with `xml.sax.saxutils.escape()`. Invalid XML element name characters are replaced. |
|
|
223
|
-
| JSON export | The `description` field
|
|
224
|
+
| JSON export | The `description` field and all column names use `json.dumps()` instead of string interpolation. |
|
|
224
225
|
|
|
225
226
|
### Credential and Logging Safety
|
|
226
227
|
|
|
@@ -254,6 +255,14 @@ These are behavioral changes driven by security or correctness issues in the ups
|
|
|
254
255
|
| `NumericParser` division by zero | `NumericAstNode.eval()` raised unhandled `ZeroDivisionError`. Now raises `NumericParserError` with a clear message. Inherited from upstream. |
|
|
255
256
|
| `CondAstNode.eval()` could return `None` | Missing fallthrough for unknown node types silently returned `None`. Now raises `CondParserError`. Inherited from upstream. |
|
|
256
257
|
| `DT_Timestamp` claims time-only values | `dateutil.parser.parse()` silently fills in today's date for bare time strings like `"13:15:45"`, so `DT_Timestamp` matched before `DT_Time` in the inference order. `parse_datetime()` now rejects time-only strings. Inherited from upstream. |
|
|
258
|
+
| `CounterVars.substitute` skipped pos 0–1 | `re.I` was passed as the positional `pos` argument, skipping the first 2 characters of every string during counter variable expansion. Counter variables at the very start of a line were never matched. Inherited from upstream. |
|
|
259
|
+
| `exec_cmd` (SQLite/DuckDB) always raised TypeError | Passed bytes to `curs.execute()` via `.encode()`, which Python 3 `sqlite3`/`duckdb` reject. `EXECUTE PROCEDURE` metacommand was non-functional on these backends. Inherited from upstream. |
|
|
260
|
+
| MySQL `LOAD DATA INFILE` injection | File path, delimiter, and quotechar were interpolated without escaping. Now single-quotes are escaped consistent with the PostgreSQL COPY path. Inherited from upstream. |
|
|
261
|
+
| Config file chain infinite loop | The `config_file` directive could chain config files without limit. A circular reference (via symlinks or different relative paths) caused an infinite loop at startup. Now capped at 20 files. Inherited from upstream. |
|
|
262
|
+
| Cursor leaks in database adapters | ~15 methods across all adapters used `curs = self.cursor()` / `curs.close()` without `try/finally`. If the query raised, the cursor leaked. Converted to `with self._cursor() as curs:`. Inherited from upstream. |
|
|
263
|
+
| JSON export malformed on special column names | Column names containing `"` or `\` produced invalid JSON. Now uses `json.dumps()` for all field names. Inherited from upstream. |
|
|
264
|
+
| Temp file creation TOCTOU race | `TempFileMgr.new_temp_fn()` discarded the `NamedTemporaryFile` handle, creating a race window. Now uses `tempfile.mkstemp()` for secure creation. Inherited from upstream. |
|
|
265
|
+
| `shlex.split` on Windows incorrect mode | Called without `posix=False` on Windows, mishandling backslash-heavy paths in SHELL commands. Inherited from upstream. |
|
|
257
266
|
|
|
258
267
|
______________________________________________________________________
|
|
259
268
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "execsql2"
|
|
7
|
-
version = "2.15.
|
|
7
|
+
version = "2.15.7"
|
|
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" }
|
|
@@ -164,7 +164,7 @@ skip-magic-trailing-comma = false
|
|
|
164
164
|
line-ending = "auto"
|
|
165
165
|
|
|
166
166
|
[tool.bumpversion]
|
|
167
|
-
current_version = "2.15.
|
|
167
|
+
current_version = "2.15.7"
|
|
168
168
|
commit = true
|
|
169
169
|
commit_args = "--no-verify"
|
|
170
170
|
tag = true
|
|
@@ -290,8 +290,11 @@ class ConfigData:
|
|
|
290
290
|
config_files = [sys_config_file, user_config_file, script_config_file, startdir_config_file]
|
|
291
291
|
else:
|
|
292
292
|
config_files = [sys_config_file, user_config_file, startdir_config_file]
|
|
293
|
+
_MAX_CONFIG_CHAIN = 20 # Guard against circular config_file references.
|
|
293
294
|
self.files_read: list = []
|
|
294
295
|
for ix, configfile in enumerate(config_files):
|
|
296
|
+
if len(self.files_read) >= _MAX_CONFIG_CHAIN:
|
|
297
|
+
break
|
|
295
298
|
if configfile not in self.files_read and Path(configfile).is_file():
|
|
296
299
|
self.files_read.append(configfile)
|
|
297
300
|
cp = ConfigParser()
|
|
@@ -243,16 +243,16 @@ class AccessDatabase(Database):
|
|
|
243
243
|
self.temp_query_names.append(qn)
|
|
244
244
|
else:
|
|
245
245
|
self.dao_flush_check()
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
246
|
+
with self._cursor() as curs:
|
|
247
|
+
if self.jet4:
|
|
248
|
+
encoded_sql = str(sql)
|
|
249
|
+
else:
|
|
250
|
+
encoded_sql = str(sql).encode(self.encoding)
|
|
251
|
+
if paramlist is None:
|
|
252
|
+
curs.execute(encoded_sql)
|
|
253
|
+
else:
|
|
254
|
+
curs.execute(encoded_sql, paramlist)
|
|
255
|
+
_state.subvars.add_substitution("$LAST_ROWCOUNT", curs.rowcount)
|
|
256
256
|
|
|
257
257
|
if type(sqlcmd) in (list, tuple):
|
|
258
258
|
for sql in sqlcmd:
|
|
@@ -269,10 +269,10 @@ class AccessDatabase(Database):
|
|
|
269
269
|
# Returns the results of the sql select statement.
|
|
270
270
|
# The Access driver returns data as unicode, so no decoding is necessary.
|
|
271
271
|
self.dao_flush_check()
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
272
|
+
with self._cursor() as curs:
|
|
273
|
+
curs.execute(sql)
|
|
274
|
+
rows = curs.fetchall()
|
|
275
|
+
return [d[0] for d in curs.description], rows
|
|
276
276
|
|
|
277
277
|
def select_rowsource(self, sql: str) -> tuple[list[str], Any]:
|
|
278
278
|
"""Return column names and an iterable that yields rows one at a time."""
|
|
@@ -308,20 +308,20 @@ class AccessDatabase(Database):
|
|
|
308
308
|
def table_exists(self, table_name: str, schema_name: str | None = None) -> bool:
|
|
309
309
|
"""Return True if the named table exists in the Access database."""
|
|
310
310
|
self.dao_flush_check()
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
311
|
+
sql = "select Name from MSysObjects where Name=? And Type In (1,4,6);"
|
|
312
|
+
with self._cursor() as curs:
|
|
313
|
+
try:
|
|
314
|
+
curs.execute(sql, (table_name,))
|
|
315
|
+
except ErrInfo:
|
|
316
|
+
raise
|
|
317
|
+
except Exception as e:
|
|
318
|
+
raise ErrInfo(
|
|
319
|
+
type="db",
|
|
320
|
+
command_text=sql,
|
|
321
|
+
exception_msg=exception_desc(),
|
|
322
|
+
other_msg=f"Failure on test for existence of Access table {table_name}",
|
|
323
|
+
) from e
|
|
324
|
+
rows = curs.fetchall()
|
|
325
325
|
return len(rows) > 0
|
|
326
326
|
|
|
327
327
|
def column_exists(
|
|
@@ -332,41 +332,41 @@ class AccessDatabase(Database):
|
|
|
332
332
|
) -> bool:
|
|
333
333
|
"""Return True if the named column exists in the given Access table."""
|
|
334
334
|
self.dao_flush_check()
|
|
335
|
-
curs = self.cursor()
|
|
336
335
|
quoted_col = self.quote_identifier(column_name)
|
|
337
336
|
quoted_tbl = self.quote_identifier(table_name)
|
|
338
337
|
sql = f"select top 1 {quoted_col} from {quoted_tbl};"
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
338
|
+
with self._cursor() as curs:
|
|
339
|
+
try:
|
|
340
|
+
curs.execute(sql)
|
|
341
|
+
except Exception:
|
|
342
|
+
return False
|
|
343
343
|
return True
|
|
344
344
|
|
|
345
345
|
def table_columns(self, table_name: str, schema_name: str | None = None) -> list[str]:
|
|
346
346
|
"""Return a list of column names for the given Access table."""
|
|
347
347
|
self.dao_flush_check()
|
|
348
|
-
curs = self.cursor()
|
|
349
348
|
quoted_tbl = self.quote_identifier(table_name)
|
|
350
|
-
|
|
351
|
-
|
|
349
|
+
with self._cursor() as curs:
|
|
350
|
+
curs.execute(f"select top 1 * from {quoted_tbl};")
|
|
351
|
+
return [d[0] for d in curs.description]
|
|
352
352
|
|
|
353
353
|
def view_exists(self, view_name: str, schema_name: str | None = None) -> bool:
|
|
354
354
|
"""Return True if the named view or query exists in the Access database."""
|
|
355
355
|
self.dao_flush_check()
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
356
|
+
sql = "select Name from MSysObjects where Name=? And Type = 5;"
|
|
357
|
+
with self._cursor() as curs:
|
|
358
|
+
try:
|
|
359
|
+
curs.execute(sql, (view_name,))
|
|
360
|
+
except ErrInfo:
|
|
361
|
+
raise
|
|
362
|
+
except Exception as e:
|
|
363
|
+
raise ErrInfo(
|
|
364
|
+
type="db",
|
|
365
|
+
command_text=sql,
|
|
366
|
+
exception_msg=exception_desc(),
|
|
367
|
+
other_msg=f"Test for existence of Access view/query {view_name}",
|
|
368
|
+
) from e
|
|
369
|
+
rows = curs.fetchall()
|
|
370
370
|
return len(rows) > 0
|
|
371
371
|
|
|
372
372
|
def schema_exists(self, schema_name: str) -> bool:
|
|
@@ -447,4 +447,5 @@ class AccessDatabase(Database):
|
|
|
447
447
|
sq_name = self.schema_qualified_table_name(schema_name, table_name)
|
|
448
448
|
quoted_col = self.quote_identifier(column_name)
|
|
449
449
|
sql = f"insert into {sq_name} ({quoted_col}) values ({self.paramsubs(1)});"
|
|
450
|
-
self.
|
|
450
|
+
with self._cursor() as curs:
|
|
451
|
+
curs.execute(sql, (pyodbc.Binary(filedata),))
|
|
@@ -460,7 +460,6 @@ class Database(ABC):
|
|
|
460
460
|
paramspec = self.paramsubs(len(columns))
|
|
461
461
|
sql = f"insert into {sq_name} ({colspec}) values ({paramspec});"
|
|
462
462
|
rows = iter(rowsource)
|
|
463
|
-
curs = self.cursor()
|
|
464
463
|
eof = False
|
|
465
464
|
total_rows = 0
|
|
466
465
|
|
|
@@ -483,7 +482,7 @@ class Database(ABC):
|
|
|
483
482
|
except ImportError:
|
|
484
483
|
use_progress = False
|
|
485
484
|
|
|
486
|
-
def _import_loop() -> int:
|
|
485
|
+
def _import_loop(curs) -> int:
|
|
487
486
|
nonlocal eof, total_rows, task_id
|
|
488
487
|
while True:
|
|
489
488
|
b = []
|
|
@@ -575,12 +574,13 @@ class Database(ABC):
|
|
|
575
574
|
break
|
|
576
575
|
return total_rows
|
|
577
576
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
577
|
+
with self._cursor() as curs:
|
|
578
|
+
if use_progress and progress_ctx is not None:
|
|
579
|
+
with progress_ctx:
|
|
580
|
+
task_id = progress_ctx.add_task(sq_name, total=None)
|
|
581
|
+
_import_loop(curs)
|
|
582
|
+
else:
|
|
583
|
+
_import_loop(curs)
|
|
584
584
|
|
|
585
585
|
if _state.exec_log:
|
|
586
586
|
_state.exec_log.log_status_info(
|
|
@@ -145,4 +145,5 @@ class DsnDatabase(Database):
|
|
|
145
145
|
sq_name = self.schema_qualified_table_name(schema_name, table_name)
|
|
146
146
|
quoted_col = self.quote_identifier(column_name)
|
|
147
147
|
sql = f"insert into {sq_name} ({quoted_col}) values ({self.paramsubs(1)});"
|
|
148
|
-
self.
|
|
148
|
+
with self._cursor() as curs:
|
|
149
|
+
curs.execute(sql, (pyodbc.Binary(filedata),))
|
|
@@ -67,7 +67,7 @@ class DuckDBDatabase(Database):
|
|
|
67
67
|
with self._cursor() as curs:
|
|
68
68
|
cmd = f"select * from {querycommand};"
|
|
69
69
|
try:
|
|
70
|
-
curs.execute(cmd
|
|
70
|
+
curs.execute(cmd)
|
|
71
71
|
_state.subvars.add_substitution("$LAST_ROWCOUNT", curs.rowcount)
|
|
72
72
|
except Exception:
|
|
73
73
|
self.rollback()
|
|
@@ -127,30 +127,29 @@ class FirebirdDatabase(Database):
|
|
|
127
127
|
|
|
128
128
|
def table_exists(self, table_name: str, schema_name: str | None = None) -> bool:
|
|
129
129
|
"""Return True if the named table exists in the Firebird database."""
|
|
130
|
-
curs = self.cursor()
|
|
131
130
|
sql = (
|
|
132
131
|
"SELECT RDB$RELATION_NAME FROM RDB$RELATIONS "
|
|
133
132
|
"WHERE RDB$SYSTEM_FLAG=0 AND RDB$VIEW_BLR IS NULL "
|
|
134
133
|
"AND RDB$RELATION_NAME=?;"
|
|
135
134
|
)
|
|
136
|
-
|
|
137
|
-
curs.execute(sql, (table_name.upper(),))
|
|
138
|
-
except ErrInfo:
|
|
139
|
-
raise
|
|
140
|
-
except Exception as e:
|
|
135
|
+
with self._cursor() as curs:
|
|
141
136
|
try:
|
|
142
|
-
|
|
143
|
-
except
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
137
|
+
curs.execute(sql, (table_name.upper(),))
|
|
138
|
+
except ErrInfo:
|
|
139
|
+
raise
|
|
140
|
+
except Exception as e:
|
|
141
|
+
try:
|
|
142
|
+
self.rollback()
|
|
143
|
+
except Exception:
|
|
144
|
+
pass # Rollback is best-effort after a failed query.
|
|
145
|
+
raise ErrInfo(
|
|
146
|
+
type="db",
|
|
147
|
+
command_text=sql,
|
|
148
|
+
exception_msg=exception_desc(),
|
|
149
|
+
other_msg=f"Failed test for existence of Firebird table {table_name}",
|
|
150
|
+
) from e
|
|
151
|
+
rows = curs.fetchall()
|
|
152
152
|
self.conn.commit()
|
|
153
|
-
curs.close()
|
|
154
153
|
return len(rows) > 0
|
|
155
154
|
|
|
156
155
|
def column_exists(
|
|
@@ -160,53 +159,52 @@ class FirebirdDatabase(Database):
|
|
|
160
159
|
schema_name: str | None = None,
|
|
161
160
|
) -> bool:
|
|
162
161
|
"""Return True if the named column exists in the given Firebird table."""
|
|
163
|
-
curs = self.cursor()
|
|
164
162
|
quoted_col = self.quote_identifier(column_name)
|
|
165
163
|
quoted_tbl = self.quote_identifier(table_name)
|
|
166
164
|
sql = f"select first 1 {quoted_col} from {quoted_tbl};"
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
165
|
+
with self._cursor() as curs:
|
|
166
|
+
try:
|
|
167
|
+
curs.execute(sql)
|
|
168
|
+
except Exception:
|
|
169
|
+
return False
|
|
171
170
|
return True
|
|
172
171
|
|
|
173
172
|
def table_columns(self, table_name: str, schema_name: str | None = None) -> list[str]:
|
|
174
173
|
"""Return a list of column names for the given Firebird table."""
|
|
175
|
-
curs = self.cursor()
|
|
176
174
|
quoted_tbl = self.quote_identifier(table_name)
|
|
177
175
|
sql = f"select first 1 * from {quoted_tbl};"
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
176
|
+
with self._cursor() as curs:
|
|
177
|
+
try:
|
|
178
|
+
curs.execute(sql)
|
|
179
|
+
except ErrInfo:
|
|
180
|
+
raise
|
|
181
|
+
except Exception as e:
|
|
182
|
+
self.rollback()
|
|
183
|
+
raise ErrInfo(
|
|
184
|
+
type="db",
|
|
185
|
+
command_text=sql,
|
|
186
|
+
exception_msg=exception_desc(),
|
|
187
|
+
other_msg=f"Failed to get column names for table {table_name} of {self.name()}",
|
|
188
|
+
) from e
|
|
189
|
+
return [d[0] for d in curs.description]
|
|
191
190
|
|
|
192
191
|
def view_exists(self, view_name: str, schema_name: str | None = None) -> bool:
|
|
193
192
|
"""Return True if the named view exists in the Firebird database."""
|
|
194
|
-
curs = self.cursor()
|
|
195
193
|
sql = "select distinct rdb$view_name from rdb$view_relations where rdb$view_name = ?;"
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
194
|
+
with self._cursor() as curs:
|
|
195
|
+
try:
|
|
196
|
+
curs.execute(sql, (view_name,))
|
|
197
|
+
except ErrInfo:
|
|
198
|
+
raise
|
|
199
|
+
except Exception as e:
|
|
200
|
+
self.rollback()
|
|
201
|
+
raise ErrInfo(
|
|
202
|
+
type="db",
|
|
203
|
+
command_text=sql,
|
|
204
|
+
exception_msg=exception_desc(),
|
|
205
|
+
other_msg=f"Failed test for existence of Firebird view {view_name}",
|
|
206
|
+
) from e
|
|
207
|
+
rows = curs.fetchall()
|
|
210
208
|
return len(rows) > 0
|
|
211
209
|
|
|
212
210
|
def schema_exists(self, schema_name: str) -> bool:
|
|
@@ -215,14 +213,13 @@ class FirebirdDatabase(Database):
|
|
|
215
213
|
|
|
216
214
|
def role_exists(self, rolename: str) -> bool:
|
|
217
215
|
"""Return True if the named role or user exists in the Firebird database."""
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
curs.close()
|
|
216
|
+
with self._cursor() as curs:
|
|
217
|
+
curs.execute(
|
|
218
|
+
"SELECT DISTINCT USER FROM RDB$USER_PRIVILEGES WHERE USER = ? union "
|
|
219
|
+
" SELECT DISTINCT RDB$ROLE_NAME FROM RDB$ROLES WHERE RDB$ROLE_NAME = ?;",
|
|
220
|
+
(rolename, rolename),
|
|
221
|
+
)
|
|
222
|
+
rows = curs.fetchall()
|
|
226
223
|
return len(rows) > 0
|
|
227
224
|
|
|
228
225
|
def drop_table(self, tablename: str) -> None:
|