execsql2 2.27.0__tar.gz → 2.28.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.28.0}/CHANGELOG.md +23 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/PKG-INFO +4 -2
- {execsql2-2.27.0 → execsql2-2.28.0}/README.md +1 -1
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/about/divergence.md +3 -1
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/getting-started/syntax.md +3 -2
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/guides/editors.md +4 -4
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/guides/formatter.md +11 -2
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/reference/lint.md +1 -1
- {execsql2-2.27.0 → execsql2-2.28.0}/extras/vscode-execsql/README.md +2 -3
- {execsql2-2.27.0 → execsql2-2.28.0}/pyproject.toml +4 -2
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/cli/commands/format.py +2 -1
- execsql2-2.28.0/src/execsql/cli/commands/lsp.py +64 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/cli/lint.py +45 -22
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/db/access.py +4 -4
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/db/base.py +17 -3
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/db/dsn.py +2 -2
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/db/duckdb.py +14 -2
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/db/firebird.py +2 -3
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/db/mysql.py +2 -2
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/db/oracle.py +5 -5
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/db/postgres.py +2 -3
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/db/sqlserver.py +2 -2
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/exporters/duckdb.py +2 -2
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/exporters/ods.py +2 -2
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/exporters/templates.py +2 -2
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/exporters/xls.py +3 -3
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/exporters/xlsx.py +2 -2
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/exporters/yaml.py +3 -4
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/format.py +71 -21
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/importers/base.py +6 -10
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/lsp/document.py +7 -3
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/metacommands/debug.py +2 -2
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/metacommands/io_fileops.py +6 -6
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/metacommands/upsert.py +2 -5
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/models.py +11 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/utils/errors.py +16 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/cli/test_cli_surface.py +1 -1
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/cli/test_lint.py +19 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/db/test_copy_new.py +37 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/db/test_duckdb.py +3 -1
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/db/test_new_table_checks.py +24 -1
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/integration/test_postgres.py +26 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/lsp/test_document.py +10 -0
- execsql2-2.28.0/tests/lsp/test_lsp_command.py +71 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/lsp/test_server.py +9 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/metacommands/test_metacommands_fileops_extra.py +4 -2
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/metacommands/test_pg_upsert.py +1 -1
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/test_format.py +60 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/utils/test_errors.py +27 -0
- {execsql2-2.27.0 → execsql2-2.28.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.28.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/.github/dependabot.yml +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/.github/workflows/ci-cd.yml +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/.gitignore +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/.pre-commit-config.yaml +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/.pre-commit-hooks.yaml +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/.python-version +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/.readthedocs.yaml +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/CONTRIBUTING.md +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/LICENSE.txt +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/NOTICE +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/SECURITY.md +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/about/contributors.md +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/about/copyright.md +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/api/cli.md +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/api/db.md +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/api/exporters.md +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/api/importers.md +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/api/index.md +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/api/metacommands.md +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/dev/adding_db_adapters.md +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/dev/adding_exporters.md +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/dev/adding_importers.md +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/dev/adding_metacommands.md +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/dev/architecture.md +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/dev/releasing.md +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/getting-started/installation.md +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/getting-started/requirements.md +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/guides/debugging.md +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/guides/documentation.md +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/guides/encoding.md +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/guides/examples.md +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/guides/logging.md +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/guides/sql_syntax.md +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/guides/usage.md +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/guides/using_scripts.md +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/images/Compare_planets.png +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/images/actions.png +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/images/actions2.png +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/images/checkboxes.png +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/images/connect.b64 +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/images/connect.png +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/images/create_conf.png +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/images/data_error1_screenshot.jpg +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/images/entry_form.png +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/images/execsql_console.png +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/images/execsql_logo_01.png +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/images/fatals.png +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/images/logo_small.png +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/images/pause_terminal.png +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/images/pause_terminal_sm.b64 +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/images/pause_terminal_sm.png +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/images/prompt_compare.png +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/images/set_build_commands.jpg +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/images/unit_conversions.b64 +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/images/unit_conversions_029.png +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/images/unmatched.png +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/images/vim_execsql_highlight.png +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/index.md +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/reference/configuration.md +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/reference/metacommands.md +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/reference/security.md +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/docs/reference/substitution_vars.md +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/extras/plugin-template/README.md +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/extras/plugin-template/pyproject.toml +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/extras/plugin-template/src/execsql_plugin_YOURNAME/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/extras/plugin-template/tests/test_plugin.py.example +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/extras/vscode-execsql/.vscodeignore +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/extras/vscode-execsql/LICENSE.txt +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/extras/vscode-execsql/extension.js +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/extras/vscode-execsql/icon.png +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/extras/vscode-execsql/package-lock.json +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/extras/vscode-execsql/package.json +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/extras/vscode-execsql/syntaxes/execsql.tmLanguage.json +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/justfile +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/scripts/benchmarks/README.md +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/scripts/benchmarks/results/.gitignore +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/scripts/benchmarks/run_benchmarks.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/scripts/generate_vscode_grammar.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/__main__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/api.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/cli/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/cli/application.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/cli/commands/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/cli/commands/config.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/cli/commands/init.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/cli/commands/lint.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/cli/commands/listing.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/cli/commands/run.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/cli/dispatch.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/cli/dsn.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/cli/help.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/cli/options.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/cli/run.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/config.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/config_validation.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/data/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/data/execsql.conf.template +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/data/script_header.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/db/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/db/factory.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/db/sqlite.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/db/tiers.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/debug/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/debug/repl.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/exceptions.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/exporters/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/exporters/base.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/exporters/delimited.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/exporters/feather.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/exporters/html.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/exporters/json.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/exporters/latex.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/exporters/markdown.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/exporters/parquet.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/exporters/pretty.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/exporters/protocol.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/exporters/raw.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/exporters/sqlite.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/exporters/values.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/exporters/xml.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/exporters/zip.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/gui/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/gui/base.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/gui/console.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/gui/desktop.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/gui/tui.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/importers/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/importers/csv.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/importers/feather.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/importers/json.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/importers/ods.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/importers/xls.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/interactive.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/lsp/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/lsp/code_actions.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/lsp/completion.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/lsp/diagnostics.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/lsp/folding.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/lsp/formatting.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/lsp/hover.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/lsp/navigation.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/lsp/rename.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/lsp/server.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/manifest.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/metacommands/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/metacommands/conditions.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/metacommands/connect.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/metacommands/control.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/metacommands/data.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/metacommands/dispatch.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/metacommands/effects.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/metacommands/io.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/metacommands/io_export.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/metacommands/io_import.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/metacommands/io_write.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/metacommands/prompt.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/metacommands/reference.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/metacommands/script_ext.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/metacommands/system.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/parser.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/plugins.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/py.typed +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/script/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/script/ast.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/script/control.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/script/engine.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/script/executor.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/script/parser.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/script/variables.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/state.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/state.pyi +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/types.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/utils/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/utils/auth.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/utils/color.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/utils/crypto.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/utils/datetime.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/utils/fileio.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/utils/gui.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/utils/mail.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/utils/numeric.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/utils/regex.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/utils/strings.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/src/execsql/utils/timer.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/templates/README.md +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/templates/config_settings.sqlite +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/templates/example_config_prompt.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/templates/make_config_db.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/templates/md_compare.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/templates/md_glossary.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/templates/md_upsert.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/templates/pg_compare.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/templates/pg_glossary.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/templates/pg_upsert.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/templates/script_template.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/templates/ss_compare.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/templates/ss_glossary.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/templates/ss_upsert.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/cli/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/cli/test_cli.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/cli/test_cli_e2e.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/cli/test_cli_run.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/cli/test_commands.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/cli/test_config_validate.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/cli/test_dispatch.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/cli/test_error_location.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/cli/test_init.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/cli/test_lint_fixtures.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/cli/test_log_exit_record.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/cli/test_manifest.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/cli/test_ping.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/cli/test_profile.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/cli/test_sigterm.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/cli/test_undeclared_imports.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/conftest.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/data/formatter_shapes/comment_anchoring.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/data/formatter_shapes/escapes_and_literals.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/data/formatter_shapes/substitution_and_metacommands.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/data/formatter_shapes/unparsable_sql.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/data/lint/conditions_and_blocks.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/data/lint/does_not_parse.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/data/lint/every_metacommand.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/data/lint/invalid.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/db/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/db/test_access_windows.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/db/test_base.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/db/test_db_adapters_mocked.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/db/test_db_adapters_mocked_extra.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/db/test_dsn.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/db/test_factory.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/db/test_factory_postgres_encoding.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/db/test_import_atomic.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/db/test_import_replacement_quoted.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/db/test_import_short_rows.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/db/test_import_timezones.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/db/test_mysql_case_folding.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/db/test_mysql_inprocess.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/db/test_oracle_driver.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/db/test_postgres.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/db/test_postgres_inprocess.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/db/test_roundtrip_adapters.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/db/test_sqlite.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/db/test_sqlite_extra.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/db/test_sqlserver_inprocess.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/db/test_tiers.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/db/test_transactions.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/exporters/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/exporters/conftest.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/exporters/test_base.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/exporters/test_db.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/exporters/test_delimited.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/exporters/test_duckdb_exporter.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/exporters/test_exporters.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/exporters/test_feather.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/exporters/test_html_extended.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/exporters/test_html_latex.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/exporters/test_json.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/exporters/test_json_extended.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/exporters/test_latex_extended.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/exporters/test_markdown.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/exporters/test_ods.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/exporters/test_ods_export.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/exporters/test_parquet.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/exporters/test_pretty_extended.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/exporters/test_raw_extended.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/exporters/test_roundtrip_columnar.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/exporters/test_roundtrip_dbfile.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/exporters/test_roundtrip_delimited.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/exporters/test_roundtrip_json.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/exporters/test_roundtrip_latex_xml.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/exporters/test_roundtrip_markup.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/exporters/test_roundtrip_ods.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/exporters/test_roundtrip_pretty_raw.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/exporters/test_roundtrip_template.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/exporters/test_roundtrip_values.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/exporters/test_roundtrip_xlsx.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/exporters/test_sqlite_exporter.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/exporters/test_templates.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/exporters/test_templates_extended.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/exporters/test_values_extended.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/exporters/test_xls_xlsx.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/exporters/test_xlsx.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/exporters/test_xml.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/exporters/test_yaml.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/exporters/test_zip.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/gui/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/gui/test_backends.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/gui/test_backends_extended.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/gui/test_compare_stats.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/gui/test_compute_row_diffs.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/gui/test_desktop_dialogs.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/gui/test_format_table.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/gui/test_tui_pilot.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/gui/test_tui_pilot_complex.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/gui/test_utils_gui_extended.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/importers/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/importers/conftest.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/importers/test_base_extended.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/importers/test_csv_blank_lines.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/importers/test_csv_edge_cases.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/importers/test_csv_importer.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/importers/test_feather_importer.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/importers/test_json_importer.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/importers/test_ods_importer.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/importers/test_roundtrip_csv.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/importers/test_xls_importer.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/integration/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/integration/conftest.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/integration/test_dsn.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/integration/test_duckdb.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/integration/test_mysql.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/integration/test_sqlite.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/live_db.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/lsp/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/lsp/test_code_actions.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/lsp/test_completion.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/lsp/test_diagnostics.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/lsp/test_folding.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/lsp/test_formatting.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/lsp/test_hover.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/lsp/test_navigation.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/lsp/test_rename.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/metacommands/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/metacommands/test_assert.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/metacommands/test_breakpoint.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/metacommands/test_conditions_extra.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/metacommands/test_connect.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/metacommands/test_io_export.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/metacommands/test_io_import.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/metacommands/test_metacommands.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/metacommands/test_metacommands_connect.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/metacommands/test_metacommands_data.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/metacommands/test_metacommands_extended.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/metacommands/test_metacommands_io.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/metacommands/test_metacommands_io_write_extra.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/metacommands/test_metacommands_script_ext.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/metacommands/test_metacommands_system.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/metacommands/test_metacommands_system_extra.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/metacommands/test_prompt.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/metacommands/test_row_count.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/metacommands/test_show_scripts.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/scripts/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/scripts/fixtures/audit_lint_bad.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/scripts/fixtures/audit_smoke/sample.json +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/scripts/fixtures/audit_smoke/sample.jsonl +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/scripts/fixtures/audit_smoke.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/scripts/fixtures/config_runtime.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/scripts/fixtures/control_flow.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/scripts/fixtures/counters_and_locals.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/scripts/fixtures/error_handling.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/scripts/fixtures/import_options/has_comments.csv +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/scripts/fixtures/import_options.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/scripts/fixtures/includes/helper.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/scripts/fixtures/includes.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/scripts/fixtures/io_formats.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/scripts/fixtures/io_roundtrip.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/scripts/fixtures/multi_db.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/scripts/fixtures/parquet_feather_roundtrip.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/scripts/fixtures/parse_only/parse_tree.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/scripts/fixtures/predicates.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/scripts/fixtures/scripts.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/scripts/fixtures/smoke.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/scripts/fixtures/subroutine_loops.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/scripts/fixtures/subvars_advanced.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/scripts/fixtures/template_export/greeting.tmpl +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/scripts/fixtures/template_export.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/scripts/fixtures/timer.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/scripts/fixtures/transactions.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/scripts/fixtures/write_create_table.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/scripts/fixtures/xlsx_ods_roundtrip.sql +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/scripts/test_sql_scripts.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/security/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/security/test_env_detection.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/security/test_expansion_bomb.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/security/test_import_regex_hardening.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/security/test_log_redaction.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/security/test_log_secrets.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/security/test_odbc_injection.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/security/test_path_containment.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/security/test_substitution_injection.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/security/test_zip_bomb.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/test_api.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/test_api_errors.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/test_api_extended.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/test_api_filewriter.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/test_api_isolation.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/test_api_subprocess.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/test_ast.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/test_ast_parser.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/test_bundled_templates.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/test_config.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/test_config_data.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/test_config_extended.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/test_date_inference.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/test_debug_repl.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/test_engine.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/test_error_messages.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/test_exceptions.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/test_executor.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/test_executor_inprocess.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/test_interactive.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/test_mail.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/test_models.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/test_package.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/test_parser.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/test_parser_params.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/test_plugins.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/test_reference.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/test_registry.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/test_script.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/test_state.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/test_tooling_versions.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/test_types.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/utils/__init__.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/utils/test_auth.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/utils/test_auth_extra.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/utils/test_crypto.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/utils/test_datetime.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/utils/test_encodedfile_context.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/utils/test_errors_extra.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/utils/test_fileio.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/utils/test_fileio_extra.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/utils/test_keyring_rules.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/utils/test_mail_tls.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/utils/test_numeric.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/utils/test_regex.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/utils/test_strings.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/utils/test_timer.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/tests/utils/test_timer_extra.py +0 -0
- {execsql2-2.27.0 → execsql2-2.28.0}/zensical.toml +0 -0
|
@@ -13,6 +13,29 @@ ______________________________________________________________________
|
|
|
13
13
|
|
|
14
14
|
______________________________________________________________________
|
|
15
15
|
|
|
16
|
+
## [2.28.0] - 2026-10-13
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- **`execsql lsp --check`** reports whether the language server can start (exit 0) or what is missing (exit 1).
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- **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.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- **`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.
|
|
29
|
+
- **`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.
|
|
30
|
+
- **`execsql format`:** a statement kept as written after another on the same line no longer starts with a space.
|
|
31
|
+
- **`execsql format` without sqlglot** printed its `pip install` hint without the `[formatter]` extra.
|
|
32
|
+
- **`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.
|
|
33
|
+
- **`execsql lsp` with an incompatible pygls** names the installed pygls version instead of saying the `lsp` extra is missing.
|
|
34
|
+
- **`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.
|
|
35
|
+
- **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.
|
|
36
|
+
|
|
37
|
+
______________________________________________________________________
|
|
38
|
+
|
|
16
39
|
## [2.27.0] - 2026-10-12
|
|
17
40
|
|
|
18
41
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: execsql2
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.28.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.28.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,11 @@ 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. |
|
|
363
365
|
| 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
366
|
| `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
367
|
| `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.
|
|
368
|
+
| `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
369
|
| 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
370
|
| 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
371
|
| `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.28.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.28.0
|
|
162
162
|
hooks:
|
|
163
163
|
- id: execsql-lint
|
|
164
164
|
args: [--ignore, V002] # optional: any execsql lint option
|
|
@@ -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.28.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.28.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()
|
|
@@ -405,8 +405,13 @@ class Database(ABC):
|
|
|
405
405
|
rows = curs.fetchall()
|
|
406
406
|
return [d[0] for d in curs.description], rows
|
|
407
407
|
|
|
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.
|
|
408
|
+
def select_rowsource(self, sql: str, *, decode_bytes: bool = True) -> tuple[list[str], Generator]:
|
|
409
|
+
"""Execute *sql* and return ``(column_names, row_generator)`` for streaming large result sets.
|
|
410
|
+
|
|
411
|
+
A binary value (``bytes``) is decoded to text with the connection's
|
|
412
|
+
encoding, as the exporters expect, unless *decode_bytes* is false:
|
|
413
|
+
COPY passes false so binary columns reach the target unchanged.
|
|
414
|
+
"""
|
|
410
415
|
# Return 1) a list of column names, and 2) an iterable that yields rows.
|
|
411
416
|
curs = self.cursor()
|
|
412
417
|
try:
|
|
@@ -433,7 +438,7 @@ class Database(ABC):
|
|
|
433
438
|
break
|
|
434
439
|
else:
|
|
435
440
|
for row in rows:
|
|
436
|
-
if self.encoding:
|
|
441
|
+
if self.encoding and decode_bytes:
|
|
437
442
|
yield [
|
|
438
443
|
c.decode(self.encoding, "backslashreplace") if isinstance(c, bytes) else c
|
|
439
444
|
for c in row
|
|
@@ -501,6 +506,15 @@ class Database(ABC):
|
|
|
501
506
|
"""The schema (MySQL: database) an unqualified table name is created in, or ``None`` if unknown."""
|
|
502
507
|
return None
|
|
503
508
|
|
|
509
|
+
def table_exists_where_created(self, table_name: str, schema_name: str | None = None) -> bool:
|
|
510
|
+
"""Whether *table_name* exists where a ``CREATE TABLE`` on this connection would put it.
|
|
511
|
+
|
|
512
|
+
The same as :meth:`table_exists`, except on a connection that sees
|
|
513
|
+
tables of other databases (DuckDB ``ATTACH``): only the current
|
|
514
|
+
database counts there.
|
|
515
|
+
"""
|
|
516
|
+
return self.table_exists(table_name, schema_name)
|
|
517
|
+
|
|
504
518
|
def _query_current_schema(self, sql: str) -> str | None:
|
|
505
519
|
with self._cursor() as curs:
|
|
506
520
|
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__(
|
|
@@ -14,7 +14,7 @@ from typing import Any
|
|
|
14
14
|
|
|
15
15
|
from execsql.db.base import Database, statement_start
|
|
16
16
|
from execsql.exceptions import ErrInfo
|
|
17
|
-
from execsql.utils.errors import exception_desc, fatal_error
|
|
17
|
+
from execsql.utils.errors import exception_desc, fatal_error, install_hint
|
|
18
18
|
import execsql.state as _state
|
|
19
19
|
|
|
20
20
|
__all__ = ["DuckDBDatabase"]
|
|
@@ -62,7 +62,7 @@ class DuckDBDatabase(Database):
|
|
|
62
62
|
try:
|
|
63
63
|
import duckdb # noqa: F401
|
|
64
64
|
except Exception:
|
|
65
|
-
fatal_error("The duckdb module is required.")
|
|
65
|
+
fatal_error(f"The duckdb module is required to use DuckDB. {install_hint('duckdb')}")
|
|
66
66
|
from execsql.types import dbt_duckdb
|
|
67
67
|
|
|
68
68
|
super().__init__(
|
|
@@ -167,6 +167,18 @@ class DuckDBDatabase(Database):
|
|
|
167
167
|
self.rollback()
|
|
168
168
|
raise
|
|
169
169
|
|
|
170
|
+
def table_exists_where_created(self, table_name: str, schema_name: str | None = None) -> bool:
|
|
171
|
+
"""Whether *table_name* exists in the current database (not an ``ATTACH``ed one)."""
|
|
172
|
+
# information_schema.tables lists the tables of every attached database.
|
|
173
|
+
sql = "select table_name from information_schema.tables where table_name = ? and table_catalog = current_database()"
|
|
174
|
+
params: list = [table_name]
|
|
175
|
+
if schema_name:
|
|
176
|
+
sql += " and table_schema = ?"
|
|
177
|
+
params.append(schema_name)
|
|
178
|
+
with self._cursor() as curs:
|
|
179
|
+
curs.execute(sql, params)
|
|
180
|
+
return len(curs.fetchall()) > 0
|
|
181
|
+
|
|
170
182
|
def view_exists(self, view_name: str, schema_name: str | None = None) -> bool:
|
|
171
183
|
"""Return True if the named view exists in the DuckDB database."""
|
|
172
184
|
# DuckDB information_schema has no 'views' table; views are listed in 'tables'
|
|
@@ -11,7 +11,7 @@ via the ``firebird-driver`` package. Corresponds to ``-t f`` on the CLI.
|
|
|
11
11
|
from execsql.db.base import Database
|
|
12
12
|
from execsql.db.tiers import SupportTier
|
|
13
13
|
from execsql.exceptions import ErrInfo
|
|
14
|
-
from execsql.utils.errors import exception_desc, fatal_error
|
|
14
|
+
from execsql.utils.errors import exception_desc, fatal_error, install_hint
|
|
15
15
|
from execsql.utils.auth import (
|
|
16
16
|
begin_login,
|
|
17
17
|
clear_stored_password,
|
|
@@ -46,8 +46,7 @@ class FirebirdDatabase(Database):
|
|
|
46
46
|
from firebird import driver as firebird_lib # noqa: F401
|
|
47
47
|
except Exception:
|
|
48
48
|
fatal_error(
|
|
49
|
-
"The firebird-driver module is required to connect to Firebird.
|
|
50
|
-
"See https://pypi.org/project/firebird-driver/",
|
|
49
|
+
f"The firebird-driver module is required to connect to Firebird. {install_hint('firebird')}",
|
|
51
50
|
)
|
|
52
51
|
from execsql.types import dbt_firebird
|
|
53
52
|
|
|
@@ -12,7 +12,7 @@ from typing import Any, cast
|
|
|
12
12
|
|
|
13
13
|
from execsql.db.base import Database
|
|
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,
|
|
@@ -81,7 +81,7 @@ class MySQLDatabase(Database):
|
|
|
81
81
|
import pymysql as mysql_lib # noqa: F401
|
|
82
82
|
except Exception:
|
|
83
83
|
fatal_error(
|
|
84
|
-
"The pymysql module is required to connect to MySQL.
|
|
84
|
+
f"The pymysql module is required to connect to MySQL. {install_hint('mysql')}",
|
|
85
85
|
)
|
|
86
86
|
from execsql.types import dbt_mysql
|
|
87
87
|
|
|
@@ -14,7 +14,7 @@ from typing import Any
|
|
|
14
14
|
from execsql.db.base import Database
|
|
15
15
|
from execsql.db.tiers import SupportTier
|
|
16
16
|
from execsql.exceptions import ErrInfo
|
|
17
|
-
from execsql.utils.errors import exception_desc, fatal_error
|
|
17
|
+
from execsql.utils.errors import exception_desc, fatal_error, install_hint
|
|
18
18
|
from execsql.utils.auth import (
|
|
19
19
|
begin_login,
|
|
20
20
|
clear_stored_password,
|
|
@@ -76,7 +76,7 @@ class OracleDatabase(Database):
|
|
|
76
76
|
) -> None:
|
|
77
77
|
if _oracle_driver() is None:
|
|
78
78
|
fatal_error(
|
|
79
|
-
|
|
79
|
+
f"The oracledb module is required to connect to Oracle. {install_hint('oracle')}",
|
|
80
80
|
)
|
|
81
81
|
from execsql.types import dbt_oracle
|
|
82
82
|
|
|
@@ -164,12 +164,12 @@ class OracleDatabase(Database):
|
|
|
164
164
|
else:
|
|
165
165
|
return super().select_data(sql)
|
|
166
166
|
|
|
167
|
-
def select_rowsource(self, sql: str) -> Any:
|
|
167
|
+
def select_rowsource(self, sql: str, *, decode_bytes: bool = True) -> Any:
|
|
168
168
|
"""Return column names and an iterable that yields rows one at a time."""
|
|
169
169
|
if sql[-1:] == ";":
|
|
170
|
-
return super().select_rowsource(sql[:-1])
|
|
170
|
+
return super().select_rowsource(sql[:-1], decode_bytes=decode_bytes)
|
|
171
171
|
else:
|
|
172
|
-
return super().select_rowsource(sql)
|
|
172
|
+
return super().select_rowsource(sql, decode_bytes=decode_bytes)
|
|
173
173
|
|
|
174
174
|
def select_rowdict(self, sql: str) -> Any:
|
|
175
175
|
"""Return column names and an iterable that yields rows as dictionaries."""
|