execsql2 2.27.0__tar.gz → 2.29.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {execsql2-2.27.0 → execsql2-2.29.0}/CHANGELOG.md +38 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/PKG-INFO +4 -2
- {execsql2-2.27.0 → execsql2-2.29.0}/README.md +1 -1
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/about/divergence.md +8 -1
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/getting-started/syntax.md +3 -2
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/guides/editors.md +4 -4
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/guides/formatter.md +11 -2
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/reference/lint.md +1 -1
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/reference/metacommands.md +2 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/reference/security.md +1 -1
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/reference/substitution_vars.md +1 -1
- {execsql2-2.27.0 → execsql2-2.29.0}/extras/vscode-execsql/README.md +2 -3
- {execsql2-2.27.0 → execsql2-2.29.0}/pyproject.toml +4 -2
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/cli/commands/format.py +2 -1
- execsql2-2.29.0/src/execsql/cli/commands/lsp.py +64 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/cli/lint.py +45 -22
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/db/access.py +4 -4
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/db/base.py +23 -12
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/db/dsn.py +2 -2
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/db/duckdb.py +14 -2
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/db/firebird.py +2 -3
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/db/mysql.py +21 -5
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/db/oracle.py +5 -5
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/db/postgres.py +3 -4
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/db/sqlserver.py +2 -2
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/exporters/duckdb.py +2 -2
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/exporters/ods.py +2 -2
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/exporters/templates.py +2 -2
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/exporters/xls.py +6 -5
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/exporters/xlsx.py +2 -2
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/exporters/yaml.py +3 -4
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/format.py +71 -21
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/importers/base.py +6 -10
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/lsp/document.py +7 -3
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/metacommands/debug.py +2 -2
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/metacommands/io_fileops.py +6 -6
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/metacommands/upsert.py +2 -5
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/models.py +11 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/script/variables.py +42 -10
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/types.py +8 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/utils/errors.py +16 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/cli/test_cli_surface.py +1 -1
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/cli/test_lint.py +19 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/db/test_copy_new.py +37 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/db/test_duckdb.py +3 -1
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/db/test_new_table_checks.py +24 -1
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/db/test_postgres_inprocess.py +3 -0
- execsql2-2.29.0/tests/db/test_quoted_substitution_roundtrip.py +40 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/importers/test_xls_importer.py +32 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/integration/test_mysql.py +56 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/integration/test_postgres.py +82 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/lsp/test_document.py +10 -0
- execsql2-2.29.0/tests/lsp/test_lsp_command.py +71 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/lsp/test_server.py +9 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/metacommands/test_metacommands_fileops_extra.py +4 -2
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/metacommands/test_pg_upsert.py +1 -1
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/scripts/fixtures/audit_smoke.sql +3 -3
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/security/test_substitution_injection.py +64 -20
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/test_format.py +60 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/test_types.py +17 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/utils/test_errors.py +27 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/uv.lock +5 -1
- execsql2-2.27.0/src/execsql/cli/commands/lsp.py +0 -40
- execsql2-2.27.0/tests/lsp/test_lsp_command.py +0 -32
- {execsql2-2.27.0 → execsql2-2.29.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/.github/dependabot.yml +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/.github/workflows/ci-cd.yml +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/.gitignore +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/.pre-commit-config.yaml +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/.pre-commit-hooks.yaml +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/.python-version +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/.readthedocs.yaml +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/CONTRIBUTING.md +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/LICENSE.txt +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/NOTICE +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/SECURITY.md +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/about/contributors.md +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/about/copyright.md +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/api/cli.md +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/api/db.md +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/api/exporters.md +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/api/importers.md +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/api/index.md +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/api/metacommands.md +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/dev/adding_db_adapters.md +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/dev/adding_exporters.md +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/dev/adding_importers.md +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/dev/adding_metacommands.md +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/dev/architecture.md +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/dev/releasing.md +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/getting-started/installation.md +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/getting-started/requirements.md +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/guides/debugging.md +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/guides/documentation.md +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/guides/encoding.md +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/guides/examples.md +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/guides/logging.md +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/guides/sql_syntax.md +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/guides/usage.md +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/guides/using_scripts.md +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/images/Compare_planets.png +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/images/actions.png +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/images/actions2.png +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/images/checkboxes.png +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/images/connect.b64 +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/images/connect.png +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/images/create_conf.png +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/images/data_error1_screenshot.jpg +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/images/entry_form.png +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/images/execsql_console.png +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/images/execsql_logo_01.png +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/images/fatals.png +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/images/logo_small.png +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/images/pause_terminal.png +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/images/pause_terminal_sm.b64 +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/images/pause_terminal_sm.png +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/images/prompt_compare.png +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/images/set_build_commands.jpg +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/images/unit_conversions.b64 +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/images/unit_conversions_029.png +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/images/unmatched.png +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/images/vim_execsql_highlight.png +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/index.md +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/docs/reference/configuration.md +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/extras/plugin-template/README.md +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/extras/plugin-template/pyproject.toml +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/extras/plugin-template/src/execsql_plugin_YOURNAME/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/extras/plugin-template/tests/test_plugin.py.example +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/extras/vscode-execsql/.vscodeignore +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/extras/vscode-execsql/LICENSE.txt +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/extras/vscode-execsql/extension.js +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/extras/vscode-execsql/icon.png +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/extras/vscode-execsql/package-lock.json +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/extras/vscode-execsql/package.json +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/extras/vscode-execsql/syntaxes/execsql.tmLanguage.json +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/justfile +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/scripts/benchmarks/README.md +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/scripts/benchmarks/results/.gitignore +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/scripts/benchmarks/run_benchmarks.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/scripts/generate_vscode_grammar.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/__main__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/api.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/cli/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/cli/application.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/cli/commands/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/cli/commands/config.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/cli/commands/init.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/cli/commands/lint.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/cli/commands/listing.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/cli/commands/run.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/cli/dispatch.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/cli/dsn.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/cli/help.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/cli/options.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/cli/run.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/config.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/config_validation.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/data/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/data/execsql.conf.template +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/data/script_header.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/db/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/db/factory.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/db/sqlite.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/db/tiers.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/debug/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/debug/repl.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/exceptions.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/exporters/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/exporters/base.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/exporters/delimited.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/exporters/feather.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/exporters/html.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/exporters/json.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/exporters/latex.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/exporters/markdown.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/exporters/parquet.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/exporters/pretty.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/exporters/protocol.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/exporters/raw.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/exporters/sqlite.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/exporters/values.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/exporters/xml.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/exporters/zip.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/gui/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/gui/base.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/gui/console.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/gui/desktop.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/gui/tui.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/importers/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/importers/csv.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/importers/feather.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/importers/json.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/importers/ods.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/importers/xls.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/interactive.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/lsp/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/lsp/code_actions.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/lsp/completion.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/lsp/diagnostics.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/lsp/folding.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/lsp/formatting.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/lsp/hover.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/lsp/navigation.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/lsp/rename.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/lsp/server.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/manifest.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/metacommands/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/metacommands/conditions.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/metacommands/connect.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/metacommands/control.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/metacommands/data.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/metacommands/dispatch.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/metacommands/effects.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/metacommands/io.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/metacommands/io_export.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/metacommands/io_import.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/metacommands/io_write.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/metacommands/prompt.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/metacommands/reference.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/metacommands/script_ext.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/metacommands/system.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/parser.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/plugins.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/py.typed +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/script/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/script/ast.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/script/control.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/script/engine.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/script/executor.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/script/parser.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/state.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/state.pyi +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/utils/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/utils/auth.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/utils/color.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/utils/crypto.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/utils/datetime.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/utils/fileio.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/utils/gui.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/utils/mail.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/utils/numeric.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/utils/regex.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/utils/strings.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/src/execsql/utils/timer.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/templates/README.md +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/templates/config_settings.sqlite +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/templates/example_config_prompt.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/templates/make_config_db.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/templates/md_compare.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/templates/md_glossary.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/templates/md_upsert.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/templates/pg_compare.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/templates/pg_glossary.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/templates/pg_upsert.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/templates/script_template.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/templates/ss_compare.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/templates/ss_glossary.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/templates/ss_upsert.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/cli/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/cli/test_cli.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/cli/test_cli_e2e.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/cli/test_cli_run.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/cli/test_commands.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/cli/test_config_validate.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/cli/test_dispatch.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/cli/test_error_location.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/cli/test_init.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/cli/test_lint_fixtures.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/cli/test_log_exit_record.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/cli/test_manifest.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/cli/test_ping.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/cli/test_profile.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/cli/test_sigterm.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/cli/test_undeclared_imports.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/conftest.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/data/formatter_shapes/comment_anchoring.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/data/formatter_shapes/escapes_and_literals.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/data/formatter_shapes/substitution_and_metacommands.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/data/formatter_shapes/unparsable_sql.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/data/lint/conditions_and_blocks.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/data/lint/does_not_parse.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/data/lint/every_metacommand.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/data/lint/invalid.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/db/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/db/test_access_windows.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/db/test_base.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/db/test_db_adapters_mocked.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/db/test_db_adapters_mocked_extra.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/db/test_dsn.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/db/test_factory.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/db/test_factory_postgres_encoding.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/db/test_import_atomic.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/db/test_import_replacement_quoted.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/db/test_import_short_rows.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/db/test_import_timezones.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/db/test_mysql_case_folding.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/db/test_mysql_inprocess.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/db/test_oracle_driver.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/db/test_postgres.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/db/test_roundtrip_adapters.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/db/test_sqlite.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/db/test_sqlite_extra.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/db/test_sqlserver_inprocess.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/db/test_tiers.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/db/test_transactions.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/exporters/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/exporters/conftest.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/exporters/test_base.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/exporters/test_db.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/exporters/test_delimited.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/exporters/test_duckdb_exporter.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/exporters/test_exporters.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/exporters/test_feather.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/exporters/test_html_extended.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/exporters/test_html_latex.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/exporters/test_json.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/exporters/test_json_extended.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/exporters/test_latex_extended.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/exporters/test_markdown.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/exporters/test_ods.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/exporters/test_ods_export.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/exporters/test_parquet.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/exporters/test_pretty_extended.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/exporters/test_raw_extended.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/exporters/test_roundtrip_columnar.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/exporters/test_roundtrip_dbfile.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/exporters/test_roundtrip_delimited.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/exporters/test_roundtrip_json.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/exporters/test_roundtrip_latex_xml.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/exporters/test_roundtrip_markup.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/exporters/test_roundtrip_ods.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/exporters/test_roundtrip_pretty_raw.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/exporters/test_roundtrip_template.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/exporters/test_roundtrip_values.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/exporters/test_roundtrip_xlsx.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/exporters/test_sqlite_exporter.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/exporters/test_templates.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/exporters/test_templates_extended.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/exporters/test_values_extended.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/exporters/test_xls_xlsx.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/exporters/test_xlsx.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/exporters/test_xml.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/exporters/test_yaml.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/exporters/test_zip.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/gui/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/gui/test_backends.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/gui/test_backends_extended.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/gui/test_compare_stats.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/gui/test_compute_row_diffs.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/gui/test_desktop_dialogs.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/gui/test_format_table.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/gui/test_tui_pilot.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/gui/test_tui_pilot_complex.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/gui/test_utils_gui_extended.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/importers/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/importers/conftest.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/importers/test_base_extended.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/importers/test_csv_blank_lines.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/importers/test_csv_edge_cases.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/importers/test_csv_importer.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/importers/test_feather_importer.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/importers/test_json_importer.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/importers/test_ods_importer.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/importers/test_roundtrip_csv.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/integration/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/integration/conftest.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/integration/test_dsn.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/integration/test_duckdb.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/integration/test_sqlite.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/live_db.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/lsp/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/lsp/test_code_actions.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/lsp/test_completion.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/lsp/test_diagnostics.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/lsp/test_folding.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/lsp/test_formatting.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/lsp/test_hover.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/lsp/test_navigation.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/lsp/test_rename.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/metacommands/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/metacommands/test_assert.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/metacommands/test_breakpoint.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/metacommands/test_conditions_extra.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/metacommands/test_connect.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/metacommands/test_io_export.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/metacommands/test_io_import.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/metacommands/test_metacommands.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/metacommands/test_metacommands_connect.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/metacommands/test_metacommands_data.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/metacommands/test_metacommands_extended.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/metacommands/test_metacommands_io.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/metacommands/test_metacommands_io_write_extra.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/metacommands/test_metacommands_script_ext.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/metacommands/test_metacommands_system.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/metacommands/test_metacommands_system_extra.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/metacommands/test_prompt.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/metacommands/test_row_count.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/metacommands/test_show_scripts.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/scripts/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/scripts/fixtures/audit_lint_bad.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/scripts/fixtures/audit_smoke/sample.json +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/scripts/fixtures/audit_smoke/sample.jsonl +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/scripts/fixtures/config_runtime.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/scripts/fixtures/control_flow.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/scripts/fixtures/counters_and_locals.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/scripts/fixtures/error_handling.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/scripts/fixtures/import_options/has_comments.csv +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/scripts/fixtures/import_options.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/scripts/fixtures/includes/helper.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/scripts/fixtures/includes.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/scripts/fixtures/io_formats.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/scripts/fixtures/io_roundtrip.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/scripts/fixtures/multi_db.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/scripts/fixtures/parquet_feather_roundtrip.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/scripts/fixtures/parse_only/parse_tree.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/scripts/fixtures/predicates.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/scripts/fixtures/scripts.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/scripts/fixtures/smoke.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/scripts/fixtures/subroutine_loops.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/scripts/fixtures/subvars_advanced.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/scripts/fixtures/template_export/greeting.tmpl +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/scripts/fixtures/template_export.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/scripts/fixtures/timer.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/scripts/fixtures/transactions.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/scripts/fixtures/write_create_table.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/scripts/fixtures/xlsx_ods_roundtrip.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/scripts/test_sql_scripts.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/security/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/security/test_env_detection.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/security/test_expansion_bomb.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/security/test_import_regex_hardening.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/security/test_log_redaction.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/security/test_log_secrets.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/security/test_odbc_injection.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/security/test_path_containment.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/security/test_zip_bomb.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/test_api.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/test_api_errors.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/test_api_extended.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/test_api_filewriter.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/test_api_isolation.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/test_api_subprocess.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/test_ast.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/test_ast_parser.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/test_bundled_templates.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/test_config.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/test_config_data.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/test_config_extended.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/test_date_inference.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/test_debug_repl.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/test_engine.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/test_error_messages.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/test_exceptions.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/test_executor.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/test_executor_inprocess.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/test_interactive.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/test_mail.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/test_models.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/test_package.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/test_parser.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/test_parser_params.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/test_plugins.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/test_reference.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/test_registry.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/test_script.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/test_state.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/test_tooling_versions.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/utils/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/utils/test_auth.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/utils/test_auth_extra.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/utils/test_crypto.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/utils/test_datetime.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/utils/test_encodedfile_context.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/utils/test_errors_extra.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/utils/test_fileio.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/utils/test_fileio_extra.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/utils/test_keyring_rules.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/utils/test_mail_tls.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/utils/test_numeric.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/utils/test_regex.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/utils/test_strings.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/utils/test_timer.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/tests/utils/test_timer_extra.py +0 -0
- {execsql2-2.27.0 → execsql2-2.29.0}/zensical.toml +0 -0
|
@@ -13,6 +13,44 @@ ______________________________________________________________________
|
|
|
13
13
|
|
|
14
14
|
______________________________________________________________________
|
|
15
15
|
|
|
16
|
+
## [2.29.0] - 2026-10-13
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- **`!'!var!'!` stores backslashes as written** on every DBMS: a value such as `C:\data\new` was stored as `C:\\data\\new` on SQLite, PostgreSQL, DuckDB, SQL Server, Oracle, Firebird and Access. MySQL/MariaDB (which read `\` as an escape) and PostgreSQL escape strings written as `E!'!var!'!` still get backslashes doubled, so both forms store the same value and the `E` workaround is no longer needed. Scripts that removed the doubled backslashes some other way should stop.
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- **Oracle IMPORT and COPY with a time column** create it as text (`VARCHAR2(15)`, holding `HH:MM:SS`); `TO NEW` stopped with "Oracle DBMS type has no specification for data type time".
|
|
25
|
+
- **MySQL IMPORT accepts a header with capital letters** (`Id,Name`): it stopped with "columns that are not in table" (leaving the new table behind), and with `IMPORT_COMMON_COLUMNS_ONLY` it loaded those columns as NULL.
|
|
26
|
+
- **PostgreSQL IMPORT honors `SKIP n`** for quoted CSV files, which load through PostgreSQL's COPY: it skipped only the header line, loading the lines before it as data or failing on them.
|
|
27
|
+
- **Spreadsheet IMPORT reads every data row:** an XLSX row whose values are all `0` or FALSE no longer ends the data (it and every row after it were dropped), and an XLS sheet imported with `SKIP n` no longer loses its last *n* rows.
|
|
28
|
+
|
|
29
|
+
______________________________________________________________________
|
|
30
|
+
|
|
31
|
+
## [2.28.0] - 2026-10-13
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
- **`execsql lsp --check`** reports whether the language server can start (exit 0) or what is missing (exit 1).
|
|
36
|
+
|
|
37
|
+
### Changed
|
|
38
|
+
|
|
39
|
+
- **Missing-package messages** (database drivers, spreadsheet, YAML and template exports, `PG_UPSERT`, the formatter and the language server) name the execsql2 extra and show how to add it while keeping the extras already installed.
|
|
40
|
+
|
|
41
|
+
### Fixed
|
|
42
|
+
|
|
43
|
+
- **`execsql format` keeps optimizer hints and MySQL executable comments:** a statement holding `/*+ ... */` or `/*! ... */` is left as written in both modes. It was rewritten to `/* + ... */` and the hint moved off its keyword, so MySQL skipped the `/*! */` SQL (for example a `WHERE` or `PARTITION BY`) and Oracle/MySQL ignored the hint; re-check files the formatter or its pre-commit hook rewrote.
|
|
44
|
+
- **`execsql format` no longer ends a final statement with a `;` the script did not have:** a script execsql refused for an unfinished last statement became one that ran it.
|
|
45
|
+
- **`execsql format`:** a statement kept as written after another on the same line no longer starts with a space.
|
|
46
|
+
- **`execsql format` without sqlglot** printed its `pip install` hint without the `[formatter]` extra.
|
|
47
|
+
- **`execsql lint` and the language server read a `SUB_INI` file once** however many lines name it; a script naming a large INI file on many lines took minutes to lint and froze the editor. V002 for a variable a `SUB_INI` defines is reported at the `SUB_INI` line, not line 0.
|
|
48
|
+
- **`execsql lsp` with an incompatible pygls** names the installed pygls version instead of saying the `lsp` extra is missing.
|
|
49
|
+
- **`COPY` keeps binary columns** (SQLite/DuckDB BLOB, PostgreSQL `bytea`) byte for byte, and `COPY ... TO NEW` creates them as binary columns. They were copied as escaped text (`\x89PNG...`), and into PostgreSQL as text.
|
|
50
|
+
- **DuckDB `TO NEW`** is no longer refused because a table of the same name exists in an `ATTACH`ed database; only the current database counts.
|
|
51
|
+
|
|
52
|
+
______________________________________________________________________
|
|
53
|
+
|
|
16
54
|
## [2.27.0] - 2026-10-12
|
|
17
55
|
|
|
18
56
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: execsql2
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.29.0
|
|
4
4
|
Summary: Runs a SQL script against a PostgreSQL, SQLite, MariaDB/MySQL, DuckDB, Firebird, MS-Access, MS-SQL-Server, or Oracle database, or an ODBC DSN. Provides metacommands to import and export data, copy data between databases, conditionally execute SQL and metacommands, and dynamically alter SQL and metacommands with substitution variables.
|
|
5
5
|
Project-URL: Homepage, https://execsql2.readthedocs.io
|
|
6
6
|
Project-URL: Repository, https://github.com/geocoug/execsql
|
|
@@ -95,8 +95,10 @@ Requires-Dist: openpyxl>=3.1; extra == 'dev'
|
|
|
95
95
|
Requires-Dist: polars>=1.0; extra == 'dev'
|
|
96
96
|
Requires-Dist: pre-commit>=3.5.0; extra == 'dev'
|
|
97
97
|
Requires-Dist: pygls<3,>=2.1; extra == 'dev'
|
|
98
|
+
Requires-Dist: pytest-asyncio>=1.0; extra == 'dev'
|
|
98
99
|
Requires-Dist: pytest-cov>=5.0.0; extra == 'dev'
|
|
99
100
|
Requires-Dist: pytest-lsp>=1.0; extra == 'dev'
|
|
101
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
100
102
|
Requires-Dist: pyyaml>=6.0; extra == 'dev'
|
|
101
103
|
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
102
104
|
Requires-Dist: sqlglot<30.22,>=30.21.0; extra == 'dev'
|
|
@@ -464,7 +466,7 @@ execsql format --no-sql --in-place scripts/
|
|
|
464
466
|
```yaml
|
|
465
467
|
repos:
|
|
466
468
|
- repo: https://github.com/geocoug/execsql
|
|
467
|
-
rev: v2.
|
|
469
|
+
rev: v2.29.0
|
|
468
470
|
hooks:
|
|
469
471
|
- id: execsql-format
|
|
470
472
|
- id: execsql-lint
|
|
@@ -286,6 +286,7 @@ The legacy flat command-list engine (`_parse_script_lines` / `runscripts` / `Com
|
|
|
286
286
|
- **Exception hierarchy** — All custom exceptions inherit from `ExecSqlError`, enabling `except ExecSqlError` to catch any execsql-originated error.
|
|
287
287
|
- **Exception chaining** — All `raise` statements inside `except` blocks preserve the original traceback via `from`.
|
|
288
288
|
- **Database and driver error messages** — End with the error itself (`OperationalError: table t already exists`). Upstream appended "in <file> on line N of execsql", a line of its own source; in the package the file was often a driver's and the line number another file's. Where in execsql the error was raised goes to `execsql.log` as `Raised at: execsql/<module>.py:<line>`.
|
|
289
|
+
- **Missing optional package messages** — Name the execsql2 extra that installs the package and how to add it to a `uv tool` or `pipx` install without dropping the extras already there (`uv tool install "execsql2[...,postgres]"`). Upstream pointed at the package's web page.
|
|
289
290
|
- **Config file errors** — An invalid value in `execsql.conf` stops a run with `Configuration error: <file>: <problem>` (and the line, for an unreadable file) plus a pointer to `execsql config --validate`. Upstream printed `Configuration error on line N of execsql.py`, where N was a line of its own source. A duplicate key, a missing section header or a bare `%` is reported the same way; upstream failed with an uncaught exception. Errors raised by `ConfigData` for a file are `ConfigFileError`, a subclass of `ConfigError`.
|
|
290
291
|
- **ASSERT error type** — `ASSERT` failures now use a dedicated `"assert"` error type that produces `**** Assertion failed.` instead of `**** Error in metacommand.`. This distinguishes intentional script-level checks from actual metacommand errors. Upstream did not have `ASSERT`.
|
|
291
292
|
|
|
@@ -360,10 +361,16 @@ These are behavioral changes driven by security or correctness issues in the ups
|
|
|
360
361
|
| `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. |
|
|
361
362
|
| Partial dates inferred as timestamps | `dateutil` fills fields missing from a value with today's date, so `1-2`, `5/6`, `March 5`, `Jan` and `Monday` were inferred as timestamps and, on DuckDB, SQL Server, MySQL, Oracle, Firebird, Access and every ODS/XLSX/JSON/Parquet/Feather import, stored as dates in the current year. Inference now requires a four-digit year (or a month-first `m/d/yy` or `m-d-yy`) and a month and day, which restores upstream's behaviour for these values. `m-d-yy` is accepted in addition to upstream's `m/d/yy`. |
|
|
362
363
|
| Offset values stored as plain timestamps | With `dateutil`, a value with a UTC offset (`2024-01-01 10:00+05:00`) also counted as a plain timestamp, so a column mixing offset and plain values was created as `timestamp` and the driver converted the offset values with the client's time zone: the stored value depended on the machine that ran the import. A value with an offset is again not a plain timestamp, as with upstream's offset-free format list, so a mixed column is text and keeps each value as written. A column where every value has an offset is still `timestamptz`. |
|
|
364
|
+
| Binary columns in `COPY` | Every value a source cursor returned as bytes was decoded to text (undecodable bytes as `\xNN` escapes) before `COPY` wrote it, and `TO NEW` typed the column as text. Upstream did the same for SQLite BLOBs; with psycopg2 a PostgreSQL `bytea` arrived as a `memoryview` and escaped the decode, so for PostgreSQL this was an execsql2 regression. `COPY` now moves bytes unchanged and creates binary columns. Exports still write the decoded text. |
|
|
365
|
+
| Backslashes in `!'!var!'!` | Upstream doubled backslashes on Windows only, to protect `re.sub`'s replacement template; execsql2 2.x doubled them on every platform as SQL escaping, which corrupted values on every DBMS but MySQL. A backslash is now doubled only where it is an escape: on a MySQL/MariaDB connection, and in a PostgreSQL escape string written as `E!'!var!'!`. Apostrophes are doubled everywhere. |
|
|
366
|
+
| Time columns on Oracle | `IMPORT` / `COPY ... TO NEW` of a column of times stopped with "Oracle DBMS type has no specification for data type time" (a `DT_Time_Oracle` type was registered but inference never produced it). A time column is now created as `VARCHAR2(15)` holding the ISO time text. |
|
|
367
|
+
| Capitalized headers on MySQL | MySQL compared the file's headers with the table's lower-cased column names, so `Id` did not match `id`: IMPORT stopped (after `TO NEW` had created the table), and with `IMPORT_COMMON_COLUMNS_ONLY` such columns were skipped. The comparison now ignores case, as on PostgreSQL and the other DBMSs. |
|
|
368
|
+
| `SKIP` on PostgreSQL's COPY path | The PostgreSQL fast path (a quoted CSV file) skipped one line, the header, whatever `SKIP` said, so the skipped lines were loaded as data. It now starts after the `SKIP` lines like every other reader. |
|
|
369
|
+
| Spreadsheet rows on import | XLSX reading stopped at the first row with no truthy value, so a row of `0` / FALSE values ended the data; XLS reading with `SKIP n` stopped *n* rows before the end of the sheet. Both now read to the first row of empty cells. |
|
|
363
370
|
| Short data lines on import | A line with fewer values than the header was padded with NULLs only on PostgreSQL and MySQL. SQLite stopped with "Too few values", DuckDB and the other DBMSs with a driver parameter-count error. With `TO NEW` / `REPLACEMENT`, type inference skipped the missing values, so the column could be created `NOT NULL` and then reject the padded line, on PostgreSQL too. Missing trailing values are now NULL on every DBMS, and inference counts them as NULL. |
|
|
364
371
|
| `COPY` re-typed text values | Upstream described the destination table by re-reading the source's text values as if they came from a file, so a text column of `1`/`0` became `boolean` and one of dates became `timestamp` with `NEW` / `REPLACEMENT`, and values copied into an existing text column were converted on the way in (`1` stored as `true`). Text from the source database now stays text. Upstream also ran the source query twice, once to describe the table and once to copy it; execsql2 runs it once and holds the rows in a temporary file in between. |
|
|
365
372
|
| `REPLACEMENT` of a quoted or schema-qualified table | The SQLite, SQL Server, Oracle, Firebird and Access adapters quoted the table name a second time before dropping it, so `IMPORT` / `COPY ... TO REPLACEMENT "my tbl"` dropped a table named `"my tbl"` (quotes included) that did not exist and then failed to create the table, and on SQL Server, Oracle and Firebird `REPLACEMENT staging.t` tried to drop one table named `staging.t`. The name is now quoted once. |
|
|
366
|
-
| `TO NEW` onto an existing table | `COPY`'s checks that the source exists and that a `NEW` target does not were dead code: each raised its error inside a `try` that discarded it. `IMPORT ... TO NEW` had no check. Both now stop with `Table <name> already exists` before any DDL (when the database cannot say whether the table exists, the command goes ahead). A name without a schema counts only in the schema the table would be created in (MySQL: the current database). The source check was removed instead of revived: PostgreSQL reads an unquoted `COPY Orders` as `orders`, which the check would have reported missing.
|
|
373
|
+
| `TO NEW` onto an existing table | `COPY`'s checks that the source exists and that a `NEW` target does not were dead code: each raised its error inside a `try` that discarded it. `IMPORT ... TO NEW` had no check. Both now stop with `Table <name> already exists` before any DDL (when the database cannot say whether the table exists, the command goes ahead). A name without a schema counts only in the schema the table would be created in (MySQL: the current database), and on DuckDB only in the current database, not an `ATTACH`ed one. The source check was removed instead of revived: PostgreSQL reads an unquoted `COPY Orders` as `orders`, which the check would have reported missing. |
|
|
367
374
|
| No `exit` log record on error or halt | The log was closed before the `exit` record was written, so a run that ended with an error, a `HALT` or a cancelled prompt left no `exit` line, and Ctrl-C was recorded as `unknown`. Every run now writes exactly one `exit` record; Ctrl-C is `interrupted`. |
|
|
368
375
|
| SIGTERM lost the run's log | Python's default SIGTERM action ends the process without running exit handlers, so the buffered `execsql.log` (every record of the run) was never written, the transaction was not rolled back explicitly and a running `SYSTEM_CMD` kept going. The `execsql` command now turns SIGTERM into a normal exit with status 143 and an `exit` record of type `terminated`. `execsql.run()` does not install a handler. |
|
|
369
376
|
| `access_use_numeric` ignored | The config option was read but never applied, so MS-Access tables created by `IMPORT` / `COPY` always got `DOUBLE` for decimal data. With `Yes` they get `NUMERIC(precision,scale)` again, as upstream. The setting is read when each table is created, so it applies per run (upstream changed the shared type map once at startup). |
|
|
@@ -300,8 +300,9 @@ The options below belong to `run`: `execsql run -tl script.sql mydb.sqlite`.
|
|
|
300
300
|
`--version` and `-o`/`--online-help` work on their own (`execsql --version`)
|
|
301
301
|
and anywhere on a `run` command line.
|
|
302
302
|
`format` has its own options, described in the [formatter guide](../guides/formatter.md);
|
|
303
|
-
`lint`'s are in the [lint rules reference](../reference/lint.md); `
|
|
304
|
-
|
|
303
|
+
`lint`'s are in the [lint rules reference](../reference/lint.md); `lsp`'s (`--check`) in
|
|
304
|
+
[Editor Support](../guides/editors.md#install); `config`, `list` and `init` are described
|
|
305
|
+
[above](#config_command).
|
|
305
306
|
|
|
306
307
|
### Connection options
|
|
307
308
|
|
|
@@ -123,9 +123,8 @@ pipx install --force "execsql2[postgres,lsp]"
|
|
|
123
123
|
```
|
|
124
124
|
|
|
125
125
|
See [Adding an extra later](../getting-started/installation.md#adding-an-extra-later).
|
|
126
|
-
Check it with `execsql lsp
|
|
127
|
-
|
|
128
|
-
extra is needed when it is not.
|
|
126
|
+
Check it with `execsql lsp --check`: it prints `execsql lsp is ready (pygls 2.x.y)` and exits 0 when the server can start, and otherwise says what is
|
|
127
|
+
missing and exits 1.
|
|
129
128
|
|
|
130
129
|
## Settings { #settings }
|
|
131
130
|
|
|
@@ -163,7 +162,8 @@ execsql is the metacommand [plugins](../dev/architecture.md#plugin-system) insta
|
|
|
163
162
|
environment, so lint knows their keywords, as `execsql lint` does. Besides the
|
|
164
163
|
open script and the configuration files, it reads the files the script names
|
|
165
164
|
in `INCLUDE` (to find the variables they define: up to 200 files of at most
|
|
166
|
-
10 MiB each) and in `SUB_INI` (at most 1 MiB
|
|
165
|
+
10 MiB each and 20 MiB together) and in `SUB_INI` (at most 1 MiB, read once
|
|
166
|
+
however many lines name it). It reads only regular files,
|
|
167
167
|
never a FIFO or a device.
|
|
168
168
|
|
|
169
169
|
## VS Code { #vscode }
|
|
@@ -130,7 +130,7 @@ Conditions joined by `AND` / `OR` go on one line when their combined text is at
|
|
|
130
130
|
|
|
131
131
|
#### Layout only, or rewrite { #layout-only }
|
|
132
132
|
|
|
133
|
-
sqlglot does more than lay SQL out: it writes the SQL it parsed in its own preferred form. By default the formatter changes only layout and
|
|
133
|
+
sqlglot does more than lay SQL out: it writes the SQL it parsed in its own preferred form. By default the formatter changes only layout and the case of keywords and unquoted names (a function written `myFunc(x)` becomes `MYFUNC(x)`; quoted names and strings keep their case). A statement sqlglot would also rewrite is kept exactly as written, while the statements around it are still formatted.
|
|
134
134
|
|
|
135
135
|
| You wrote | sqlglot writes |
|
|
136
136
|
| --------------------------- | ------------------------------------- |
|
|
@@ -149,6 +149,15 @@ rewrite_sql = Yes
|
|
|
149
149
|
|
|
150
150
|
With it on, the rewrites a statement gets can change when the pinned sqlglot changes (see above). Turning it off later does not undo rewrites already written; it only stops new ones.
|
|
151
151
|
|
|
152
|
+
In either mode, a statement holding an optimizer hint (`/*+ ... */`, Oracle and MySQL) or a MySQL executable comment (`/*! ... */`) is kept exactly as written: the server reads them as part of the statement, and sqlglot would turn them into ordinary comments or move a hint away from its keyword.
|
|
153
|
+
|
|
154
|
+
```sql
|
|
155
|
+
INSERT /*+ APPEND */ INTO sales_hist SELECT * FROM sales_stage;
|
|
156
|
+
SELECT a FROM t /*!50001 WHERE a > 1 */;
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
The formatter never adds a `;` to a final statement that has none: execsql refuses to run a script that ends with an unfinished statement, and formatting leaves that for you to fix.
|
|
160
|
+
|
|
152
161
|
#### Comment handling
|
|
153
162
|
|
|
154
163
|
Comments interleaved within SQL statements (e.g. `--` comments between SELECT columns, or inside CASE expressions) are preserved through formatting using a marker-based round-trip:
|
|
@@ -261,7 +270,7 @@ select id,name,created_at from users where active = true order by name;
|
|
|
261
270
|
```yaml
|
|
262
271
|
repos:
|
|
263
272
|
- repo: https://github.com/geocoug/execsql
|
|
264
|
-
rev: v2.
|
|
273
|
+
rev: v2.29.0
|
|
265
274
|
hooks:
|
|
266
275
|
- id: execsql-format
|
|
267
276
|
- id: execsql-lint # optional: fail the commit on lint errors
|
|
@@ -158,7 +158,7 @@ The `execsql-lint` [pre-commit](https://pre-commit.com/) hook runs `execsql lint
|
|
|
158
158
|
```yaml
|
|
159
159
|
repos:
|
|
160
160
|
- repo: https://github.com/geocoug/execsql
|
|
161
|
-
rev: v2.
|
|
161
|
+
rev: v2.29.0
|
|
162
162
|
hooks:
|
|
163
163
|
- id: execsql-lint
|
|
164
164
|
args: [--ignore, V002] # optional: any execsql lint option
|
|
@@ -1950,6 +1950,8 @@ The delimiter characters that will be recognized in a text file, and that can be
|
|
|
1950
1950
|
|
|
1951
1951
|
The SKIP key phrase specifies the number of lines (or rows) at the beginning of the file (or worksheet) to discard before evaluating the remainder of the input as a data table.
|
|
1952
1952
|
|
|
1953
|
+
A worksheet's data ends at the first row whose cells are all empty; a row of zeros or FALSE values is data.
|
|
1954
|
+
|
|
1953
1955
|
If the NEW keyword is used, the input will be scanned to determine the data type of each column, and a CREATE TABLE statement run to create a new table for the data. A column is typed as a date or timestamp only if every value gives a full date with the year written out (`2024-01-05`, `1/5/2024`, `Jan 5, 2024`) or as a month-first two-digit year (`1/5/24`, `01-05-24`); values such as `1-2`, `5/6`, `March 5`, `Jan`, `Monday` or a day-first `13-05-24` leave the column as text. Scanning of the file to determine data formats is separate from the scanning that may be done to determine the quote and delimiter characters. If a table of that name already exists, the IMPORT stops before creating or loading anything, with the error `Table <name> already exists`. A table name without a schema is looked for only in the schema the table would be created in (on MySQL, the current database), so a table of the same name in another schema does not stop the IMPORT. If the table already exists when the NEW keyword is used, a fatal error will result and *execsql* will halt. If the REPLACEMENT keyword is used, the result is the same as if the NEW keyword were used, except that an existing table of the given name will be deleted first. This replacement is not atomic: if the drop succeeds but a later create or load step fails, the old table has already been removed. If the drop fails, *execsql* stops the IMPORT command before creating or loading the replacement table. *execsql* uses a "drop table" statement to drop an existing table, and the "drop table" statement may not succeed if there are dependencies on that table (see the discussion of [implicit drop table
|
|
1954
1956
|
statements](../guides/sql_syntax.md#implicit_drop)). If the table to be dropped does not exist, an informational message will be written to the log.
|
|
1955
1957
|
|
|
@@ -184,7 +184,7 @@ SELECT * FROM !!target_schema!!.mytable;
|
|
|
184
184
|
|
|
185
185
|
If a variable value is derived from external input (command-line arguments, environment variables, `PROMPT` responses, or data read from a file), the script author is responsible for quoting or validating that value before it is substituted into SQL.
|
|
186
186
|
|
|
187
|
-
The `!'!varname!'!` dereferencing form wraps the value in single quotes and doubles any embedded apostrophes, which helps when inserting text values into SQL literals:
|
|
187
|
+
The `!'!varname!'!` dereferencing form wraps the value in single quotes and doubles any embedded apostrophes (and, on MySQL/MariaDB, backslashes, so `\'` cannot end the literal there), which helps when inserting text values into SQL literals:
|
|
188
188
|
|
|
189
189
|
```sql
|
|
190
190
|
-- !x! sub author_name !!&AUTHOR!!
|
|
@@ -11,7 +11,7 @@ SUB <match_string> <replacement_string>
|
|
|
11
11
|
The \<match_string> identifies the substitution variable; the \<replacement_string> is the text inserted in its place. A substitution is recognized only when the match string is bracketed by one of three tokens:
|
|
12
12
|
|
|
13
13
|
- `!!name!!` — insert the value verbatim.
|
|
14
|
-
- `!'!name!'!` — wrap the value in single quotes, doubling any embedded apostrophe. Suitable for SQL string literals containing user data.
|
|
14
|
+
- `!'!name!'!` — wrap the value in single quotes, doubling any embedded apostrophe; on a MySQL or MariaDB connection, which reads `\` in a string literal as an escape, backslashes are doubled too. The value is stored exactly as written (`C:\data\new` stays `C:\data\new`). Suitable for SQL string literals containing user data. On PostgreSQL, `E!'!name!'!` (an escape string) doubles backslashes too, so it stores the same value as `!'!name!'!`; the `E` is not needed.
|
|
15
15
|
- `!"!name!"!` — wrap the value in double quotes. Suitable for SQL quoted identifiers, or for metacommand arguments that must be quoted.
|
|
16
16
|
|
|
17
17
|
Variables can also be set before the script starts: with `--var NAME=VALUE` on the [command line](../getting-started/syntax.md#options), or in the `[variables]` section of a [config file](configuration.md#configuration). `--var` wins over the config file, and a `SUB` in the script can reassign either.
|
|
@@ -196,9 +196,8 @@ ______________________________________________________________________
|
|
|
196
196
|
|
|
197
197
|
**No lint findings or completion**
|
|
198
198
|
|
|
199
|
-
- Run `execsql lsp
|
|
200
|
-
|
|
201
|
-
extra with the extras you already have, e.g. `uv tool install "execsql2[postgres,lsp]"`
|
|
199
|
+
- Run `execsql lsp --check` in a terminal. It says `execsql lsp is ready` when the server can start;
|
|
200
|
+
otherwise it says what is missing. Add the extra with the extras you already have, e.g. `uv tool install "execsql2[postgres,lsp]"`
|
|
202
201
|
([Adding an extra later](https://execsql2.readthedocs.io/en/latest/getting-started/installation/#adding-an-extra-later)).
|
|
203
202
|
- If `execsql` works in a terminal but not in VS Code, set `execsql.server.path` to its full path
|
|
204
203
|
(`which execsql`).
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "execsql2"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.29.0"
|
|
8
8
|
description = "Runs a SQL script against a PostgreSQL, SQLite, MariaDB/MySQL, DuckDB, Firebird, MS-Access, MS-SQL-Server, or Oracle database, or an ODBC DSN. Provides metacommands to import and export data, copy data between databases, conditionally execute SQL and metacommands, and dynamically alter SQL and metacommands with substitution variables."
|
|
9
9
|
readme = { file = "README.md", content-type = "text/markdown" }
|
|
10
10
|
license = { file = "LICENSE.txt" }
|
|
@@ -103,6 +103,8 @@ dev = [
|
|
|
103
103
|
"mkdocstrings-python>=2.0.3",
|
|
104
104
|
"mypy>=1.10",
|
|
105
105
|
"pre-commit>=3.5.0",
|
|
106
|
+
"pytest>=8",
|
|
107
|
+
"pytest-asyncio>=1.0",
|
|
106
108
|
"pytest-cov>=5.0.0",
|
|
107
109
|
"pytest-lsp>=1.0",
|
|
108
110
|
"ruff>=0.4",
|
|
@@ -187,7 +189,7 @@ line-ending = "auto"
|
|
|
187
189
|
exclude = ["*.md"]
|
|
188
190
|
|
|
189
191
|
[tool.bumpversion]
|
|
190
|
-
current_version = "2.
|
|
192
|
+
current_version = "2.29.0"
|
|
191
193
|
commit = true
|
|
192
194
|
tag = true
|
|
193
195
|
tag_name = "v{new_version}"
|
|
@@ -5,6 +5,7 @@ from __future__ import annotations
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
|
|
7
7
|
import typer
|
|
8
|
+
from rich.markup import escape
|
|
8
9
|
|
|
9
10
|
from execsql.cli.application import ExecsqlCommand, app
|
|
10
11
|
from execsql.cli.help import _err_console
|
|
@@ -90,7 +91,7 @@ def format_cmd(
|
|
|
90
91
|
try:
|
|
91
92
|
_require_sqlglot()
|
|
92
93
|
except ImportError as exc:
|
|
93
|
-
_err_console.print(f"[bold red]Error:[/bold red] {exc}", highlight=False)
|
|
94
|
+
_err_console.print(f"[bold red]Error:[/bold red] {escape(str(exc))}", highlight=False)
|
|
94
95
|
raise typer.Exit(code=1) from exc
|
|
95
96
|
raise typer.Exit(
|
|
96
97
|
code=run_formatter(
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""The ``lsp`` command: the language server editors start for ``.sql`` files."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from rich.markup import escape
|
|
7
|
+
|
|
8
|
+
from execsql.cli.application import ExecsqlCommand, app
|
|
9
|
+
from execsql.cli.help import _err_console
|
|
10
|
+
from execsql.utils.errors import install_hint
|
|
11
|
+
|
|
12
|
+
__all__ = ["lsp_cmd"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _lsp_libraries_installed() -> bool:
|
|
16
|
+
"""Whether pygls and lsprotocol are installed (whether or not this execsql can use them)."""
|
|
17
|
+
from importlib.util import find_spec
|
|
18
|
+
|
|
19
|
+
return all(find_spec(name) is not None for name in ("pygls", "lsprotocol"))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _pygls_version() -> str:
|
|
23
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
return version("pygls")
|
|
27
|
+
except PackageNotFoundError:
|
|
28
|
+
return "unknown"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@app.command(
|
|
32
|
+
cls=ExecsqlCommand,
|
|
33
|
+
name="lsp",
|
|
34
|
+
help=(
|
|
35
|
+
"Run the execsql language server on stdin/stdout, for an editor to start. "
|
|
36
|
+
"Shows execsql lint findings as you type, completes metacommands, "
|
|
37
|
+
"conditional tests and variables, explains them on hover, and finds where variables, "
|
|
38
|
+
"SCRIPTs and included files are defined, offers quick fixes, and formats documents. Requires the lsp "
|
|
39
|
+
"extra; --check reports whether it is installed."
|
|
40
|
+
),
|
|
41
|
+
)
|
|
42
|
+
def lsp_cmd(
|
|
43
|
+
# Editors' language clients pass --stdio (VS Code always does); stdio is the only transport.
|
|
44
|
+
stdio: bool = typer.Option(False, "--stdio", hidden=True),
|
|
45
|
+
check: bool = typer.Option(False, "--check", help="Report whether the language server can start, and exit."),
|
|
46
|
+
) -> None:
|
|
47
|
+
"""The ``lsp`` command; its user-facing help is the decorator's ``help``."""
|
|
48
|
+
try:
|
|
49
|
+
from execsql.lsp.server import create_server
|
|
50
|
+
except ImportError as exc:
|
|
51
|
+
if _lsp_libraries_installed():
|
|
52
|
+
# pygls is there but this execsql cannot use it (another major version, a broken install).
|
|
53
|
+
message = (
|
|
54
|
+
f"execsql lsp cannot use the installed pygls {_pygls_version()} ({exc}). "
|
|
55
|
+
f"It needs pygls 2.x, which the lsp extra installs. {install_hint('lsp')}"
|
|
56
|
+
)
|
|
57
|
+
else:
|
|
58
|
+
message = f"execsql lsp needs the lsp extra. {install_hint('lsp')}"
|
|
59
|
+
_err_console.print(f"[bold red]Error:[/bold red] {escape(message)}", highlight=False)
|
|
60
|
+
raise typer.Exit(code=1) from exc
|
|
61
|
+
if check:
|
|
62
|
+
typer.echo(f"execsql lsp is ready (pygls {_pygls_version()}).")
|
|
63
|
+
return
|
|
64
|
+
create_server().start_io()
|
|
@@ -48,6 +48,7 @@ Public surface:
|
|
|
48
48
|
|
|
49
49
|
from __future__ import annotations
|
|
50
50
|
|
|
51
|
+
import functools
|
|
51
52
|
import json
|
|
52
53
|
import re
|
|
53
54
|
from collections import Counter
|
|
@@ -460,23 +461,40 @@ def _read_ini_vars(
|
|
|
460
461
|
language server; a file that is unreadable or not valid INI defines
|
|
461
462
|
nothing.
|
|
462
463
|
"""
|
|
463
|
-
from configparser import ConfigParser
|
|
464
|
-
from configparser import Error as ConfigError
|
|
465
|
-
|
|
466
464
|
p = Path(ini_file)
|
|
467
465
|
if not p.is_absolute() and script_dir is not None:
|
|
468
466
|
p = script_dir / p
|
|
467
|
+
try:
|
|
468
|
+
if not p.is_file():
|
|
469
|
+
return
|
|
470
|
+
stat = p.stat()
|
|
471
|
+
if stat.st_size > _MAX_INI_BYTES:
|
|
472
|
+
return
|
|
473
|
+
resolved = str(p.resolve())
|
|
474
|
+
except OSError:
|
|
475
|
+
return
|
|
476
|
+
defined_vars.update(_ini_section_keys(resolved, stat.st_size, stat.st_mtime_ns, section))
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
@functools.lru_cache(maxsize=64)
|
|
480
|
+
def _ini_section_keys(path: str, size: int, mtime_ns: int, section: str) -> frozenset[str]:
|
|
481
|
+
"""The upper-cased keys of *section* in the INI file at *path*.
|
|
482
|
+
|
|
483
|
+
Cached by the file's size and modification time: a script that names
|
|
484
|
+
the same file on many SUB_INI lines, and the language server re-linting
|
|
485
|
+
on every edit, read it once.
|
|
486
|
+
"""
|
|
487
|
+
from configparser import ConfigParser
|
|
488
|
+
from configparser import Error as ConfigError
|
|
469
489
|
|
|
470
490
|
cp = ConfigParser()
|
|
471
491
|
try:
|
|
472
|
-
|
|
473
|
-
return
|
|
474
|
-
cp.read_string(p.read_text(encoding="utf-8"), source=str(p))
|
|
492
|
+
cp.read_string(Path(path).read_text(encoding="utf-8"), source=path)
|
|
475
493
|
except (OSError, UnicodeDecodeError, ConfigError):
|
|
476
|
-
return
|
|
477
|
-
if cp.has_section(section):
|
|
478
|
-
|
|
479
|
-
|
|
494
|
+
return frozenset()
|
|
495
|
+
if not cp.has_section(section):
|
|
496
|
+
return frozenset()
|
|
497
|
+
return frozenset(key.upper() for key, _value in cp.items(section))
|
|
480
498
|
|
|
481
499
|
|
|
482
500
|
def _is_known_var(raw_name: str, defined_vars: set[str]) -> bool:
|
|
@@ -834,7 +852,12 @@ def _referencing_text(node: Node) -> list[str]:
|
|
|
834
852
|
return texts
|
|
835
853
|
|
|
836
854
|
|
|
837
|
-
def _check_unused_variables(
|
|
855
|
+
def _check_unused_variables(
|
|
856
|
+
script: Script,
|
|
857
|
+
defined: set[str],
|
|
858
|
+
issues: list[_Issue],
|
|
859
|
+
script_dir: Path | None = None,
|
|
860
|
+
) -> None:
|
|
838
861
|
"""Report variables a SUB defines that nothing ever reads.
|
|
839
862
|
|
|
840
863
|
Almost always a typo in either the definition or the reference — the two
|
|
@@ -845,8 +868,11 @@ def _check_unused_variables(script: Script, defined: set[str], issues: list[_Iss
|
|
|
845
868
|
referenced: set[str] = set()
|
|
846
869
|
_collect_var_references(script.body, referenced)
|
|
847
870
|
unused = sorted(defined - referenced)
|
|
871
|
+
lines: dict[str, int] = {}
|
|
872
|
+
if unused:
|
|
873
|
+
_definition_lines(script.body, script_dir, lines)
|
|
848
874
|
for name in unused:
|
|
849
|
-
line =
|
|
875
|
+
line = lines.get(name, 0)
|
|
850
876
|
issues.append(
|
|
851
877
|
_issue(
|
|
852
878
|
"V002",
|
|
@@ -857,18 +883,15 @@ def _check_unused_variables(script: Script, defined: set[str], issues: list[_Iss
|
|
|
857
883
|
)
|
|
858
884
|
|
|
859
885
|
|
|
860
|
-
def
|
|
861
|
-
"""
|
|
886
|
+
def _definition_lines(nodes: list[Node], script_dir: Path | None, lines: dict[str, int]) -> None:
|
|
887
|
+
"""Add to *lines* the line of the first metacommand that defines each variable, in script order."""
|
|
862
888
|
for node in nodes:
|
|
863
889
|
if isinstance(node, MetaCommandStatement):
|
|
864
890
|
probe: set[str] = set()
|
|
865
|
-
_extract_var_definition(node.command,
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
if found:
|
|
870
|
-
return found
|
|
871
|
-
return 0
|
|
891
|
+
_extract_var_definition(node.command, script_dir, probe)
|
|
892
|
+
for name in probe:
|
|
893
|
+
lines.setdefault(name, node.span.start_line)
|
|
894
|
+
_definition_lines(list(node.children()), script_dir, lines)
|
|
872
895
|
|
|
873
896
|
|
|
874
897
|
# ---------------------------------------------------------------------------
|
|
@@ -1008,7 +1031,7 @@ def lint(
|
|
|
1008
1031
|
)
|
|
1009
1032
|
|
|
1010
1033
|
# Pass 3: whole-script rules, which need every reference collected first
|
|
1011
|
-
_check_unused_variables(script, all_defined, issues)
|
|
1034
|
+
_check_unused_variables(script, all_defined, issues, script_dir)
|
|
1012
1035
|
|
|
1013
1036
|
return issues
|
|
1014
1037
|
|
|
@@ -17,7 +17,7 @@ from typing import Any, cast
|
|
|
17
17
|
from execsql.db.base import Database, _fetched
|
|
18
18
|
from execsql.db.tiers import SupportTier
|
|
19
19
|
from execsql.exceptions import ErrInfo
|
|
20
|
-
from execsql.utils.errors import exception_desc, fatal_error
|
|
20
|
+
from execsql.utils.errors import exception_desc, fatal_error, install_hint
|
|
21
21
|
from execsql.utils.auth import (
|
|
22
22
|
begin_login,
|
|
23
23
|
clear_stored_password,
|
|
@@ -68,7 +68,7 @@ class AccessDatabase(Database):
|
|
|
68
68
|
try:
|
|
69
69
|
import pyodbc # noqa: F401
|
|
70
70
|
except Exception:
|
|
71
|
-
fatal_error("The pyodbc module is required.
|
|
71
|
+
fatal_error(f"The pyodbc module is required to connect to Access. {install_hint('odbc')}")
|
|
72
72
|
from execsql.types import dbt_access
|
|
73
73
|
|
|
74
74
|
super().__init__(
|
|
@@ -327,8 +327,8 @@ class AccessDatabase(Database):
|
|
|
327
327
|
rows = curs.fetchall()
|
|
328
328
|
return [d[0] for d in curs.description], rows
|
|
329
329
|
|
|
330
|
-
def select_rowsource(self, sql: str) -> tuple[list[str], Any]:
|
|
331
|
-
"""Return column names and an iterable that yields rows one at a time."""
|
|
330
|
+
def select_rowsource(self, sql: str, *, decode_bytes: bool = True) -> tuple[list[str], Any]: # noqa: ARG002
|
|
331
|
+
"""Return column names and an iterable that yields rows one at a time (values as the driver returns them)."""
|
|
332
332
|
# Return 1) a list of column names, and 2) an iterable that yields rows.
|
|
333
333
|
self.dao_flush_check()
|
|
334
334
|
curs = self.cursor()
|
|
@@ -215,21 +215,18 @@ class Database(ABC):
|
|
|
215
215
|
def quote_literal(self, value: Any) -> str:
|
|
216
216
|
"""Return *value* as a SQL string literal, safely escaped.
|
|
217
217
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
Override per-DBMS only when the wire protocol requires a different
|
|
225
|
-
literal form.
|
|
218
|
+
Standard SQL: wrap in single quotes and double embedded apostrophes;
|
|
219
|
+
a backslash is an ordinary character. Embedded NUL bytes are
|
|
220
|
+
rejected (most wire protocols truncate or reject them). A DBMS
|
|
221
|
+
that reads a backslash in a literal as an escape (MySQL) overrides
|
|
222
|
+
this.
|
|
226
223
|
"""
|
|
227
224
|
if value is None:
|
|
228
225
|
return "NULL"
|
|
229
226
|
s = str(value)
|
|
230
227
|
if "\x00" in s:
|
|
231
228
|
raise ValueError("SQL literal contains a NUL byte; refusing to quote.")
|
|
232
|
-
return "'" + s.replace("
|
|
229
|
+
return "'" + s.replace("'", "''") + "'"
|
|
233
230
|
|
|
234
231
|
def paramsubs(self, paramcount: int) -> str:
|
|
235
232
|
"""Return a comma-separated string of *paramcount* parameter placeholders."""
|
|
@@ -405,8 +402,13 @@ class Database(ABC):
|
|
|
405
402
|
rows = curs.fetchall()
|
|
406
403
|
return [d[0] for d in curs.description], rows
|
|
407
404
|
|
|
408
|
-
def select_rowsource(self, sql: str) -> tuple[list[str], Generator]:
|
|
409
|
-
"""Execute *sql* and return ``(column_names, row_generator)`` for streaming large result sets.
|
|
405
|
+
def select_rowsource(self, sql: str, *, decode_bytes: bool = True) -> tuple[list[str], Generator]:
|
|
406
|
+
"""Execute *sql* and return ``(column_names, row_generator)`` for streaming large result sets.
|
|
407
|
+
|
|
408
|
+
A binary value (``bytes``) is decoded to text with the connection's
|
|
409
|
+
encoding, as the exporters expect, unless *decode_bytes* is false:
|
|
410
|
+
COPY passes false so binary columns reach the target unchanged.
|
|
411
|
+
"""
|
|
410
412
|
# Return 1) a list of column names, and 2) an iterable that yields rows.
|
|
411
413
|
curs = self.cursor()
|
|
412
414
|
try:
|
|
@@ -433,7 +435,7 @@ class Database(ABC):
|
|
|
433
435
|
break
|
|
434
436
|
else:
|
|
435
437
|
for row in rows:
|
|
436
|
-
if self.encoding:
|
|
438
|
+
if self.encoding and decode_bytes:
|
|
437
439
|
yield [
|
|
438
440
|
c.decode(self.encoding, "backslashreplace") if isinstance(c, bytes) else c
|
|
439
441
|
for c in row
|
|
@@ -501,6 +503,15 @@ class Database(ABC):
|
|
|
501
503
|
"""The schema (MySQL: database) an unqualified table name is created in, or ``None`` if unknown."""
|
|
502
504
|
return None
|
|
503
505
|
|
|
506
|
+
def table_exists_where_created(self, table_name: str, schema_name: str | None = None) -> bool:
|
|
507
|
+
"""Whether *table_name* exists where a ``CREATE TABLE`` on this connection would put it.
|
|
508
|
+
|
|
509
|
+
The same as :meth:`table_exists`, except on a connection that sees
|
|
510
|
+
tables of other databases (DuckDB ``ATTACH``): only the current
|
|
511
|
+
database counts there.
|
|
512
|
+
"""
|
|
513
|
+
return self.table_exists(table_name, schema_name)
|
|
514
|
+
|
|
504
515
|
def _query_current_schema(self, sql: str) -> str | None:
|
|
505
516
|
with self._cursor() as curs:
|
|
506
517
|
curs.execute(sql)
|
|
@@ -12,7 +12,7 @@ the CLI.
|
|
|
12
12
|
from execsql.db.base import Database
|
|
13
13
|
from execsql.db.tiers import SupportTier
|
|
14
14
|
from execsql.exceptions import ErrInfo
|
|
15
|
-
from execsql.utils.errors import exception_desc, fatal_error
|
|
15
|
+
from execsql.utils.errors import exception_desc, fatal_error, install_hint
|
|
16
16
|
from execsql.utils.auth import (
|
|
17
17
|
begin_login,
|
|
18
18
|
clear_stored_password,
|
|
@@ -51,7 +51,7 @@ class DsnDatabase(Database):
|
|
|
51
51
|
try:
|
|
52
52
|
import pyodbc # noqa: F401
|
|
53
53
|
except Exception:
|
|
54
|
-
fatal_error("The pyodbc module is required.
|
|
54
|
+
fatal_error(f"The pyodbc module is required to connect through an ODBC DSN. {install_hint('odbc')}")
|
|
55
55
|
from execsql.types import dbt_dsn
|
|
56
56
|
|
|
57
57
|
super().__init__(
|