execsql2 2.10.1__tar.gz → 2.11.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.10.1 → execsql2-2.11.0}/CHANGELOG.md +21 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/PKG-INFO +2 -1
- {execsql2-2.10.1 → execsql2-2.11.0}/README.md +1 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/about/divergence.md +12 -7
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/getting-started/syntax.md +4 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/reference/metacommands.md +23 -10
- {execsql2-2.10.1 → execsql2-2.11.0}/pyproject.toml +2 -2
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/cli/__init__.py +6 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/cli/run.py +5 -1
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/metacommands/debug_repl.py +105 -44
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/script/engine.py +10 -6
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/utils/errors.py +41 -2
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/metacommands/test_breakpoint.py +108 -48
- execsql2-2.11.0/tests/test_error_messages.py +452 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/uv.lock +1 -1
- {execsql2-2.10.1 → execsql2-2.11.0}/.claude/agents/dba.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/.claude/agents/herald.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/.claude/agents/inspector.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/.claude/agents/oracle.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/.claude/agents/patcher.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/.claude/agents/qa.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/.claude/agents/scribe.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/.claude/commands/code-oracle.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/.claude/commands/migrate.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/.claude/commands/review-changes.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/.claude/commands/test-module.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/.claude/commands/update-changelog.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/.claude/commands/where-is.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/.claude/project_context.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/.claude/state/status.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/.github/workflows/ci-cd.yml +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/.gitignore +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/.pre-commit-config.yaml +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/.pre-commit-hooks.yaml +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/.python-version +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/.readthedocs.yaml +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/CLAUDE.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/CONTRIBUTING.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/LICENSE.txt +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/NOTICE +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/SECURITY.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/about/contributors.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/about/copyright.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/api/cli.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/api/db.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/api/exporters.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/api/importers.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/api/index.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/api/metacommands.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/dev/adding_db_adapters.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/dev/adding_exporters.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/dev/adding_importers.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/dev/adding_metacommands.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/dev/architecture.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/getting-started/installation.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/getting-started/requirements.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/guides/debugging.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/guides/documentation.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/guides/encoding.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/guides/examples.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/guides/formatter.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/guides/logging.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/guides/sql_syntax.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/guides/usage.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/guides/using_scripts.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/images/Compare_planets.png +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/images/actions.png +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/images/actions2.png +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/images/checkboxes.png +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/images/connect.b64 +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/images/connect.png +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/images/create_conf.png +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/images/data_error1_screenshot.jpg +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/images/entry_form.png +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/images/execsql_console.png +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/images/execsql_logo_01.png +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/images/fatals.png +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/images/logo_small.png +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/images/pause_terminal.png +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/images/pause_terminal_sm.b64 +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/images/pause_terminal_sm.png +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/images/prompt_compare.png +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/images/set_build_commands.jpg +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/images/unit_conversions.b64 +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/images/unit_conversions_029.png +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/images/unmatched.png +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/images/vim_execsql_highlight.png +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/index.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/reference/configuration.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/reference/security.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/docs/reference/substitution_vars.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/extras/vscode-execsql/README.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/extras/vscode-execsql/package.json +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/extras/vscode-execsql/syntaxes/execsql.tmLanguage.json +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/justfile +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/scripts/generate_vscode_grammar.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/__init__.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/__main__.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/cli/dsn.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/cli/help.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/cli/lint.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/config.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/constants.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/db/__init__.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/db/access.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/db/base.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/db/dsn.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/db/duckdb.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/db/factory.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/db/firebird.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/db/mysql.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/db/oracle.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/db/postgres.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/db/sqlite.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/db/sqlserver.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/exceptions.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/exporters/__init__.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/exporters/base.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/exporters/delimited.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/exporters/duckdb.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/exporters/feather.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/exporters/html.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/exporters/json.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/exporters/latex.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/exporters/markdown.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/exporters/ods.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/exporters/parquet.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/exporters/pretty.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/exporters/protocol.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/exporters/raw.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/exporters/sqlite.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/exporters/templates.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/exporters/values.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/exporters/xls.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/exporters/xlsx.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/exporters/xml.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/exporters/yaml.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/exporters/zip.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/format.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/gui/__init__.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/gui/base.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/gui/console.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/gui/desktop.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/gui/tui.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/importers/__init__.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/importers/base.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/importers/csv.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/importers/feather.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/importers/ods.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/importers/xls.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/metacommands/__init__.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/metacommands/conditions.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/metacommands/connect.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/metacommands/control.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/metacommands/data.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/metacommands/debug.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/metacommands/dispatch.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/metacommands/io.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/metacommands/io_export.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/metacommands/io_fileops.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/metacommands/io_import.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/metacommands/io_write.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/metacommands/prompt.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/metacommands/script_ext.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/metacommands/system.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/models.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/parser.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/py.typed +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/script/__init__.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/script/control.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/script/variables.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/state.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/types.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/utils/__init__.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/utils/auth.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/utils/crypto.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/utils/datetime.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/utils/fileio.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/utils/gui.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/utils/mail.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/utils/numeric.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/utils/regex.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/utils/strings.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/src/execsql/utils/timer.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/templates/README.md +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/templates/config_settings.sqlite +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/templates/example_config_prompt.sql +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/templates/execsql.conf +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/templates/make_config_db.sql +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/templates/md_compare.sql +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/templates/md_glossary.sql +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/templates/md_upsert.sql +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/templates/pg_compare.sql +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/templates/pg_glossary.sql +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/templates/pg_upsert.sql +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/templates/script_template.sql +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/templates/ss_compare.sql +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/templates/ss_glossary.sql +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/templates/ss_upsert.sql +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/__init__.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/cli/__init__.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/cli/test_cli.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/cli/test_cli_e2e.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/cli/test_cli_run.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/cli/test_lint.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/cli/test_ping.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/cli/test_profile.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/conftest.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/db/__init__.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/db/test_base.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/db/test_duckdb.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/db/test_factory.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/db/test_postgres.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/db/test_sqlite.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/db/test_sqlite_extra.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/exporters/__init__.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/exporters/test_base.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/exporters/test_db.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/exporters/test_delimited.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/exporters/test_duckdb_exporter.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/exporters/test_exporters.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/exporters/test_feather.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/exporters/test_html_latex.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/exporters/test_json.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/exporters/test_markdown.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/exporters/test_ods.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/exporters/test_parquet.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/exporters/test_sqlite_exporter.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/exporters/test_templates.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/exporters/test_xls_xlsx.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/exporters/test_xlsx.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/exporters/test_xml.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/exporters/test_yaml.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/exporters/test_zip.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/gui/__init__.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/gui/test_backends.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/importers/__init__.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/importers/test_csv_importer.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/importers/test_feather_importer.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/importers/test_ods_importer.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/importers/test_xls_importer.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/integration/__init__.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/integration/conftest.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/integration/test_dsn.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/integration/test_duckdb.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/integration/test_mysql.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/integration/test_postgres.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/integration/test_sqlite.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/metacommands/__init__.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/metacommands/test_assert.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/metacommands/test_connect.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/metacommands/test_metacommands.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/metacommands/test_metacommands_connect.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/metacommands/test_metacommands_data.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/metacommands/test_metacommands_extended.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/metacommands/test_metacommands_fileops_extra.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/metacommands/test_metacommands_io.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/metacommands/test_metacommands_io_write_extra.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/metacommands/test_metacommands_script_ext.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/metacommands/test_metacommands_system.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/metacommands/test_metacommands_system_extra.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/metacommands/test_row_count.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/test_config.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/test_config_data.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/test_constants.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/test_engine.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/test_exceptions.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/test_format.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/test_mail.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/test_models.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/test_package.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/test_parser.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/test_registry.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/test_script.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/test_state.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/test_types.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/utils/__init__.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/utils/test_auth.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/utils/test_auth_extra.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/utils/test_crypto.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/utils/test_datetime.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/utils/test_errors.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/utils/test_errors_extra.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/utils/test_fileio.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/utils/test_fileio_extra.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/utils/test_numeric.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/utils/test_regex.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/utils/test_strings.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/utils/test_timer.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/tests/utils/test_timer_extra.py +0 -0
- {execsql2-2.10.1 → execsql2-2.11.0}/zensical.toml +0 -0
|
@@ -13,6 +13,27 @@ ______________________________________________________________________
|
|
|
13
13
|
|
|
14
14
|
______________________________________________________________________
|
|
15
15
|
|
|
16
|
+
## [2.11.0] - 2026-04-01
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- `--debug` CLI flag — starts the script in step-through debug mode. The debug REPL pauses before each statement, as if `BREAKPOINT` were inserted at the top with `.next` always active.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- BREAKPOINT debug REPL now pauses **before** each statement instead of after, so the upcoming statement can be inspected before it runs.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- BREAKPOINT REPL no longer wraps variable values in extra single quotes — values are now displayed exactly as defined.
|
|
29
|
+
- Error messages now include script file name and line number — `ErrInfo` fields `script_file` and `script_line_no` are populated via a new `stamp_errinfo()` helper called from `exit_now()` and metacommand error paths, restoring monolith-level "Line N of script foo.sql" context in all error output.
|
|
30
|
+
- `$ERROR_MESSAGE` substitution variable is now updated on every error: in `exit_now()`, in non-halting SQL errors (`SqlStmt.run()`), and in non-halting metacommand errors (`MetacommandStmt.run()`). Previously it was initialized to `""` and never changed.
|
|
31
|
+
- `MetacommandStmt.run()` now re-raises the original handler `ErrInfo` when `halt_on_metacommand_err` is True, instead of discarding it and raising a generic "Unknown metacommand" error.
|
|
32
|
+
- `write_warning()` now accepts an `always=True` keyword argument that bypasses the `conf.write_warnings` gate, ensuring structural warnings (IF-level mismatch, unsubstituted variables) are always visible on stderr.
|
|
33
|
+
- Uncaught-exception error message in `_execute_script_direct()` and `_execute_script_textual_console()` no longer appends "in script , line 0" when `current_script_line()` returns an empty string.
|
|
34
|
+
|
|
35
|
+
______________________________________________________________________
|
|
36
|
+
|
|
16
37
|
## [2.10.1] - 2026-04-01
|
|
17
38
|
|
|
18
39
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: execsql2
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.11.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: Repository, https://github.com/geocoug/execsql
|
|
6
6
|
Project-URL: Issues, https://github.com/geocoug/execsql/issues
|
|
@@ -223,6 +223,7 @@ execsql script.sql # read connection from config file
|
|
|
223
223
|
| `--dry-run` | Parse the script and report commands without executing |
|
|
224
224
|
| `--lint` | Static analysis: check structure and warn on issues (no DB) |
|
|
225
225
|
| `--progress` | Show a progress bar for long-running IMPORT operations |
|
|
226
|
+
| `--debug` | Start in step-through debug mode (REPL pauses before each stmt) |
|
|
226
227
|
| `--dump-keywords` | Print metacommand keywords as JSON and exit |
|
|
227
228
|
| `--gui-framework {tkinter,textual}` | GUI framework for interactive prompts |
|
|
228
229
|
|
|
@@ -113,6 +113,7 @@ execsql script.sql # read connection from config file
|
|
|
113
113
|
| `--dry-run` | Parse the script and report commands without executing |
|
|
114
114
|
| `--lint` | Static analysis: check structure and warn on issues (no DB) |
|
|
115
115
|
| `--progress` | Show a progress bar for long-running IMPORT operations |
|
|
116
|
+
| `--debug` | Start in step-through debug mode (REPL pauses before each stmt) |
|
|
116
117
|
| `--dump-keywords` | Print metacommand keywords as JSON and exit |
|
|
117
118
|
| `--gui-framework {tkinter,textual}` | GUI framework for interactive prompts |
|
|
118
119
|
|
|
@@ -22,6 +22,7 @@ ______________________________________________________________________
|
|
|
22
22
|
| `--progress` | Show a Rich progress bar during long-running IMPORT operations. |
|
|
23
23
|
| `--dump-keywords` | Emit all metacommand keywords, conditionals, config options, and export formats as structured JSON. |
|
|
24
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. |
|
|
25
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. |
|
|
26
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. Top 20 slowest statements are shown. |
|
|
27
28
|
| `--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. |
|
|
@@ -167,13 +168,17 @@ These are behavioral changes driven by security or correctness issues in the ups
|
|
|
167
168
|
|
|
168
169
|
### Bug Fixes
|
|
169
170
|
|
|
170
|
-
| Area | Fix
|
|
171
|
-
| --------------------------------- |
|
|
172
|
-
| Oracle default port | Corrected from `5432` (PostgreSQL) to `1521`.
|
|
173
|
-
| MySQL `LOAD DATA INFILE` encoding | Python encoding names are now mapped to MySQL charset names.
|
|
174
|
-
| `dt_cast` type converters | Base `Database` class auto-populates 8 type converters that were previously left empty after the refactor.
|
|
175
|
-
| `FileWriter` CPU busy-loop | Uses blocking `queue.get(timeout=0.1)` instead of `get_nowait()` in a tight loop.
|
|
176
|
-
| Substitution variable cycles | 100-iteration limit prevents infinite loops on cyclic variable references.
|
|
171
|
+
| Area | Fix |
|
|
172
|
+
| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
173
|
+
| Oracle default port | Corrected from `5432` (PostgreSQL) to `1521`. |
|
|
174
|
+
| MySQL `LOAD DATA INFILE` encoding | Python encoding names are now mapped to MySQL charset names. |
|
|
175
|
+
| `dt_cast` type converters | Base `Database` class auto-populates 8 type converters that were previously left empty after the refactor. |
|
|
176
|
+
| `FileWriter` CPU busy-loop | Uses blocking `queue.get(timeout=0.1)` instead of `get_nowait()` in a tight loop. |
|
|
177
|
+
| Substitution variable cycles | 100-iteration limit prevents infinite loops on cyclic variable references. |
|
|
178
|
+
| Script location in error messages | `ErrInfo.script_file` and `script_line_no` are now populated via `stamp_errinfo()` so error output includes "Line N of script foo.sql" context — restoring behavior present in the monolith. |
|
|
179
|
+
| `$ERROR_MESSAGE` not updated | `$ERROR_MESSAGE` is now set on every error path: `exit_now()`, non-halting SQL errors, and non-halting metacommand errors. Previously it was initialized to `""` and never changed. |
|
|
180
|
+
| Metacommand error message lost | When `halt_on_metacommand_err` is `ON`, the original handler `ErrInfo` is now re-raised; the generic "Unknown metacommand" message no longer replaces the specific error from the handler. |
|
|
181
|
+
| Empty script name in error msg | `_execute_script_direct()` and `_execute_script_textual_console()` no longer append "in script , line 0" to uncaught-exception messages when `current_script_line()` returns an empty string. |
|
|
177
182
|
|
|
178
183
|
______________________________________________________________________
|
|
179
184
|
|
|
@@ -224,6 +224,10 @@ Valid encoding names can be displayed with the `-y` option. See also [Character
|
|
|
224
224
|
execsql --ping --dsn sqlite:///mydb.sqlite
|
|
225
225
|
```
|
|
226
226
|
|
|
227
|
+
`--debug`
|
|
228
|
+
|
|
229
|
+
: Start in step-through debug mode. The debug REPL pauses before each statement, as if a `BREAKPOINT` metacommand were inserted at the top of the script with `.next` always active. Type `.continue` or `.c` at the REPL prompt to resume normal execution, or `.next` / `.n` to step one statement at a time. Silently skipped in non-TTY environments.
|
|
230
|
+
|
|
227
231
|
`--profile`
|
|
228
232
|
|
|
229
233
|
: Record the wall-clock execution time of each SQL statement and metacommand. After the script finishes, print a summary table to the console showing elapsed time, percentage of total time, source file and line number, command type, and a preview of the command text. Statements are sorted from slowest to fastest; the top 20 are displayed. Useful for identifying slow queries or metacommands in long-running scripts.
|
|
@@ -127,20 +127,30 @@ Pauses script execution and drops into an interactive debug REPL (read-eval-prin
|
|
|
127
127
|
|
|
128
128
|
**Non-interactive safety:** If `sys.stdin` is not a TTY (e.g. CI pipelines, piped input, batch execution) the metacommand is silently skipped. Scripts will never hang in automation.
|
|
129
129
|
|
|
130
|
-
|
|
130
|
+
All REPL commands are dot-prefixed to avoid ambiguity with variable names and SQL. Anything without a dot prefix is treated as a variable lookup or SQL.
|
|
131
|
+
|
|
132
|
+
**REPL commands (dot-prefixed):**
|
|
131
133
|
|
|
132
134
|
| Command | Description |
|
|
133
135
|
|---------|-------------|
|
|
134
|
-
|
|
|
135
|
-
|
|
|
136
|
-
|
|
|
137
|
-
|
|
|
138
|
-
| `
|
|
139
|
-
| `
|
|
140
|
-
| `
|
|
141
|
-
|
|
136
|
+
| `.continue` or `.c` | Resume script execution |
|
|
137
|
+
| `.abort` or `.q` | Halt the script with exit status 1 |
|
|
138
|
+
| `.vars` | List user, system, local, and counter variables (grouped by type) |
|
|
139
|
+
| `.vars all` | Include environment variables (`&`) in the listing |
|
|
140
|
+
| `.next` or `.n` | Execute the next script statement, then pause again (step mode) |
|
|
141
|
+
| `.stack` | Show the command-list stack: script name, cursor index, and nesting depth |
|
|
142
|
+
| `.help` | Show the list of available REPL commands |
|
|
143
|
+
|
|
144
|
+
**Variable inspection and SQL (no dot prefix):**
|
|
142
145
|
|
|
143
|
-
|
|
146
|
+
| Input | Description |
|
|
147
|
+
|-------|-------------|
|
|
148
|
+
| `logfile` | Print the value of the `logfile` variable |
|
|
149
|
+
| `$ARG_1` | Print the value of a system/built-in variable |
|
|
150
|
+
| `&HOME` | Print the value of an environment variable |
|
|
151
|
+
| `SELECT ...;` | Run ad-hoc SQL against the current database and pretty-print results |
|
|
152
|
+
|
|
153
|
+
Pressing Ctrl-D (EOF) or Ctrl-C (KeyboardInterrupt) at the `execsql debug>` prompt resumes execution, the same as typing `.continue`.
|
|
144
154
|
|
|
145
155
|
**Example:**
|
|
146
156
|
|
|
@@ -153,6 +163,9 @@ SELECT count(*) FROM staging;
|
|
|
153
163
|
|
|
154
164
|
BREAKPOINT is silently skipped inside a `False` [IF](#if_cmd) block.
|
|
155
165
|
|
|
166
|
+
!!! tip
|
|
167
|
+
Use `execsql --debug script.sql` to start in step-through mode without adding a `BREAKPOINT` metacommand to your script. The REPL pauses before each statement.
|
|
168
|
+
|
|
156
169
|
|
|
157
170
|
## BEGIN BATCH and END BATCH { #batch }
|
|
158
171
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "execsql2"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.11.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" }
|
|
@@ -158,7 +158,7 @@ skip-magic-trailing-comma = false
|
|
|
158
158
|
line-ending = "auto"
|
|
159
159
|
|
|
160
160
|
[tool.bumpversion]
|
|
161
|
-
current_version = "2.
|
|
161
|
+
current_version = "2.11.0"
|
|
162
162
|
commit = true
|
|
163
163
|
commit_args = "--no-verify"
|
|
164
164
|
tag = true
|
|
@@ -278,6 +278,11 @@ def main(
|
|
|
278
278
|
"--profile",
|
|
279
279
|
help="Record per-statement execution times and print a timing summary after the script completes.",
|
|
280
280
|
),
|
|
281
|
+
debug: bool = typer.Option(
|
|
282
|
+
False,
|
|
283
|
+
"--debug",
|
|
284
|
+
help="Start in step-through debug mode. The debug REPL pauses before each statement.",
|
|
285
|
+
),
|
|
281
286
|
version: bool | None = typer.Option(
|
|
282
287
|
None,
|
|
283
288
|
"--version",
|
|
@@ -446,6 +451,7 @@ def main(
|
|
|
446
451
|
profile=profile,
|
|
447
452
|
ping=ping,
|
|
448
453
|
lint=lint,
|
|
454
|
+
debug=debug,
|
|
449
455
|
)
|
|
450
456
|
|
|
451
457
|
|
|
@@ -214,6 +214,7 @@ def _run(
|
|
|
214
214
|
profile: bool = False,
|
|
215
215
|
ping: bool = False,
|
|
216
216
|
lint: bool = False,
|
|
217
|
+
debug: bool = False,
|
|
217
218
|
) -> None:
|
|
218
219
|
"""Initialise state, connect to the database, load the script, and run it.
|
|
219
220
|
|
|
@@ -547,6 +548,9 @@ def _run(
|
|
|
547
548
|
if profile:
|
|
548
549
|
_state.profile_data = []
|
|
549
550
|
|
|
551
|
+
if debug:
|
|
552
|
+
_state.step_mode = True
|
|
553
|
+
|
|
550
554
|
_execute_script_direct(conf, profile=profile)
|
|
551
555
|
|
|
552
556
|
|
|
@@ -653,7 +657,7 @@ def _execute_script_direct(conf: ConfigData, *, profile: bool = False) -> None:
|
|
|
653
657
|
lno = strace[0][1]
|
|
654
658
|
msg = f"{Path(sys.argv[0]).name}: Uncaught exception {sys.exc_info()[0]} ({sys.exc_info()[1]}) on line {lno}"
|
|
655
659
|
script, slno = current_script_line()
|
|
656
|
-
if script
|
|
660
|
+
if script:
|
|
657
661
|
msg += f" in script {script}, line {slno}"
|
|
658
662
|
from execsql.utils.errors import exit_now
|
|
659
663
|
|
|
@@ -13,6 +13,11 @@ The REPL allows the user to:
|
|
|
13
13
|
- Step through the script one statement at a time.
|
|
14
14
|
- Resume or abort execution.
|
|
15
15
|
|
|
16
|
+
All REPL commands are dot-prefixed (``.continue``, ``.vars``, ``.next``)
|
|
17
|
+
to avoid ambiguity with variable names and SQL. Anything not starting
|
|
18
|
+
with ``.`` is treated as either a variable lookup (if it matches a known
|
|
19
|
+
variable) or SQL (if it ends with ``;``).
|
|
20
|
+
|
|
16
21
|
In non-interactive environments (CI, piped input, ``sys.stdin.isatty()`` is
|
|
17
22
|
``False``) the metacommand is silently skipped so automated pipelines are not
|
|
18
23
|
blocked.
|
|
@@ -30,15 +35,19 @@ __all__ = ["x_breakpoint"]
|
|
|
30
35
|
# ---------------------------------------------------------------------------
|
|
31
36
|
|
|
32
37
|
_HELP_TEXT = """\
|
|
33
|
-
execsql debug REPL commands
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
next n
|
|
40
|
-
stack Show the command-list stack (script name, line, depth)
|
|
41
|
-
help Show this help text
|
|
38
|
+
execsql debug REPL — all commands start with '.'
|
|
39
|
+
|
|
40
|
+
.continue .c Resume script execution
|
|
41
|
+
.abort .q Halt the script (exit 1)
|
|
42
|
+
.vars List user, system, local, and counter variables
|
|
43
|
+
.vars all Include environment variables (&) in the listing
|
|
44
|
+
.next .n Execute the next statement then pause again (step mode)
|
|
45
|
+
.stack Show the command-list stack (script name, line, depth)
|
|
46
|
+
.help Show this help text
|
|
47
|
+
|
|
48
|
+
Everything else:
|
|
49
|
+
varname Print a variable's value (e.g. logfile, $ARG_1, &HOME)
|
|
50
|
+
SELECT ...; Run ad-hoc SQL against the current database
|
|
42
51
|
"""
|
|
43
52
|
|
|
44
53
|
|
|
@@ -64,7 +73,7 @@ def x_breakpoint(**kwargs: Any) -> None:
|
|
|
64
73
|
def _debug_repl() -> None:
|
|
65
74
|
"""Interactive read-eval-print loop for script debugging.
|
|
66
75
|
|
|
67
|
-
Reads commands from stdin until the user types
|
|
76
|
+
Reads commands from stdin until the user types ``.continue`` or ``.abort``,
|
|
68
77
|
or until EOF / KeyboardInterrupt.
|
|
69
78
|
"""
|
|
70
79
|
try:
|
|
@@ -72,7 +81,7 @@ def _debug_repl() -> None:
|
|
|
72
81
|
except ImportError:
|
|
73
82
|
pass # readline not available on Windows; continue without it
|
|
74
83
|
|
|
75
|
-
_write("\n[Breakpoint] Script paused. Type 'help' for commands, '
|
|
84
|
+
_write("\n[Breakpoint] Script paused. Type '.help' for commands, '.c' to resume.\n")
|
|
76
85
|
|
|
77
86
|
while True:
|
|
78
87
|
try:
|
|
@@ -87,27 +96,48 @@ def _debug_repl() -> None:
|
|
|
87
96
|
if not line:
|
|
88
97
|
continue
|
|
89
98
|
|
|
90
|
-
|
|
99
|
+
# Dot-prefixed → REPL command
|
|
100
|
+
if line.startswith("."):
|
|
101
|
+
_handle_dot_command(line)
|
|
102
|
+
if line.lower().lstrip(".") in ("continue", "c"):
|
|
103
|
+
return
|
|
104
|
+
if line.lower().lstrip(".") in ("abort", "q", "quit"):
|
|
105
|
+
# _handle_dot_command already raised SystemExit, but guard anyway
|
|
106
|
+
return
|
|
107
|
+
if line.lower().lstrip(".") in ("next", "n"):
|
|
108
|
+
return
|
|
109
|
+
continue
|
|
91
110
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
elif lower in ("abort", "q", "quit"):
|
|
95
|
-
raise SystemExit(1)
|
|
96
|
-
elif lower == "help":
|
|
97
|
-
_write(_HELP_TEXT)
|
|
98
|
-
elif lower == "vars":
|
|
99
|
-
_print_all_vars()
|
|
100
|
-
elif lower == "stack":
|
|
101
|
-
_print_stack()
|
|
102
|
-
elif lower in ("next", "n"):
|
|
103
|
-
_enable_step_mode()
|
|
104
|
-
return
|
|
105
|
-
elif line[0] in ("$", "&", "@"):
|
|
106
|
-
_print_var(line)
|
|
107
|
-
elif line.rstrip().endswith(";"):
|
|
111
|
+
# SQL (ends with semicolon)
|
|
112
|
+
if line.rstrip().endswith(";"):
|
|
108
113
|
_run_sql(line)
|
|
109
|
-
|
|
110
|
-
|
|
114
|
+
continue
|
|
115
|
+
|
|
116
|
+
# Everything else → variable lookup
|
|
117
|
+
_print_var(line)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _handle_dot_command(line: str) -> None:
|
|
121
|
+
"""Dispatch a dot-prefixed REPL command."""
|
|
122
|
+
# Strip the leading dot and normalize
|
|
123
|
+
cmd = line[1:].strip().lower()
|
|
124
|
+
|
|
125
|
+
if cmd in ("continue", "c"):
|
|
126
|
+
return # caller checks and returns from _debug_repl
|
|
127
|
+
elif cmd in ("abort", "q", "quit"):
|
|
128
|
+
raise SystemExit(1)
|
|
129
|
+
elif cmd == "help":
|
|
130
|
+
_write(_HELP_TEXT)
|
|
131
|
+
elif cmd == "vars all":
|
|
132
|
+
_print_all_vars(include_env=True)
|
|
133
|
+
elif cmd == "vars":
|
|
134
|
+
_print_all_vars()
|
|
135
|
+
elif cmd == "stack":
|
|
136
|
+
_print_stack()
|
|
137
|
+
elif cmd in ("next", "n"):
|
|
138
|
+
_enable_step_mode()
|
|
139
|
+
else:
|
|
140
|
+
_write(f" Unknown command: {line!r}. Type '.help' for available commands.\n")
|
|
111
141
|
|
|
112
142
|
|
|
113
143
|
# ---------------------------------------------------------------------------
|
|
@@ -125,8 +155,8 @@ def _write(text: str) -> None:
|
|
|
125
155
|
sys.stdout.flush()
|
|
126
156
|
|
|
127
157
|
|
|
128
|
-
def _print_all_vars() -> None:
|
|
129
|
-
"""Print
|
|
158
|
+
def _print_all_vars(*, include_env: bool = False) -> None:
|
|
159
|
+
"""Print substitution variables grouped by type."""
|
|
130
160
|
subvars = _state.subvars
|
|
131
161
|
if subvars is None:
|
|
132
162
|
_write(" (no substitution variables defined)\n")
|
|
@@ -135,17 +165,52 @@ def _print_all_vars() -> None:
|
|
|
135
165
|
if not items:
|
|
136
166
|
_write(" (no substitution variables defined)\n")
|
|
137
167
|
return
|
|
138
|
-
|
|
139
|
-
|
|
168
|
+
|
|
169
|
+
# Group by prefix.
|
|
170
|
+
user_vars: list[tuple[str, str]] = []
|
|
171
|
+
system_vars: list[tuple[str, str]] = []
|
|
172
|
+
counter_vars: list[tuple[str, str]] = []
|
|
173
|
+
local_vars: list[tuple[str, str]] = []
|
|
174
|
+
env_vars: list[tuple[str, str]] = []
|
|
175
|
+
|
|
140
176
|
for name, value in sorted(items):
|
|
141
|
-
|
|
177
|
+
if name.startswith("&"):
|
|
178
|
+
env_vars.append((name, value))
|
|
179
|
+
elif name.startswith("~"):
|
|
180
|
+
local_vars.append((name, value))
|
|
181
|
+
elif name.startswith("@"):
|
|
182
|
+
counter_vars.append((name, value))
|
|
183
|
+
elif name.startswith("$"):
|
|
184
|
+
system_vars.append((name, value))
|
|
185
|
+
else:
|
|
186
|
+
user_vars.append((name, value))
|
|
187
|
+
|
|
188
|
+
def _print_group(label: str, group: list[tuple[str, str]]) -> None:
|
|
189
|
+
if not group:
|
|
190
|
+
return
|
|
191
|
+
_write(f" {label}:\n")
|
|
192
|
+
max_name = max(len(n) for n, _ in group)
|
|
193
|
+
for name, value in group:
|
|
194
|
+
_write(f" {name:<{max_name}} = {value}\n")
|
|
195
|
+
|
|
196
|
+
_print_group("User variables", user_vars)
|
|
197
|
+
_print_group("System variables ($)", system_vars)
|
|
198
|
+
_print_group("Local variables (~)", local_vars)
|
|
199
|
+
_print_group("Counter variables (@)", counter_vars)
|
|
200
|
+
if include_env:
|
|
201
|
+
_print_group("Environment variables (&)", env_vars)
|
|
202
|
+
|
|
203
|
+
if not any([user_vars, system_vars, local_vars, counter_vars]):
|
|
204
|
+
if env_vars:
|
|
205
|
+
_write(" (no script variables defined — use '.vars all' to see environment variables)\n")
|
|
206
|
+
else:
|
|
207
|
+
_write(" (no variables defined)\n")
|
|
142
208
|
|
|
143
209
|
|
|
144
210
|
def _print_var(varname: str) -> None:
|
|
145
211
|
"""Print the value of a single substitution variable.
|
|
146
212
|
|
|
147
|
-
|
|
148
|
-
varname: The variable reference as typed by the user, e.g. ``$FOO``.
|
|
213
|
+
Tries the name as typed, then with the sigil prefix stripped.
|
|
149
214
|
"""
|
|
150
215
|
subvars = _state.subvars
|
|
151
216
|
if subvars is None:
|
|
@@ -153,14 +218,14 @@ def _print_var(varname: str) -> None:
|
|
|
153
218
|
return
|
|
154
219
|
# Try the name as typed first, then without the sigil prefix ($, &, @, #, ~).
|
|
155
220
|
# SUB creates variables without a prefix (e.g., "logfile"), but users
|
|
156
|
-
#
|
|
221
|
+
# may type "$logfile" at the prompt.
|
|
157
222
|
value = subvars.varvalue(varname)
|
|
158
223
|
if value is None and len(varname) > 1 and varname[0] in "$&@#~":
|
|
159
224
|
value = subvars.varvalue(varname[1:])
|
|
160
225
|
if value is None:
|
|
161
226
|
_write(f" {varname}: (undefined)\n")
|
|
162
227
|
else:
|
|
163
|
-
_write(f" {varname} = {value
|
|
228
|
+
_write(f" {varname} = {value}\n")
|
|
164
229
|
|
|
165
230
|
|
|
166
231
|
def _print_stack() -> None:
|
|
@@ -177,11 +242,7 @@ def _print_stack() -> None:
|
|
|
177
242
|
|
|
178
243
|
|
|
179
244
|
def _run_sql(sql: str) -> None:
|
|
180
|
-
"""Execute ad-hoc SQL against the current database and pretty-print the results.
|
|
181
|
-
|
|
182
|
-
Args:
|
|
183
|
-
sql: A complete SQL statement ending with a semicolon.
|
|
184
|
-
"""
|
|
245
|
+
"""Execute ad-hoc SQL against the current database and pretty-print the results."""
|
|
185
246
|
dbs = _state.dbs
|
|
186
247
|
if dbs is None:
|
|
187
248
|
_write(" (no database connection is active)\n")
|
|
@@ -306,6 +306,7 @@ class SqlStmt:
|
|
|
306
306
|
e = ErrInfo(type="exception", exception_msg=exception_desc())
|
|
307
307
|
if e:
|
|
308
308
|
_state.subvars.add_substitution("$LAST_ERROR", cmd)
|
|
309
|
+
_state.subvars.add_substitution("$ERROR_MESSAGE", str(e))
|
|
309
310
|
_state.status.sql_error = True
|
|
310
311
|
if _state.status.halt_on_err:
|
|
311
312
|
from execsql.utils.errors import exit_now
|
|
@@ -350,8 +351,11 @@ class MetacommandStmt:
|
|
|
350
351
|
if e:
|
|
351
352
|
_state.status.metacommand_error = True
|
|
352
353
|
_state.subvars.add_substitution("$LAST_ERROR", cmd)
|
|
354
|
+
_state.subvars.add_substitution("$ERROR_MESSAGE", str(e))
|
|
353
355
|
if _state.status.halt_on_metacommand_err:
|
|
354
|
-
raise ErrInfo
|
|
356
|
+
# Re-raise the original ErrInfo so its message is preserved, not
|
|
357
|
+
# replaced with the generic "Unknown metacommand" text.
|
|
358
|
+
raise e
|
|
355
359
|
if _state.if_stack.all_true():
|
|
356
360
|
# but nothing applies, because we got here.
|
|
357
361
|
_state.status.metacommand_error = True
|
|
@@ -489,6 +493,11 @@ class CommandList:
|
|
|
489
493
|
_state.subvars.add_substitution("$CURRENT_SCRIPT_NAME", Path(cmditem.source).name)
|
|
490
494
|
_state.subvars.add_substitution("$CURRENT_SCRIPT_LINE", str(cmditem.line_no))
|
|
491
495
|
_state.subvars.add_substitution("$SCRIPT_LINE", str(cmditem.line_no))
|
|
496
|
+
if _state.step_mode:
|
|
497
|
+
_state.step_mode = False
|
|
498
|
+
from execsql.metacommands.debug_repl import _debug_repl
|
|
499
|
+
|
|
500
|
+
_debug_repl()
|
|
492
501
|
_profiling = _state.profile_data is not None
|
|
493
502
|
if _profiling:
|
|
494
503
|
import time as _time
|
|
@@ -506,11 +515,6 @@ class CommandList:
|
|
|
506
515
|
cmditem.command.commandline()[:100],
|
|
507
516
|
),
|
|
508
517
|
)
|
|
509
|
-
if _state.step_mode:
|
|
510
|
-
_state.step_mode = False
|
|
511
|
-
from execsql.metacommands.debug_repl import _debug_repl
|
|
512
|
-
|
|
513
|
-
_debug_repl()
|
|
514
518
|
self.cmdptr += 1
|
|
515
519
|
|
|
516
520
|
def run_next(self) -> None:
|
|
@@ -30,6 +30,7 @@ __all__ = [
|
|
|
30
30
|
"exception_desc",
|
|
31
31
|
"exit_now",
|
|
32
32
|
"fatal_error",
|
|
33
|
+
"stamp_errinfo",
|
|
33
34
|
"write_warning",
|
|
34
35
|
"file_size_date",
|
|
35
36
|
"chainfuncs",
|
|
@@ -70,9 +71,37 @@ def exception_desc() -> str:
|
|
|
70
71
|
return f"{exc_type}: {exc_strval} in {exc_filename} on line {exc_lineno} of execsql."
|
|
71
72
|
|
|
72
73
|
|
|
74
|
+
def stamp_errinfo(errinfo: ErrInfo) -> ErrInfo:
|
|
75
|
+
"""Attach script location from ``_state.last_command`` to an :class:`~execsql.exceptions.ErrInfo`.
|
|
76
|
+
|
|
77
|
+
Reads the source file name, line number, command text, and command type from
|
|
78
|
+
the most-recently-executed :class:`~execsql.script.engine.ScriptCmd` and
|
|
79
|
+
populates any ``None`` fields on *errinfo*. This ensures that error messages
|
|
80
|
+
include "Line N of script foo.sql" context even when the ErrInfo was originally
|
|
81
|
+
created deep inside a handler that had no access to execution state.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
errinfo: The :class:`~execsql.exceptions.ErrInfo` to stamp.
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
The same *errinfo* object, with location fields populated.
|
|
88
|
+
"""
|
|
89
|
+
lc = _state.last_command
|
|
90
|
+
if lc is not None and errinfo.script_file is None:
|
|
91
|
+
errinfo.script_file = lc.source
|
|
92
|
+
errinfo.script_line_no = lc.line_no
|
|
93
|
+
if errinfo.cmd is None:
|
|
94
|
+
errinfo.cmd = lc.command.commandline() if hasattr(lc.command, "commandline") else None
|
|
95
|
+
errinfo.cmdtype = lc.command_type
|
|
96
|
+
return errinfo
|
|
97
|
+
|
|
98
|
+
|
|
73
99
|
def exit_now(exit_status: int, errinfo: ErrInfo | None, logmsg: str | None = None) -> None:
|
|
74
100
|
em = None
|
|
75
101
|
if errinfo is not None:
|
|
102
|
+
stamp_errinfo(errinfo)
|
|
103
|
+
if _state.subvars is not None:
|
|
104
|
+
_state.subvars.add_substitution("$ERROR_MESSAGE", errinfo.errmsg())
|
|
76
105
|
em = errinfo.write()
|
|
77
106
|
if _state.err_halt_writespec is not None:
|
|
78
107
|
try:
|
|
@@ -147,10 +176,20 @@ def fatal_error(error_msg: str | None = None) -> None:
|
|
|
147
176
|
exit_now(1, ErrInfo("error", other_msg=error_msg))
|
|
148
177
|
|
|
149
178
|
|
|
150
|
-
def write_warning(warning_msg: str) -> None:
|
|
179
|
+
def write_warning(warning_msg: str, *, always: bool = False) -> None:
|
|
180
|
+
"""Write a non-fatal warning message to the log and optionally to stderr.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
warning_msg: The warning text to emit.
|
|
184
|
+
always: When ``True``, always write to stderr regardless of the
|
|
185
|
+
``conf.write_warnings`` setting. Use this for structural warnings
|
|
186
|
+
(e.g. IF-level mismatch, unsubstituted variables) that should always
|
|
187
|
+
be visible. When ``False`` (default), stderr output is gated by
|
|
188
|
+
``conf.write_warnings``.
|
|
189
|
+
"""
|
|
151
190
|
if _state.exec_log is not None:
|
|
152
191
|
_state.exec_log.log_status_warning(warning_msg)
|
|
153
|
-
if _state.
|
|
192
|
+
if _state.output is not None and (always or (_state.conf is not None and _state.conf.write_warnings)):
|
|
154
193
|
_state.output.write_err(f"**** Warning {warning_msg}")
|
|
155
194
|
|
|
156
195
|
|