execsql2 2.13.1__tar.gz → 2.13.2__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.13.1 → execsql2-2.13.2}/CHANGELOG.md +17 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/PKG-INFO +1 -1
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/about/divergence.md +15 -15
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/getting-started/syntax.md +6 -1
- {execsql2-2.13.1 → execsql2-2.13.2}/pyproject.toml +2 -2
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/cli/lint.py +277 -92
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/cli/test_lint.py +270 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/uv.lock +1 -1
- {execsql2-2.13.1 → execsql2-2.13.2}/.claude/agents/dba.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/.claude/agents/herald.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/.claude/agents/inspector.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/.claude/agents/liaison.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/.claude/agents/oracle.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/.claude/agents/patcher.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/.claude/agents/qa.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/.claude/agents/scribe.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/.claude/commands/code-oracle.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/.claude/commands/migrate.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/.claude/commands/review-changes.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/.claude/commands/test-module.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/.claude/commands/update-changelog.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/.claude/commands/where-is.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/.claude/project_context.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/.claude/state/status.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/.github/workflows/ci-cd.yml +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/.gitignore +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/.pre-commit-config.yaml +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/.pre-commit-hooks.yaml +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/.python-version +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/.readthedocs.yaml +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/CLAUDE.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/CONTRIBUTING.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/LICENSE.txt +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/NOTICE +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/README.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/SECURITY.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/about/contributors.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/about/copyright.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/api/cli.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/api/db.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/api/exporters.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/api/importers.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/api/index.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/api/metacommands.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/dev/adding_db_adapters.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/dev/adding_exporters.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/dev/adding_importers.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/dev/adding_metacommands.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/dev/architecture.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/getting-started/installation.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/getting-started/requirements.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/guides/debugging.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/guides/documentation.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/guides/encoding.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/guides/examples.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/guides/formatter.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/guides/logging.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/guides/sql_syntax.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/guides/usage.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/guides/using_scripts.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/images/Compare_planets.png +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/images/actions.png +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/images/actions2.png +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/images/checkboxes.png +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/images/connect.b64 +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/images/connect.png +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/images/create_conf.png +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/images/data_error1_screenshot.jpg +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/images/entry_form.png +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/images/execsql_console.png +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/images/execsql_logo_01.png +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/images/fatals.png +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/images/logo_small.png +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/images/pause_terminal.png +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/images/pause_terminal_sm.b64 +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/images/pause_terminal_sm.png +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/images/prompt_compare.png +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/images/set_build_commands.jpg +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/images/unit_conversions.b64 +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/images/unit_conversions_029.png +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/images/unmatched.png +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/images/vim_execsql_highlight.png +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/index.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/reference/configuration.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/reference/metacommands.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/reference/security.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/docs/reference/substitution_vars.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/extras/vscode-execsql/README.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/extras/vscode-execsql/package.json +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/extras/vscode-execsql/syntaxes/execsql.tmLanguage.json +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/justfile +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/scripts/generate_vscode_grammar.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/__init__.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/__main__.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/cli/__init__.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/cli/dsn.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/cli/help.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/cli/run.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/config.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/constants.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/db/__init__.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/db/access.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/db/base.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/db/dsn.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/db/duckdb.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/db/factory.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/db/firebird.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/db/mysql.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/db/oracle.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/db/postgres.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/db/sqlite.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/db/sqlserver.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/debug/__init__.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/debug/repl.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/exceptions.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/exporters/__init__.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/exporters/base.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/exporters/delimited.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/exporters/duckdb.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/exporters/feather.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/exporters/html.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/exporters/json.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/exporters/latex.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/exporters/markdown.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/exporters/ods.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/exporters/parquet.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/exporters/pretty.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/exporters/protocol.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/exporters/raw.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/exporters/sqlite.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/exporters/templates.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/exporters/values.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/exporters/xls.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/exporters/xlsx.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/exporters/xml.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/exporters/yaml.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/exporters/zip.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/format.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/gui/__init__.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/gui/base.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/gui/console.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/gui/desktop.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/gui/tui.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/importers/__init__.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/importers/base.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/importers/csv.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/importers/feather.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/importers/json.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/importers/ods.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/importers/xls.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/metacommands/__init__.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/metacommands/conditions.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/metacommands/connect.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/metacommands/control.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/metacommands/data.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/metacommands/debug.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/metacommands/dispatch.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/metacommands/io.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/metacommands/io_export.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/metacommands/io_fileops.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/metacommands/io_import.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/metacommands/io_write.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/metacommands/prompt.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/metacommands/script_ext.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/metacommands/system.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/metacommands/upsert.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/models.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/parser.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/py.typed +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/script/__init__.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/script/control.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/script/engine.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/script/variables.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/state.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/types.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/utils/__init__.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/utils/auth.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/utils/crypto.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/utils/datetime.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/utils/errors.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/utils/fileio.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/utils/gui.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/utils/mail.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/utils/numeric.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/utils/regex.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/utils/strings.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/src/execsql/utils/timer.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/templates/README.md +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/templates/config_settings.sqlite +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/templates/example_config_prompt.sql +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/templates/execsql.conf +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/templates/make_config_db.sql +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/templates/md_compare.sql +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/templates/md_glossary.sql +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/templates/md_upsert.sql +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/templates/pg_compare.sql +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/templates/pg_glossary.sql +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/templates/pg_upsert.sql +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/templates/script_template.sql +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/templates/ss_compare.sql +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/templates/ss_glossary.sql +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/templates/ss_upsert.sql +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/__init__.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/cli/__init__.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/cli/test_cli.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/cli/test_cli_e2e.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/cli/test_cli_run.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/cli/test_ping.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/cli/test_profile.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/conftest.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/db/__init__.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/db/test_base.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/db/test_duckdb.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/db/test_factory.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/db/test_postgres.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/db/test_sqlite.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/db/test_sqlite_extra.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/exporters/__init__.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/exporters/test_base.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/exporters/test_db.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/exporters/test_delimited.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/exporters/test_duckdb_exporter.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/exporters/test_exporters.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/exporters/test_feather.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/exporters/test_html_latex.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/exporters/test_json.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/exporters/test_markdown.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/exporters/test_ods.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/exporters/test_parquet.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/exporters/test_sqlite_exporter.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/exporters/test_templates.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/exporters/test_xls_xlsx.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/exporters/test_xlsx.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/exporters/test_xml.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/exporters/test_yaml.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/exporters/test_zip.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/gui/__init__.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/gui/test_backends.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/importers/__init__.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/importers/test_csv_importer.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/importers/test_feather_importer.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/importers/test_json_importer.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/importers/test_ods_importer.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/importers/test_xls_importer.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/integration/__init__.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/integration/conftest.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/integration/test_dsn.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/integration/test_duckdb.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/integration/test_mysql.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/integration/test_postgres.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/integration/test_sqlite.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/metacommands/__init__.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/metacommands/test_assert.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/metacommands/test_breakpoint.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/metacommands/test_connect.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/metacommands/test_io_export.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/metacommands/test_io_import.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/metacommands/test_metacommands.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/metacommands/test_metacommands_connect.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/metacommands/test_metacommands_data.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/metacommands/test_metacommands_extended.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/metacommands/test_metacommands_fileops_extra.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/metacommands/test_metacommands_io.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/metacommands/test_metacommands_io_write_extra.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/metacommands/test_metacommands_script_ext.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/metacommands/test_metacommands_system.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/metacommands/test_metacommands_system_extra.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/metacommands/test_pg_upsert.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/metacommands/test_row_count.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/test_config.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/test_config_data.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/test_constants.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/test_engine.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/test_error_messages.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/test_exceptions.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/test_format.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/test_mail.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/test_models.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/test_package.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/test_parser.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/test_registry.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/test_script.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/test_state.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/test_types.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/utils/__init__.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/utils/test_auth.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/utils/test_auth_extra.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/utils/test_crypto.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/utils/test_datetime.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/utils/test_errors.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/utils/test_errors_extra.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/utils/test_fileio.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/utils/test_fileio_extra.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/utils/test_numeric.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/utils/test_regex.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/utils/test_strings.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/utils/test_timer.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/tests/utils/test_timer_extra.py +0 -0
- {execsql2-2.13.1 → execsql2-2.13.2}/zensical.toml +0 -0
|
@@ -13,6 +13,23 @@ ______________________________________________________________________
|
|
|
13
13
|
|
|
14
14
|
______________________________________________________________________
|
|
15
15
|
|
|
16
|
+
## [2.13.2] - 2026-04-06
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- `--lint` static analysis improvements:
|
|
21
|
+
- Track `SUB_EMPTY`, `SUB_ADD`, `SUB_APPEND`, and `SUBDATA` as variable definitions, eliminating false undefined-variable warnings.
|
|
22
|
+
- Descend into named script blocks via `EXECUTE SCRIPT` / `EXEC SCRIPT` / `RUN SCRIPT` so variables defined inside are visible to the caller.
|
|
23
|
+
- Two-pass variable collection: definition order no longer matters. Variables can be referenced before their SUB definition without false warnings.
|
|
24
|
+
- Read `SUB_INI` INI files at lint time and register section keys as defined variables.
|
|
25
|
+
- Auto-discover built-in system variables by scanning installed source instead of a hand-maintained list.
|
|
26
|
+
- Exclude `$COUNTER_N` variables from undefined-variable warnings.
|
|
27
|
+
- Warn when `EXECUTE SCRIPT` targets a non-existent script block (respects `IF EXISTS`).
|
|
28
|
+
- Eliminate duplicate warnings for script blocks reached via multiple execution paths.
|
|
29
|
+
- Sort errors before warnings, both by line number. Pad location columns for alignment.
|
|
30
|
+
|
|
31
|
+
______________________________________________________________________
|
|
32
|
+
|
|
16
33
|
## [2.13.1] - 2026-04-04
|
|
17
34
|
|
|
18
35
|
### Changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: execsql2
|
|
3
|
-
Version: 2.13.
|
|
3
|
+
Version: 2.13.2
|
|
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
|
|
@@ -13,21 +13,21 @@ ______________________________________________________________________
|
|
|
13
13
|
|
|
14
14
|
### CLI Options
|
|
15
15
|
|
|
16
|
-
| Flag | Description
|
|
17
|
-
| ------------------------------- |
|
|
18
|
-
| `--version` | Print version and exit (Rich-formatted).
|
|
19
|
-
| `-c` / `--command` | Execute an inline SQL or metacommand string instead of a script file.
|
|
20
|
-
| `--dsn` / `--connection-string` | Accept a standard database URL (e.g. `postgresql://user:pass@host/db`). Supports `postgresql`, `mysql`, `mssql`, `oracle`, `firebird`, `sqlite`, and `duckdb` schemes.
|
|
21
|
-
| `--output-dir` | Set a default base directory for export output files.
|
|
22
|
-
| `--progress` | Show a Rich progress bar during long-running IMPORT operations.
|
|
23
|
-
| `--dump-keywords` | Emit all metacommand keywords, conditionals, config options, and export formats as structured JSON.
|
|
24
|
-
| `--gui-framework` | Select GUI backend: `tkinter` (default) or `textual` (terminal UI).
|
|
25
|
-
| `--debug` | Start in step-through debug mode. The debug REPL pauses before each statement, as if `BREAKPOINT` were at the top with `.next` always active.
|
|
26
|
-
| `--dry-run` | Parse the script and print the full command list without connecting to a database or executing anything. Substitution variables already populated at parse time (env vars, `--assign-arg` values, built-in start-time vars) are expanded in the output; execution-time variables (`$DB_NAME`, `$CURRENT_TIME`, etc.) remain unexpanded.
|
|
27
|
-
| `--profile` | Record wall-clock time for each SQL and metacommand statement. After the script completes, print a summary table sorted by elapsed time (descending), showing time, percentage of total, source location, command type, and a command preview.
|
|
28
|
-
| `--profile-limit N` | Number of top statements to display in the `--profile` summary (default: 20). Remaining statements are counted and noted in the output footer.
|
|
29
|
-
| `--ping` | Test database connectivity and exit. Connects using the supplied connection parameters, queries for the server version, and prints a one-line success message (exit 0) or the error (exit 1). No script file argument is required.
|
|
30
|
-
| `--lint` | Parse the script and perform static analysis without connecting to a database. Reports unmatched IF/ENDIF, LOOP/END LOOP, and BEGIN BATCH/END BATCH blocks (errors)
|
|
16
|
+
| Flag | Description |
|
|
17
|
+
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
18
|
+
| `--version` | Print version and exit (Rich-formatted). |
|
|
19
|
+
| `-c` / `--command` | Execute an inline SQL or metacommand string instead of a script file. |
|
|
20
|
+
| `--dsn` / `--connection-string` | Accept a standard database URL (e.g. `postgresql://user:pass@host/db`). Supports `postgresql`, `mysql`, `mssql`, `oracle`, `firebird`, `sqlite`, and `duckdb` schemes. |
|
|
21
|
+
| `--output-dir` | Set a default base directory for export output files. |
|
|
22
|
+
| `--progress` | Show a Rich progress bar during long-running IMPORT operations. |
|
|
23
|
+
| `--dump-keywords` | Emit all metacommand keywords, conditionals, config options, and export formats as structured JSON. |
|
|
24
|
+
| `--gui-framework` | Select GUI backend: `tkinter` (default) or `textual` (terminal UI). |
|
|
25
|
+
| `--debug` | Start in step-through debug mode. The debug REPL pauses before each statement, as if `BREAKPOINT` were at the top with `.next` always active. |
|
|
26
|
+
| `--dry-run` | Parse the script and print the full command list without connecting to a database or executing anything. Substitution variables already populated at parse time (env vars, `--assign-arg` values, built-in start-time vars) are expanded in the output; execution-time variables (`$DB_NAME`, `$CURRENT_TIME`, etc.) remain unexpanded. |
|
|
27
|
+
| `--profile` | Record wall-clock time for each SQL and metacommand statement. After the script completes, print a summary table sorted by elapsed time (descending), showing time, percentage of total, source location, command type, and a command preview. |
|
|
28
|
+
| `--profile-limit N` | Number of top statements to display in the `--profile` summary (default: 20). Remaining statements are counted and noted in the output footer. |
|
|
29
|
+
| `--ping` | Test database connectivity and exit. Connects using the supplied connection parameters, queries for the server version, and prints a one-line success message (exit 0) or the error (exit 1). No script file argument is required. |
|
|
30
|
+
| `--lint` | Parse the script and perform static analysis without connecting to a database. Reports unmatched IF/ENDIF, LOOP/END LOOP, and BEGIN BATCH/END BATCH blocks (errors); potentially undefined `!!$VAR!!` references (warnings); missing INCLUDE file targets (warnings); and unknown `EXECUTE SCRIPT` targets (warnings). Variable analysis uses two passes so definition order does not matter. The linter descends into named script blocks reached via `EXECUTE SCRIPT` / `EXEC SCRIPT` / `RUN SCRIPT`, reads `SUB_INI` INI files at lint time, recognizes `SUB_EMPTY` / `SUB_ADD` / `SUB_APPEND` / `SUBDATA` as definitions, suppresses false warnings for `$COUNTER_N`, and auto-discovers built-in system variables from the installed source. Exits 0 if no errors, 1 if errors found. |
|
|
31
31
|
|
|
32
32
|
### Export Formats
|
|
33
33
|
|
|
@@ -205,10 +205,15 @@ Valid encoding names can be displayed with the `-y` option. See also [Character
|
|
|
205
205
|
- **Unmatched IF / ENDIF** — open IF blocks with no closing ENDIF, or orphan ENDIF with no IF (error).
|
|
206
206
|
- **Unmatched LOOP / END LOOP** — open LOOP with no END LOOP, or orphan END LOOP (error).
|
|
207
207
|
- **Unmatched BEGIN BATCH / END BATCH** — open batch with no close, or orphan END BATCH (error).
|
|
208
|
-
- **Potentially undefined variables** — `!!$VAR!!` references where `$VAR` is not a built-in variable, not `$ARG_N`, and was not defined by a
|
|
208
|
+
- **Potentially undefined variables** — `!!$VAR!!` references where `$VAR` is not a built-in system variable, not `$ARG_N`, not `$COUNTER_N`, and was not defined by a `SUB`, `SUB_EMPTY`, `SUB_ADD`, `SUB_APPEND`, `SUBDATA`, or `SUB_INI` metacommand anywhere in the script (warning — may be a false-positive if the variable is set in a config file or via `-a`).
|
|
209
209
|
- **Missing INCLUDE files** — `INCLUDE` targets that do not exist on disk relative to the script's directory (warning; `INCLUDE IF EXISTS` targets are never checked).
|
|
210
|
+
- **Unknown EXECUTE SCRIPT target** — `EXECUTE SCRIPT` names a script block that was not defined in the file (warning; `EXECUTE SCRIPT IF EXISTS` targets are never warned about).
|
|
210
211
|
- **Empty script** — no commands found (warning).
|
|
211
212
|
|
|
213
|
+
Variable analysis uses two passes: the first collects every variable definition across the entire script and all named script blocks; the second performs the checks. Variables may be referenced before their definition point without producing false warnings. The linter also descends into named script blocks (`BEGIN SCRIPT … END SCRIPT`) reached via `EXECUTE SCRIPT`, `EXEC SCRIPT`, or `RUN SCRIPT`, so variables defined inside a block are visible to the caller. `SUB_INI` INI files are read at lint time to register their section keys as defined variables.
|
|
214
|
+
|
|
215
|
+
Built-in system variables are discovered automatically from the installed execsql source, so new variables added in future releases are recognized without any linter changes.
|
|
216
|
+
|
|
212
217
|
Exits 0 when no errors are found (warnings alone do not affect the exit code). Exits 1 when any errors are found.
|
|
213
218
|
|
|
214
219
|
```bash
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "execsql2"
|
|
7
|
-
version = "2.13.
|
|
7
|
+
version = "2.13.2"
|
|
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" }
|
|
@@ -161,7 +161,7 @@ skip-magic-trailing-comma = false
|
|
|
161
161
|
line-ending = "auto"
|
|
162
162
|
|
|
163
163
|
[tool.bumpversion]
|
|
164
|
-
current_version = "2.13.
|
|
164
|
+
current_version = "2.13.2"
|
|
165
165
|
commit = true
|
|
166
166
|
commit_args = "--no-verify"
|
|
167
167
|
tag = true
|
|
@@ -10,11 +10,18 @@ Checks performed
|
|
|
10
10
|
2. **Unmatched LOOP / END LOOP** — mismatched nesting depth (error).
|
|
11
11
|
3. **Unmatched BEGIN BATCH / END BATCH** — mismatched nesting depth (error).
|
|
12
12
|
4. **Potentially undefined variables** — ``!!$VAR!!`` tokens not preceded by a
|
|
13
|
-
``SUB``
|
|
14
|
-
built-in
|
|
15
|
-
|
|
13
|
+
``SUB`` (or ``SUB_EMPTY``, ``SUB_ADD``, ``SUB_APPEND``, ``SUBDATA``)
|
|
14
|
+
metacommand in the same parsed command list and not in the set of built-in
|
|
15
|
+
variables (warning). Note: ``SUB_INI`` and ``SELECT_SUB`` define variables
|
|
16
|
+
whose names are not statically knowable — those may produce false-positive
|
|
17
|
+
warnings.
|
|
18
|
+
5. **EXECUTE SCRIPT flow analysis** — when an ``EXECUTE SCRIPT <name>``
|
|
19
|
+
metacommand is encountered, the linter descends into the named script
|
|
20
|
+
block (if found in ``_state.savedscripts``) and merges any variables it
|
|
21
|
+
defines back into the caller's scope.
|
|
22
|
+
6. **Missing INCLUDE files** — INCLUDE target does not exist on disk relative
|
|
16
23
|
to the script directory (warning).
|
|
17
|
-
|
|
24
|
+
7. **Empty script** — no commands found (warning).
|
|
18
25
|
|
|
19
26
|
The function walks ``CommandList.cmdlist`` and also descends into any
|
|
20
27
|
``CommandList`` objects stored in ``_state.savedscripts`` (i.e. named scripts
|
|
@@ -67,6 +74,31 @@ _RX_END_BATCH = re.compile(r"^\s*END\s+BATCH\s*$", re.I)
|
|
|
67
74
|
# SUB <varname> <value> — defines a substitution variable
|
|
68
75
|
_RX_SUB = re.compile(r"^\s*SUB\s+(?P<name>[+~]?\w+)\s+", re.I)
|
|
69
76
|
|
|
77
|
+
# SUB_EMPTY <varname> — defines a variable with empty string
|
|
78
|
+
_RX_SUB_EMPTY = re.compile(r"^\s*SUB_EMPTY\s+(?P<name>[+~]?\w+)\s*$", re.I)
|
|
79
|
+
|
|
80
|
+
# SUB_ADD <varname> <expr> — increments a variable (implies it exists)
|
|
81
|
+
_RX_SUB_ADD = re.compile(r"^\s*SUB_ADD\s+(?P<name>[+~]?\w+)\s+", re.I)
|
|
82
|
+
|
|
83
|
+
# SUB_APPEND <varname> <text> — appends to a variable (implies it exists)
|
|
84
|
+
_RX_SUB_APPEND = re.compile(r"^\s*SUB_APPEND\s+(?P<name>[+~]?\w+)\s", re.I)
|
|
85
|
+
|
|
86
|
+
# SUBDATA <varname> <datasource> — defines a variable from a query result
|
|
87
|
+
_RX_SUBDATA = re.compile(r"^\s*SUBDATA\s+(?P<name>[+~]?\w+)\s+", re.I)
|
|
88
|
+
|
|
89
|
+
# SUB_INI [FILE] <filename> [SECTION] <section> — bulk-defines variables from INI file
|
|
90
|
+
_RX_SUB_INI = re.compile(
|
|
91
|
+
r'^\s*SUB_INI\s+(?:FILE\s+)?(?:"(?P<qfile>[^"]+)"|(?P<file>\S+))'
|
|
92
|
+
r"(?:\s+SECTION)?\s+(?P<section>\w+)\s*$",
|
|
93
|
+
re.I,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# EXECUTE SCRIPT / EXEC SCRIPT / RUN SCRIPT
|
|
97
|
+
_RX_EXEC_SCRIPT = re.compile(
|
|
98
|
+
r"^\s*(?:EXEC(?:UTE)?|RUN)\s+SCRIPT(?:\s+IF\s+EXISTS)?\s+(?P<script_id>\w+)",
|
|
99
|
+
re.I,
|
|
100
|
+
)
|
|
101
|
+
|
|
70
102
|
# INCLUDE <file>
|
|
71
103
|
_RX_INCLUDE = re.compile(
|
|
72
104
|
r"^\s*INCLUDE(?:\s+IF\s+EXISTS?)?\s+(?P<path>\S+.*?)\s*$",
|
|
@@ -76,67 +108,41 @@ _RX_INCLUDE = re.compile(
|
|
|
76
108
|
# Variable reference — !!name!! where name may start with $, @, &, ~, #, +
|
|
77
109
|
_RX_VAR_REF = re.compile(r"!!([$@&~#+]?\w+)!!", re.I)
|
|
78
110
|
|
|
79
|
-
# Built-in system variables
|
|
80
|
-
#
|
|
81
|
-
#
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
"CURRENT_ALIAS",
|
|
115
|
-
"AUTOCOMMIT_STATE",
|
|
116
|
-
"TIMER",
|
|
117
|
-
"DB_USER",
|
|
118
|
-
"DB_SERVER",
|
|
119
|
-
"DB_NAME",
|
|
120
|
-
"DB_NEED_PWD",
|
|
121
|
-
"RANDOM",
|
|
122
|
-
"UUID",
|
|
123
|
-
"VERSION1",
|
|
124
|
-
"VERSION2",
|
|
125
|
-
"VERSION3",
|
|
126
|
-
"CANCEL_HALT_STATE",
|
|
127
|
-
"ERROR_HALT_STATE",
|
|
128
|
-
"METACOMMAND_ERROR_HALT_STATE",
|
|
129
|
-
"CONSOLE_WAIT_WHEN_ERROR_HALT_STATE",
|
|
130
|
-
"CONSOLE_WAIT_WHEN_DONE_STATE",
|
|
131
|
-
"CURRENT_DBMS",
|
|
132
|
-
"CURRENT_DATABASE",
|
|
133
|
-
"SYSTEM_CMD_EXIT_STATUS",
|
|
134
|
-
# Connection-populated
|
|
135
|
-
"DB_FILE",
|
|
136
|
-
"DB_PORT",
|
|
137
|
-
# Counter variables (@@name) are always valid — skip validation
|
|
138
|
-
},
|
|
139
|
-
)
|
|
111
|
+
# Built-in system variables — extracted automatically from the installed
|
|
112
|
+
# ``execsql`` source by scanning for ``add_substitution("$NAME", ...)`` and
|
|
113
|
+
# ``register_lazy("$NAME", ...)`` calls. This avoids maintaining a hand-
|
|
114
|
+
# curated list that drifts out of sync when new system variables are added.
|
|
115
|
+
# Variable names are stored upper-case without the leading ``$``.
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _discover_builtin_vars() -> frozenset[str]:
|
|
119
|
+
"""Scan the execsql package source for ``$VARNAME`` system variables."""
|
|
120
|
+
import importlib.util
|
|
121
|
+
|
|
122
|
+
_rx_add_sub = re.compile(r'(?:(?<!\w)add_substitution|(?<!\w)sv)\s*\(\s*["\'](\$\w+)["\']')
|
|
123
|
+
_rx_lazy = re.compile(r'register_lazy\s*\(\s*["\'](\$\w+)["\']')
|
|
124
|
+
|
|
125
|
+
names: set[str] = set()
|
|
126
|
+
|
|
127
|
+
spec = importlib.util.find_spec("execsql")
|
|
128
|
+
if spec is None or spec.submodule_search_locations is None:
|
|
129
|
+
return frozenset(names)
|
|
130
|
+
|
|
131
|
+
pkg_dir = Path(spec.submodule_search_locations[0])
|
|
132
|
+
for src_file in pkg_dir.rglob("*.py"):
|
|
133
|
+
try:
|
|
134
|
+
text = src_file.read_text(encoding="utf-8")
|
|
135
|
+
except OSError:
|
|
136
|
+
continue
|
|
137
|
+
for m in _rx_add_sub.finditer(text):
|
|
138
|
+
names.add(m.group(1).lstrip("$").upper())
|
|
139
|
+
for m in _rx_lazy.finditer(text):
|
|
140
|
+
names.add(m.group(1).lstrip("$").upper())
|
|
141
|
+
|
|
142
|
+
return frozenset(names)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
_BUILTIN_VARS: frozenset[str] = _discover_builtin_vars()
|
|
140
146
|
|
|
141
147
|
|
|
142
148
|
# ---------------------------------------------------------------------------
|
|
@@ -159,23 +165,90 @@ def _warning(source: str, line_no: int, message: str) -> _Issue:
|
|
|
159
165
|
# ---------------------------------------------------------------------------
|
|
160
166
|
|
|
161
167
|
|
|
168
|
+
def _collect_defined_vars(
|
|
169
|
+
cmdlist: CommandList,
|
|
170
|
+
script_dir: Path | None,
|
|
171
|
+
defined_vars: set[str],
|
|
172
|
+
*,
|
|
173
|
+
_savedscripts: dict | None = None,
|
|
174
|
+
_visited_scripts: set[str] | None = None,
|
|
175
|
+
) -> None:
|
|
176
|
+
"""Pass 1: walk *cmdlist* and collect all variable definitions into *defined_vars*.
|
|
177
|
+
|
|
178
|
+
This populates the set with every variable name that could be defined at
|
|
179
|
+
runtime — ``SUB``, ``SUB_EMPTY``, ``SUB_ADD``, ``SUB_APPEND``,
|
|
180
|
+
``SUBDATA``, and ``SUB_INI`` (by reading the INI file on disk). It also
|
|
181
|
+
descends into ``EXECUTE SCRIPT`` targets to collect their definitions.
|
|
182
|
+
|
|
183
|
+
No issues are reported; structural checks and variable-reference validation
|
|
184
|
+
happen in pass 2 (:func:`_lint_cmdlist`).
|
|
185
|
+
"""
|
|
186
|
+
visited = _visited_scripts if _visited_scripts is not None else set()
|
|
187
|
+
|
|
188
|
+
for cmd in cmdlist.cmdlist:
|
|
189
|
+
if cmd.command_type == "sql":
|
|
190
|
+
continue
|
|
191
|
+
stmt = cmd.command.statement
|
|
192
|
+
|
|
193
|
+
# SUB <name> <value>
|
|
194
|
+
sub_m = _RX_SUB.match(stmt)
|
|
195
|
+
if sub_m:
|
|
196
|
+
defined_vars.add(sub_m.group("name").lstrip("+~").upper())
|
|
197
|
+
|
|
198
|
+
# SUB_EMPTY / SUB_ADD / SUB_APPEND / SUBDATA
|
|
199
|
+
for rx in (_RX_SUB_EMPTY, _RX_SUB_ADD, _RX_SUB_APPEND, _RX_SUBDATA):
|
|
200
|
+
m = rx.match(stmt)
|
|
201
|
+
if m:
|
|
202
|
+
defined_vars.add(m.group("name").lstrip("+~").upper())
|
|
203
|
+
break
|
|
204
|
+
|
|
205
|
+
# SUB_INI — read INI file keys
|
|
206
|
+
ini_m = _RX_SUB_INI.match(stmt)
|
|
207
|
+
if ini_m:
|
|
208
|
+
ini_file = ini_m.group("qfile") or ini_m.group("file")
|
|
209
|
+
ini_section = ini_m.group("section")
|
|
210
|
+
if ini_file and not _RX_VAR_REF.search(ini_file):
|
|
211
|
+
_read_ini_vars(ini_file, ini_section, script_dir, defined_vars)
|
|
212
|
+
|
|
213
|
+
# EXECUTE SCRIPT — descend into named script block
|
|
214
|
+
exec_m = _RX_EXEC_SCRIPT.match(stmt)
|
|
215
|
+
if exec_m and _savedscripts is not None:
|
|
216
|
+
script_id = exec_m.group("script_id").lower()
|
|
217
|
+
if script_id in _savedscripts and script_id not in visited:
|
|
218
|
+
visited.add(script_id)
|
|
219
|
+
_collect_defined_vars(
|
|
220
|
+
_savedscripts[script_id],
|
|
221
|
+
script_dir,
|
|
222
|
+
defined_vars,
|
|
223
|
+
_savedscripts=_savedscripts,
|
|
224
|
+
_visited_scripts=visited,
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
|
|
162
228
|
def _lint_cmdlist(
|
|
163
229
|
cmdlist: CommandList,
|
|
164
230
|
script_dir: Path | None,
|
|
165
231
|
defined_vars: set[str],
|
|
232
|
+
*,
|
|
233
|
+
_savedscripts: dict | None = None,
|
|
234
|
+
_visited_scripts: set[str] | None = None,
|
|
166
235
|
) -> list[_Issue]:
|
|
167
|
-
"""
|
|
236
|
+
"""Pass 2: lint a :class:`CommandList` for structural and variable issues.
|
|
168
237
|
|
|
169
238
|
Args:
|
|
170
239
|
cmdlist: The parsed command list to analyse.
|
|
171
240
|
script_dir: Directory of the top-level script file, used for resolving
|
|
172
241
|
relative INCLUDE paths. ``None`` for inline (``-c``) scripts.
|
|
173
|
-
defined_vars:
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
242
|
+
defined_vars: Set of variable names (without sigil) that have been
|
|
243
|
+
pre-collected by :func:`_collect_defined_vars`. This includes
|
|
244
|
+
*all* top-level and script-block definitions so that ordering
|
|
245
|
+
does not matter.
|
|
246
|
+
_savedscripts: Dictionary of named script blocks (from
|
|
247
|
+
``_state.savedscripts``). Passed explicitly so the function can
|
|
248
|
+
descend into EXECUTE SCRIPT targets.
|
|
249
|
+
_visited_scripts: Set of script IDs already descended into, shared
|
|
250
|
+
across recursive calls to prevent infinite recursion from circular
|
|
251
|
+
EXECUTE SCRIPT references.
|
|
179
252
|
|
|
180
253
|
Returns:
|
|
181
254
|
List of ``(severity, source, line_no, message)`` issue tuples.
|
|
@@ -191,6 +264,10 @@ def _lint_cmdlist(
|
|
|
191
264
|
batch_depth = 0
|
|
192
265
|
batch_open_locs: list[tuple[str, int]] = []
|
|
193
266
|
|
|
267
|
+
# Track which EXECUTE SCRIPT targets we've already descended into to
|
|
268
|
+
# prevent infinite recursion from circular script references.
|
|
269
|
+
visited_scripts: set[str] = _visited_scripts if _visited_scripts is not None else set()
|
|
270
|
+
|
|
194
271
|
for cmd in cmdlist.cmdlist:
|
|
195
272
|
src = cmd.source
|
|
196
273
|
lno = cmd.line_no
|
|
@@ -202,7 +279,7 @@ def _lint_cmdlist(
|
|
|
202
279
|
_check_var_ref(m.group(1), src, lno, defined_vars, issues)
|
|
203
280
|
continue
|
|
204
281
|
|
|
205
|
-
# Metacommand checks
|
|
282
|
+
# Metacommand checks — variable references
|
|
206
283
|
for m in _RX_VAR_REF.finditer(stmt):
|
|
207
284
|
_check_var_ref(m.group(1), src, lno, defined_vars, issues)
|
|
208
285
|
|
|
@@ -247,11 +324,27 @@ def _lint_cmdlist(
|
|
|
247
324
|
batch_depth -= 1
|
|
248
325
|
batch_open_locs.pop()
|
|
249
326
|
|
|
250
|
-
# --
|
|
251
|
-
|
|
252
|
-
if
|
|
253
|
-
|
|
254
|
-
|
|
327
|
+
# -- EXECUTE SCRIPT — descend into named script block --
|
|
328
|
+
exec_m = _RX_EXEC_SCRIPT.match(stmt)
|
|
329
|
+
if exec_m and _savedscripts is not None:
|
|
330
|
+
script_id = exec_m.group("script_id").lower()
|
|
331
|
+
if script_id not in _savedscripts:
|
|
332
|
+
# Warn unless it's EXECUTE SCRIPT IF EXISTS
|
|
333
|
+
if not re.search(r"\bIF\s+EXISTS\b", stmt, re.I):
|
|
334
|
+
issues.append(
|
|
335
|
+
_warning(src, lno, f"EXECUTE SCRIPT target not found: '{script_id}'"),
|
|
336
|
+
)
|
|
337
|
+
elif script_id not in visited_scripts:
|
|
338
|
+
visited_scripts.add(script_id)
|
|
339
|
+
sub_issues = _lint_cmdlist(
|
|
340
|
+
_savedscripts[script_id],
|
|
341
|
+
script_dir,
|
|
342
|
+
defined_vars,
|
|
343
|
+
_savedscripts=_savedscripts,
|
|
344
|
+
_visited_scripts=visited_scripts,
|
|
345
|
+
)
|
|
346
|
+
for sev, ssrc, slno, msg in sub_issues:
|
|
347
|
+
issues.append((sev, ssrc, slno, f"[script '{script_id}'] {msg}"))
|
|
255
348
|
|
|
256
349
|
# -- INCLUDE file existence --
|
|
257
350
|
inc_m = _RX_INCLUDE.match(stmt)
|
|
@@ -307,6 +400,10 @@ def _check_var_ref(
|
|
|
307
400
|
if re.match(r"^ARG_\d+$", name, re.I):
|
|
308
401
|
return
|
|
309
402
|
|
|
403
|
+
# $COUNTER_N is managed by CounterVars (@@counter metacommands)
|
|
404
|
+
if re.match(r"^COUNTER_\d+$", name, re.I):
|
|
405
|
+
return
|
|
406
|
+
|
|
310
407
|
# Built-in system variables
|
|
311
408
|
if name.upper() in _BUILTIN_VARS:
|
|
312
409
|
return
|
|
@@ -325,6 +422,35 @@ def _check_var_ref(
|
|
|
325
422
|
)
|
|
326
423
|
|
|
327
424
|
|
|
425
|
+
def _read_ini_vars(
|
|
426
|
+
ini_file: str,
|
|
427
|
+
section: str,
|
|
428
|
+
script_dir: Path | None,
|
|
429
|
+
defined_vars: set[str],
|
|
430
|
+
) -> None:
|
|
431
|
+
"""Read an INI file and register its section keys as defined variables.
|
|
432
|
+
|
|
433
|
+
Mirrors what ``SUB_INI`` does at runtime: reads a
|
|
434
|
+
:class:`~configparser.ConfigParser` section and defines each key as a
|
|
435
|
+
substitution variable. If the file does not exist or the section is
|
|
436
|
+
missing, silently does nothing (the runtime handler behaves the same way).
|
|
437
|
+
"""
|
|
438
|
+
from configparser import ConfigParser
|
|
439
|
+
|
|
440
|
+
p = Path(ini_file)
|
|
441
|
+
if not p.is_absolute() and script_dir is not None:
|
|
442
|
+
p = script_dir / p
|
|
443
|
+
|
|
444
|
+
if not p.exists():
|
|
445
|
+
return
|
|
446
|
+
|
|
447
|
+
cp = ConfigParser()
|
|
448
|
+
cp.read(p)
|
|
449
|
+
if cp.has_section(section):
|
|
450
|
+
for key, _value in cp.items(section):
|
|
451
|
+
defined_vars.add(key.upper())
|
|
452
|
+
|
|
453
|
+
|
|
328
454
|
def _check_include_path(
|
|
329
455
|
raw_path: str,
|
|
330
456
|
script_dir: Path | None,
|
|
@@ -393,19 +519,68 @@ def _lint_script(
|
|
|
393
519
|
return issues
|
|
394
520
|
|
|
395
521
|
script_dir = Path(script_path).resolve().parent if script_path else None
|
|
522
|
+
savedscripts: dict = getattr(_state, "savedscripts", {})
|
|
523
|
+
|
|
524
|
+
# ------------------------------------------------------------------
|
|
525
|
+
# Pass 1: collect all variable definitions from the top-level script
|
|
526
|
+
# and all reachable script blocks. This ensures definition order does
|
|
527
|
+
# not matter — a script block executed early can reference variables
|
|
528
|
+
# defined later in the top-level script.
|
|
529
|
+
# ------------------------------------------------------------------
|
|
530
|
+
all_defined: set[str] = set()
|
|
531
|
+
collect_visited: set[str] = set()
|
|
532
|
+
_collect_defined_vars(
|
|
533
|
+
cmdlist,
|
|
534
|
+
script_dir,
|
|
535
|
+
all_defined,
|
|
536
|
+
_savedscripts=savedscripts,
|
|
537
|
+
_visited_scripts=collect_visited,
|
|
538
|
+
)
|
|
539
|
+
# Also collect from every saved script block (they may define vars
|
|
540
|
+
# referenced by other blocks). Share the visited set so each block
|
|
541
|
+
# is only traversed once (O(N) instead of O(N²)).
|
|
542
|
+
for saved_cl in savedscripts.values():
|
|
543
|
+
_collect_defined_vars(
|
|
544
|
+
saved_cl,
|
|
545
|
+
script_dir,
|
|
546
|
+
all_defined,
|
|
547
|
+
_savedscripts=savedscripts,
|
|
548
|
+
_visited_scripts=collect_visited,
|
|
549
|
+
)
|
|
396
550
|
|
|
397
|
-
#
|
|
398
|
-
#
|
|
399
|
-
#
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
551
|
+
# ------------------------------------------------------------------
|
|
552
|
+
# Pass 2: lint for structural issues and undefined-variable warnings
|
|
553
|
+
# using the complete variable set from pass 1.
|
|
554
|
+
# ------------------------------------------------------------------
|
|
555
|
+
# Shared visited-scripts tracker — prevents duplicate lint warnings
|
|
556
|
+
# when the same script block is reached via multiple paths.
|
|
557
|
+
visited: set[str] = set()
|
|
558
|
+
|
|
559
|
+
issues.extend(
|
|
560
|
+
_lint_cmdlist(
|
|
561
|
+
cmdlist,
|
|
562
|
+
script_dir,
|
|
563
|
+
all_defined,
|
|
564
|
+
_savedscripts=savedscripts,
|
|
565
|
+
_visited_scripts=visited,
|
|
566
|
+
),
|
|
567
|
+
)
|
|
403
568
|
|
|
404
|
-
# Analyse each named SCRIPT block
|
|
405
|
-
|
|
406
|
-
|
|
569
|
+
# Analyse each named SCRIPT block that was NOT already visited via
|
|
570
|
+
# EXECUTE SCRIPT (standalone analysis catches structural issues like
|
|
571
|
+
# unmatched IF/ENDIF in script blocks that are never executed).
|
|
572
|
+
for script_name, saved_cl in savedscripts.items():
|
|
573
|
+
if script_name in visited:
|
|
574
|
+
continue
|
|
575
|
+
visited.add(script_name)
|
|
576
|
+
saved_issues = _lint_cmdlist(
|
|
577
|
+
saved_cl,
|
|
578
|
+
script_dir,
|
|
579
|
+
set(all_defined),
|
|
580
|
+
_savedscripts=savedscripts,
|
|
581
|
+
_visited_scripts=visited,
|
|
582
|
+
)
|
|
407
583
|
for sev, src, lno, msg in saved_issues:
|
|
408
|
-
# Annotate with the script name if the source is the same file
|
|
409
584
|
issues.append((sev, src, lno, f"[script '{script_name}'] {msg}"))
|
|
410
585
|
|
|
411
586
|
return issues
|
|
@@ -440,12 +615,22 @@ def _print_lint_results(issues: list[_Issue], script_label: str) -> int:
|
|
|
440
615
|
_console.print()
|
|
441
616
|
return 0
|
|
442
617
|
|
|
443
|
-
|
|
444
|
-
|
|
618
|
+
# Sort: errors first, then warnings; within each group sort by line number.
|
|
619
|
+
_sev_order = {"error": 0, "warning": 1}
|
|
620
|
+
sorted_issues = sorted(issues, key=lambda i: (_sev_order.get(i[0], 9), i[2]))
|
|
621
|
+
|
|
622
|
+
# Compute the widest location string so columns align.
|
|
623
|
+
locs: list[str] = []
|
|
624
|
+
for _, source, line_no, _ in sorted_issues:
|
|
625
|
+
locs.append(f"{source}:{line_no}" if line_no else source)
|
|
626
|
+
loc_width = max(len(loc) for loc in locs) if locs else 0
|
|
627
|
+
|
|
628
|
+
for (severity, _source, _line_no, message), loc in zip(sorted_issues, locs):
|
|
629
|
+
pad = " " * (loc_width - len(loc))
|
|
445
630
|
if severity == "error":
|
|
446
|
-
_console.print(f" [bold red]ERROR [/bold red] [dim]{loc}[/dim] {message}")
|
|
631
|
+
_console.print(f" [bold red]ERROR [/bold red] [dim]{loc}[/dim]{pad} {message}")
|
|
447
632
|
else:
|
|
448
|
-
_console.print(f" [bold yellow]WARNING[/bold yellow] [dim]{loc}[/dim] {message}")
|
|
633
|
+
_console.print(f" [bold yellow]WARNING[/bold yellow] [dim]{loc}[/dim]{pad} {message}")
|
|
449
634
|
|
|
450
635
|
_console.print()
|
|
451
636
|
parts = []
|