execsql2 2.6.0__tar.gz → 2.8.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.6.0 → execsql2-2.8.0}/.claude/project_context.md +7 -7
- {execsql2-2.6.0 → execsql2-2.8.0}/CHANGELOG.md +31 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/PKG-INFO +6 -2
- {execsql2-2.6.0 → execsql2-2.8.0}/README.md +2 -1
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/about/divergence.md +23 -18
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/getting-started/syntax.md +12 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/reference/metacommands.md +52 -1
- {execsql2-2.6.0 → execsql2-2.8.0}/extras/vscode-execsql/syntaxes/execsql.tmLanguage.json +2 -2
- {execsql2-2.6.0 → execsql2-2.8.0}/pyproject.toml +3 -3
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/cli/__init__.py +6 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/cli/run.py +95 -7
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/exporters/__init__.py +3 -3
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/exporters/delimited.py +2 -2
- execsql2-2.8.0/src/execsql/exporters/markdown.py +126 -0
- execsql2-2.8.0/src/execsql/exporters/xlsx.py +317 -0
- execsql2-2.8.0/src/execsql/exporters/yaml.py +87 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/metacommands/__init__.py +25 -2
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/metacommands/control.py +33 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/metacommands/dispatch.py +42 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/metacommands/io.py +2 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/metacommands/io_export.py +75 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/script/engine.py +16 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/state.py +10 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/cli/test_cli.py +55 -0
- execsql2-2.8.0/tests/cli/test_profile.py +282 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/conftest.py +2 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/exporters/test_delimited.py +2 -8
- execsql2-2.8.0/tests/exporters/test_markdown.py +302 -0
- execsql2-2.8.0/tests/exporters/test_xlsx.py +419 -0
- execsql2-2.8.0/tests/exporters/test_yaml.py +140 -0
- execsql2-2.8.0/tests/metacommands/test_assert.py +197 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/uv.lock +5 -1
- {execsql2-2.6.0 → execsql2-2.8.0}/.claude/agents/dba.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/.claude/agents/herald.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/.claude/agents/inspector.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/.claude/agents/oracle.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/.claude/agents/patcher.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/.claude/agents/qa.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/.claude/agents/scribe.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/.claude/commands/code-oracle.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/.claude/commands/migrate.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/.claude/commands/review-changes.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/.claude/commands/test-module.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/.claude/commands/update-changelog.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/.claude/commands/where-is.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/.claude/state/status.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/.github/workflows/ci-cd.yml +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/.gitignore +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/.pre-commit-config.yaml +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/.pre-commit-hooks.yaml +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/.python-version +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/.readthedocs.yaml +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/CLAUDE.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/CONTRIBUTING.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/LICENSE.txt +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/NOTICE +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/SECURITY.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/about/contributors.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/about/copyright.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/api/cli.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/api/db.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/api/exporters.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/api/importers.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/api/index.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/api/metacommands.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/dev/adding_db_adapters.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/dev/adding_exporters.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/dev/adding_importers.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/dev/adding_metacommands.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/dev/architecture.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/getting-started/installation.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/getting-started/requirements.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/guides/debugging.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/guides/documentation.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/guides/encoding.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/guides/examples.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/guides/formatter.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/guides/logging.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/guides/sql_syntax.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/guides/usage.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/guides/using_scripts.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/images/Compare_planets.png +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/images/actions.png +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/images/actions2.png +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/images/checkboxes.png +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/images/connect.b64 +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/images/connect.png +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/images/create_conf.png +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/images/data_error1_screenshot.jpg +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/images/entry_form.png +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/images/execsql_console.png +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/images/execsql_logo_01.png +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/images/fatals.png +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/images/logo_small.png +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/images/pause_terminal.png +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/images/pause_terminal_sm.b64 +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/images/pause_terminal_sm.png +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/images/prompt_compare.png +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/images/set_build_commands.jpg +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/images/unit_conversions.b64 +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/images/unit_conversions_029.png +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/images/unmatched.png +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/images/vim_execsql_highlight.png +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/index.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/reference/configuration.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/reference/security.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/docs/reference/substitution_vars.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/extras/vscode-execsql/README.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/extras/vscode-execsql/package.json +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/justfile +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/scripts/generate_vscode_grammar.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/__init__.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/__main__.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/cli/dsn.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/cli/help.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/config.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/constants.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/db/__init__.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/db/access.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/db/base.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/db/dsn.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/db/duckdb.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/db/factory.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/db/firebird.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/db/mysql.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/db/oracle.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/db/postgres.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/db/sqlite.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/db/sqlserver.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/exceptions.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/exporters/base.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/exporters/duckdb.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/exporters/feather.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/exporters/html.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/exporters/json.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/exporters/latex.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/exporters/ods.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/exporters/parquet.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/exporters/pretty.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/exporters/protocol.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/exporters/raw.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/exporters/sqlite.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/exporters/templates.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/exporters/values.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/exporters/xls.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/exporters/xml.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/exporters/zip.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/format.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/gui/__init__.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/gui/base.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/gui/console.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/gui/desktop.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/gui/tui.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/importers/__init__.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/importers/base.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/importers/csv.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/importers/feather.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/importers/ods.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/importers/xls.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/metacommands/conditions.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/metacommands/connect.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/metacommands/data.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/metacommands/debug.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/metacommands/io_fileops.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/metacommands/io_import.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/metacommands/io_write.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/metacommands/prompt.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/metacommands/script_ext.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/metacommands/system.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/models.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/parser.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/py.typed +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/script/__init__.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/script/control.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/script/variables.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/types.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/utils/__init__.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/utils/auth.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/utils/crypto.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/utils/datetime.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/utils/errors.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/utils/fileio.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/utils/gui.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/utils/mail.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/utils/numeric.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/utils/regex.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/utils/strings.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/src/execsql/utils/timer.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/templates/README.md +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/templates/config_settings.sqlite +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/templates/example_config_prompt.sql +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/templates/execsql.conf +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/templates/make_config_db.sql +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/templates/md_compare.sql +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/templates/md_glossary.sql +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/templates/md_upsert.sql +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/templates/pg_compare.sql +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/templates/pg_glossary.sql +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/templates/pg_upsert.sql +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/templates/script_template.sql +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/templates/ss_compare.sql +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/templates/ss_glossary.sql +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/templates/ss_upsert.sql +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/__init__.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/cli/__init__.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/cli/test_cli_e2e.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/cli/test_cli_run.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/db/__init__.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/db/test_base.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/db/test_duckdb.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/db/test_factory.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/db/test_postgres.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/db/test_sqlite.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/db/test_sqlite_extra.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/exporters/__init__.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/exporters/test_base.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/exporters/test_db.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/exporters/test_duckdb_exporter.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/exporters/test_exporters.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/exporters/test_feather.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/exporters/test_html_latex.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/exporters/test_json.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/exporters/test_ods.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/exporters/test_parquet.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/exporters/test_sqlite_exporter.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/exporters/test_templates.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/exporters/test_xls_xlsx.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/exporters/test_xml.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/exporters/test_zip.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/gui/__init__.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/gui/test_backends.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/importers/__init__.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/importers/test_csv_importer.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/importers/test_feather_importer.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/importers/test_ods_importer.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/importers/test_xls_importer.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/integration/__init__.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/integration/conftest.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/integration/test_dsn.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/integration/test_duckdb.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/integration/test_mysql.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/integration/test_postgres.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/integration/test_sqlite.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/metacommands/__init__.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/metacommands/test_connect.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/metacommands/test_metacommands.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/metacommands/test_metacommands_connect.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/metacommands/test_metacommands_data.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/metacommands/test_metacommands_extended.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/metacommands/test_metacommands_fileops_extra.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/metacommands/test_metacommands_io.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/metacommands/test_metacommands_io_write_extra.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/metacommands/test_metacommands_script_ext.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/metacommands/test_metacommands_system.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/metacommands/test_metacommands_system_extra.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/test_config.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/test_config_data.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/test_constants.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/test_engine.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/test_exceptions.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/test_format.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/test_mail.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/test_models.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/test_package.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/test_parser.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/test_registry.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/test_script.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/test_state.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/test_types.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/utils/__init__.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/utils/test_auth.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/utils/test_auth_extra.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/utils/test_crypto.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/utils/test_datetime.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/utils/test_errors.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/utils/test_errors_extra.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/utils/test_fileio.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/utils/test_fileio_extra.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/utils/test_numeric.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/utils/test_regex.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/utils/test_strings.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/utils/test_timer.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/tests/utils/test_timer_extra.py +0 -0
- {execsql2-2.6.0 → execsql2-2.8.0}/zensical.toml +0 -0
|
@@ -265,16 +265,16 @@ ______________________________________________________________________
|
|
|
265
265
|
|
|
266
266
|
### v2.7 — New Export/Import Formats
|
|
267
267
|
|
|
268
|
-
- [
|
|
269
|
-
- [
|
|
270
|
-
- [
|
|
271
|
-
- [
|
|
268
|
+
- [x] **Parquet import** — already existed as `IMPORT TO table FROM PARQUET file` (verified present).
|
|
269
|
+
- [x] **YAML export** — `FORMAT YAML` via PyYAML, list-of-dicts with native type preservation.
|
|
270
|
+
- [x] **Markdown (GFM) export** — `FORMAT MARKDOWN` / `MD`, pipe tables with alignment and escaping.
|
|
271
|
+
- [x] **Excel (XLSX) multi-sheet export** — `FORMAT XLSX` single + multi-sheet via openpyxl, bold headers, inventory sheet, sheet name deduplication.
|
|
272
272
|
|
|
273
273
|
### v2.8 — Scripting Power Features
|
|
274
274
|
|
|
275
|
-
- [
|
|
276
|
-
- [
|
|
277
|
-
- [
|
|
275
|
+
- [x] **`ASSERT` metacommand** — `-- !x! ASSERT <condition> "message"`. Reuses IF condition engine. 13 tests.
|
|
276
|
+
- [x] **`--dry-run` improvements** — expands substitution variables populated at parse time. 4 new tests.
|
|
277
|
+
- [x] **Script profiling (`--profile`)** — per-statement `perf_counter()` timing with sorted summary table. 19 tests.
|
|
278
278
|
- [ ] **Parallel execution blocks** — `PARALLEL BEGIN ... PARALLEL END` for independent statements. See design notes below.
|
|
279
279
|
|
|
280
280
|
### v2.9 — Library API & Developer Experience
|
|
@@ -13,6 +13,37 @@ ______________________________________________________________________
|
|
|
13
13
|
|
|
14
14
|
______________________________________________________________________
|
|
15
15
|
|
|
16
|
+
## [2.8.0] - 2026-04-01
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- **`--profile` flag** — records wall-clock time for each SQL and metacommand statement and prints a formatted timing summary after the script completes. The summary lists statements sorted by elapsed time descending (top 20 shown), with per-statement percentage of total time, source location, command type, and a preview of the command text.
|
|
21
|
+
- **`ASSERT` metacommand** — evaluates any IF-compatible condition and raises an error (halting the script when `HALT_ON_METACOMMAND_ERROR` is `ON`) if the condition is false. Supports an optional quoted failure message; omitting the message produces `Assertion failed: <condition>`. A passing assertion is logged. ASSERT is silently skipped inside a false IF block.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- **`--dry-run` expands substitution variables** — the command list printed by `--dry-run` now shows resolved `!!$VAR!!` / `!!&ENV!!` tokens for variables that are already populated at parse time (environment variables, `--assign-arg` values, config-sourced variables, and built-in start-time variables like `$SCRIPT_START_TIME`). Variables that are set during execution (e.g. `$CURRENT_TIME`, `$DB_NAME`, `$TIMER`) remain unexpanded because the database connection has not yet been established. Local `~`-prefixed script-scope variables are also left unexpanded. If expansion fails (e.g. a cycle is detected), the raw token is displayed instead.
|
|
26
|
+
|
|
27
|
+
______________________________________________________________________
|
|
28
|
+
|
|
29
|
+
## [2.7.1] - 2026-04-01
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- Fix `AttributeError: module 'execsql.state' has no attribute 'dedup_words'` when importing CSV files with `DEDUP_COL_HDRS` enabled — `dedup_words` is now correctly imported from `execsql.utils.strings` instead of accessed through the state module.
|
|
34
|
+
|
|
35
|
+
______________________________________________________________________
|
|
36
|
+
|
|
37
|
+
## [2.7.0] - 2026-04-01
|
|
38
|
+
|
|
39
|
+
### Added
|
|
40
|
+
|
|
41
|
+
- **Markdown export** (`FORMAT MARKDOWN` / `FORMAT MD`) — GitHub-flavored pipe tables with column alignment, pipe/backslash escaping, and zip support. No dependencies required.
|
|
42
|
+
- **YAML export** (`FORMAT YAML`) — list-of-dicts output via PyYAML with native type preservation (int, float, null). Requires `PyYAML` (included in `formats` extras).
|
|
43
|
+
- **XLSX export** (`FORMAT XLSX`) — single-sheet and multi-sheet Excel export via openpyxl with bold headers, native type preservation, sheet name deduplication, and a "Datasheets" inventory sheet. Multi-sheet syntax: `EXPORT table1, table2 TO file.xlsx AS XLSX`.
|
|
44
|
+
|
|
45
|
+
______________________________________________________________________
|
|
46
|
+
|
|
16
47
|
## [2.6.0] - 2026-04-01
|
|
17
48
|
|
|
18
49
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: execsql2
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.8.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
|
|
@@ -54,6 +54,7 @@ Requires-Dist: polars; extra == 'all'
|
|
|
54
54
|
Requires-Dist: psycopg2-binary; extra == 'all'
|
|
55
55
|
Requires-Dist: pymysql; extra == 'all'
|
|
56
56
|
Requires-Dist: pyodbc; extra == 'all'
|
|
57
|
+
Requires-Dist: pyyaml; extra == 'all'
|
|
57
58
|
Requires-Dist: tables; extra == 'all'
|
|
58
59
|
Requires-Dist: xlrd; extra == 'all'
|
|
59
60
|
Provides-Extra: all-db
|
|
@@ -76,6 +77,7 @@ Requires-Dist: openpyxl; extra == 'dev'
|
|
|
76
77
|
Requires-Dist: polars; extra == 'dev'
|
|
77
78
|
Requires-Dist: pre-commit>=3.5.0; extra == 'dev'
|
|
78
79
|
Requires-Dist: pytest-cov>=5.0.0; extra == 'dev'
|
|
80
|
+
Requires-Dist: pyyaml; extra == 'dev'
|
|
79
81
|
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
80
82
|
Requires-Dist: tables; extra == 'dev'
|
|
81
83
|
Requires-Dist: tox-uv>=1.13.1; extra == 'dev'
|
|
@@ -91,6 +93,7 @@ Requires-Dist: jinja2; extra == 'formats'
|
|
|
91
93
|
Requires-Dist: odfpy; extra == 'formats'
|
|
92
94
|
Requires-Dist: openpyxl; extra == 'formats'
|
|
93
95
|
Requires-Dist: polars; extra == 'formats'
|
|
96
|
+
Requires-Dist: pyyaml; extra == 'formats'
|
|
94
97
|
Requires-Dist: tables; extra == 'formats'
|
|
95
98
|
Requires-Dist: xlrd; extra == 'formats'
|
|
96
99
|
Provides-Extra: mssql
|
|
@@ -227,9 +230,10 @@ Run `execsql --help` for the full option list, or `execsql -m` to list all metac
|
|
|
227
230
|
# Features
|
|
228
231
|
|
|
229
232
|
- Import data from CSV, TSV, Excel, OpenDocument, Feather, or Parquet files into a database table.
|
|
230
|
-
- Export query results in
|
|
233
|
+
- Export query results in 20+ formats including CSV, TSV, JSON, YAML, XML, HTML, Markdown, LaTeX, XLSX, OpenDocument, Feather, Parquet, HDF5, DuckDB, SQLite, plain text, and Jinja2 templates.
|
|
231
234
|
- Copy data between databases, including across different DBMS types.
|
|
232
235
|
- Conditionally execute SQL and metacommands using `IF`/`ELSE`/`ENDIF` based on data values, DBMS type, or user input.
|
|
236
|
+
- Validate data with `ASSERT` — halt the script with a clear error message if a condition is false (ideal for CI pipelines).
|
|
233
237
|
- Loop over blocks of SQL and metacommands using `LOOP`/`ENDLOOP`.
|
|
234
238
|
- Use substitution variables (`SUB`, `$ARG_x`, built-in variables like `$date_tag`) to parameterize scripts.
|
|
235
239
|
- Include or chain scripts with `INCLUDE` and `SCRIPT`.
|
|
@@ -120,9 +120,10 @@ Run `execsql --help` for the full option list, or `execsql -m` to list all metac
|
|
|
120
120
|
# Features
|
|
121
121
|
|
|
122
122
|
- Import data from CSV, TSV, Excel, OpenDocument, Feather, or Parquet files into a database table.
|
|
123
|
-
- Export query results in
|
|
123
|
+
- Export query results in 20+ formats including CSV, TSV, JSON, YAML, XML, HTML, Markdown, LaTeX, XLSX, OpenDocument, Feather, Parquet, HDF5, DuckDB, SQLite, plain text, and Jinja2 templates.
|
|
124
124
|
- Copy data between databases, including across different DBMS types.
|
|
125
125
|
- Conditionally execute SQL and metacommands using `IF`/`ELSE`/`ENDIF` based on data values, DBMS type, or user input.
|
|
126
|
+
- Validate data with `ASSERT` — halt the script with a clear error message if a condition is false (ideal for CI pipelines).
|
|
126
127
|
- Loop over blocks of SQL and metacommands using `LOOP`/`ENDLOOP`.
|
|
127
128
|
- Use substitution variables (`SUB`, `$ARG_x`, built-in variables like `$date_tag`) to parameterize scripts.
|
|
128
129
|
- Include or chain scripts with `INCLUDE` and `SCRIPT`.
|
|
@@ -13,30 +13,35 @@ ______________________________________________________________________
|
|
|
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
|
-
| `--dry-run` | Parse the script and print the full command list without connecting to a database or executing anything.
|
|
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
|
+
| `--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
|
+
| `--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. |
|
|
26
27
|
|
|
27
28
|
### Export Formats
|
|
28
29
|
|
|
29
|
-
| Format
|
|
30
|
-
|
|
|
31
|
-
| `PARQUET`
|
|
32
|
-
| `FEATHER`
|
|
30
|
+
| Format | Description |
|
|
31
|
+
| ----------------- | -------------------------------------------------------------------------------------------------------------------- |
|
|
32
|
+
| `PARQUET` | Export query or table results to Apache Parquet via `polars`. |
|
|
33
|
+
| `FEATHER` | Export to Apache Feather/IPC via `polars` + `pyarrow` (upstream used `pandas`). |
|
|
34
|
+
| `YAML` | Export query or table results as a YAML sequence of mappings via `PyYAML`. |
|
|
35
|
+
| `MARKDOWN` / `MD` | Export query or table results as a GitHub-Flavored Markdown (GFM) pipe table. Pure Python, no optional dependencies. |
|
|
36
|
+
| `XLSX` | Export query or table results to an Excel XLSX workbook via `openpyxl` (single or multi-sheet). |
|
|
33
37
|
|
|
34
38
|
### Metacommands
|
|
35
39
|
|
|
36
|
-
| Metacommand | Description
|
|
37
|
-
| ---------------------- |
|
|
38
|
-
| `
|
|
39
|
-
| `CONFIG
|
|
40
|
+
| Metacommand | Description |
|
|
41
|
+
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
42
|
+
| `ASSERT` | Evaluate a condition and raise an error (halting the script) if it is false. Supports all IF conditions. Optional quoted failure message. Skipped in false IF blocks. |
|
|
43
|
+
| `CONFIG SHOW_PROGRESS` | Enable the Rich progress bar for IMPORT operations at runtime. |
|
|
44
|
+
| `CONFIG LOG_SQL` | Enable SQL query audit logging — writes executed SQL to the log file. |
|
|
40
45
|
|
|
41
46
|
### Configuration Options
|
|
42
47
|
|
|
@@ -175,21 +175,33 @@ Valid encoding names can be displayed with the `-y` option. See also [Character
|
|
|
175
175
|
### Informational options
|
|
176
176
|
|
|
177
177
|
`-m`, `--metacommands`
|
|
178
|
+
|
|
178
179
|
: List all metacommands and exit.
|
|
179
180
|
|
|
180
181
|
`-o`, `--online-help`
|
|
182
|
+
|
|
181
183
|
: Open the online documentation in the default browser.
|
|
182
184
|
|
|
183
185
|
`-y`, `--encodings`
|
|
186
|
+
|
|
184
187
|
: List all valid character encoding names and exit.
|
|
185
188
|
|
|
186
189
|
`--dump-keywords`
|
|
190
|
+
|
|
187
191
|
: Dump all metacommand keywords, conditional functions, config options, and export formats as JSON and exit. Useful for tooling that consumes execsql's keyword registry (e.g., the VS Code grammar generator).
|
|
188
192
|
|
|
189
193
|
`--dry-run`
|
|
194
|
+
|
|
190
195
|
: Parse the script (or inline `-c` command) and print the full command list — SQL statements and metacommands with source locations — without connecting to a database or executing anything. Useful for validating scripts.
|
|
191
196
|
|
|
197
|
+
Substitution variables that are already populated at parse time are expanded in the output: environment variables (`!!&ENV_VAR!!`), `--assign-arg` values (`!!$ARG_1!!`), and built-in start-time variables like `!!$SCRIPT_START_TIME!!` and `!!$USER!!`. Variables that are set during execution — such as `$CURRENT_TIME`, `$DB_NAME`, and `$TIMER` — remain unexpanded because no database connection is established in dry-run mode. Local `~`-prefixed script-scope variables are also left unexpanded.
|
|
198
|
+
|
|
199
|
+
`--profile`
|
|
200
|
+
|
|
201
|
+
: 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.
|
|
202
|
+
|
|
192
203
|
`--version`
|
|
204
|
+
|
|
193
205
|
: Show the version number and exit.
|
|
194
206
|
|
|
195
207
|
## Configuration File Defaults { #config_defaults }
|
|
@@ -48,6 +48,42 @@ Double quotes (as shown above), apostrophes, or square brackets can be used to d
|
|
|
48
48
|
|
|
49
49
|
See the [PROMPT ASK](#prompt_ask) metacommand for a version of this command that uses a GUI window and that can display a data table with the prompt.
|
|
50
50
|
|
|
51
|
+
## ASSERT { #assert }
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
ASSERT <condition>
|
|
55
|
+
ASSERT <condition> "<failure message>"
|
|
56
|
+
ASSERT <condition> '<failure message>'
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Evaluates `<condition>` using the same expression engine as [IF](#if_cmd). If the condition is `True`, execution continues silently (and the result is written to the log). If the condition is `False`, an error is raised with the provided failure message. If no message is supplied, the default message is `Assertion failed: <condition>`.
|
|
60
|
+
|
|
61
|
+
When [HALT_ON_METACOMMAND_ERROR](#config) is `ON` (the default), a failed assertion halts the script. When it is `OFF`, execution continues after the failure is logged.
|
|
62
|
+
|
|
63
|
+
The `<condition>` supports all conditional tests available to `IF`, including:
|
|
64
|
+
|
|
65
|
+
- `TABLE_EXISTS <table>` / `TABLE_NOT_EXISTS <table>`
|
|
66
|
+
- `COLUMN_EXISTS <table> <column>` / `COLUMN_NOT_EXISTS <table> <column>`
|
|
67
|
+
- `ROWCOUNT <op> <n>` (e.g. `ROWCOUNT > 0`)
|
|
68
|
+
- Variable comparisons: `$varname = 'value'`, `$varname != 'value'`
|
|
69
|
+
- Numeric comparisons: `$varname > <n>`, `$varname <= <n>`
|
|
70
|
+
- `DATABASE_TYPE <type>` / `DATABASE_TYPE_NOT <type>`
|
|
71
|
+
|
|
72
|
+
ASSERT is silently skipped inside a `False` [IF](#if_cmd) block.
|
|
73
|
+
|
|
74
|
+
**Examples:**
|
|
75
|
+
|
|
76
|
+
```sql
|
|
77
|
+
-- Halt with a custom message if the staging table is missing.
|
|
78
|
+
-- !x! ASSERT TABLE_EXISTS staging "staging table must exist before running this script"
|
|
79
|
+
|
|
80
|
+
-- Halt with the default message if no rows were returned.
|
|
81
|
+
-- !x! ASSERT ROWCOUNT > 0
|
|
82
|
+
|
|
83
|
+
-- Verify a substitution variable has the expected value.
|
|
84
|
+
-- !x! ASSERT $env = 'prod' 'expected production environment'
|
|
85
|
+
```
|
|
86
|
+
|
|
51
87
|
## AUTOCOMMIT
|
|
52
88
|
|
|
53
89
|
```
|
|
@@ -932,7 +968,22 @@ JSON_TS or JSON_TABLESCHEMA
|
|
|
932
968
|
|
|
933
969
|
LATEX
|
|
934
970
|
|
|
935
|
-
: Input for the [
|
|
971
|
+
: Input for the [LaTeX](https://www.latex-project.org/) typesetting system. If the "APPEND" keyword is not used, a complete document (of class article) will be written. If the "APPEND" keyword is used, only the table definition will be written to the output file. If the "APPEND" keyword is used and an existing output file contains an \\end directive, the table will be written before that directive rather than at the physical end of the file. Wide or long tables may exceed LaTeΧ's default page size. If the "DESCRIPTION" keyword is used, the given description will be used as the table's caption. Data exported in LaTeX format cannot be written into a zipfile.
|
|
972
|
+
|
|
973
|
+
|
|
974
|
+
MARKDOWN or MD
|
|
975
|
+
|
|
976
|
+
: [GitHub-Flavored Markdown](https://github.github.com/gfm/) pipe table. Column values are aligned and pipe (`|`) and backslash (`\`) characters in data are escaped. If the "DESCRIPTION" keyword is used, the description is written as an HTML comment (`<!-- ... -->`) before the table. If the "APPEND" keyword is used, only the table is appended (no repeated headers). No optional dependencies required.
|
|
977
|
+
|
|
978
|
+
|
|
979
|
+
XLSX
|
|
980
|
+
|
|
981
|
+
: [Excel](https://www.microsoft.com/en-us/microsoft-365/excel) workbook in the Office Open XML format. One or more tables (or views) can be exported to an XLSX workbook. Each table will be exported to a separate worksheet within the workbook, with the first row containing bold column headers. To export multiple tables, their names must be separated by commas. The "APPEND" keyword can be used to add worksheets to an existing workbook. The name of the view or table exported will be used as the worksheet name; if this conflicts with a sheet already in the workbook, a number will be appended to make the sheet name unique. A "Datasheets" inventory sheet is created with author, date, description, and source information for each data sheet. Data types are preserved natively (integers, floats, dates, datetimes, booleans). The `openpyxl` library must be installed (`pip install execsql2[excel]`). Data exported in XLSX format cannot be written into a zipfile.
|
|
982
|
+
|
|
983
|
+
|
|
984
|
+
YAML
|
|
985
|
+
|
|
986
|
+
: [YAML](https://yaml.org/) sequence of mappings. Each row is represented as a mapping (dictionary) with column names as keys. Python data types are preserved — integers remain integers, floats remain floats, and `None` becomes YAML `null`. If the "APPEND" keyword is used, a new YAML document is appended to the file (multi-document stream). The `PyYAML` library must be installed (`pip install execsql2[formats]`). No description text is included in the output even if provided.
|
|
936
987
|
|
|
937
988
|
|
|
938
989
|
SQLITE
|
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
},
|
|
94
94
|
"action-keywords": {
|
|
95
95
|
"comment": "sub, write, execute script, export, etc.",
|
|
96
|
-
"match": "(?i)\\b(reset\\s+dialog_canceled|write\\s+create_table|export_metadata|sub_querystring|execute\\s+script|append\\s+script|extend\\s+script|reset\\s+counter|export\\s+query|sub_tempfile|write\\s+script|import_file|set\\s+counter|sub_decrypt|sub_encrypt|autocommit|copy\\s+query|disconnect|select_sub|sub_append|system_cmd|pg_vacuum|sub_empty|sub_local|connect|include|max_int|rm_file|subdata|sub_add|sub_ini|export|import|rm_sub|debug|email|serve|write|copy|log|run|sub|use|zip|cd)\\b",
|
|
96
|
+
"match": "(?i)\\b(reset\\s+dialog_canceled|write\\s+create_table|export_metadata|sub_querystring|execute\\s+script|append\\s+script|extend\\s+script|reset\\s+counter|export\\s+query|sub_tempfile|write\\s+script|import_file|set\\s+counter|sub_decrypt|sub_encrypt|autocommit|copy\\s+query|disconnect|select_sub|sub_append|system_cmd|pg_vacuum|sub_empty|sub_local|connect|include|max_int|rm_file|subdata|sub_add|sub_ini|assert|export|import|rm_sub|debug|email|serve|write|copy|log|run|sub|use|zip|cd)\\b",
|
|
97
97
|
"name": "keyword.other.execsql"
|
|
98
98
|
},
|
|
99
99
|
"config-event-keywords": {
|
|
@@ -118,7 +118,7 @@
|
|
|
118
118
|
},
|
|
119
119
|
"export-formats": {
|
|
120
120
|
"comment": "Export/import format names",
|
|
121
|
-
"match": "(?i)\\b(json_tableschema|cgi-html|feather|json_ts|txt-and|unitsep|duckdb|sqlite|values|latex|plain|hdf5|html|json|tabq|tsvq|b64|csv|ods|raw|tab|tsv|txt|xml|us)\\b",
|
|
121
|
+
"match": "(?i)\\b(json_tableschema|cgi-html|markdown|feather|json_ts|txt-and|unitsep|duckdb|sqlite|values|latex|plain|hdf5|html|json|tabq|tsvq|xlsx|yaml|b64|csv|ods|raw|tab|tsv|txt|xml|md|us)\\b",
|
|
122
122
|
"name": "support.constant.execsql"
|
|
123
123
|
},
|
|
124
124
|
"secondary-operators": {
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "execsql2"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.8.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" }
|
|
@@ -56,7 +56,7 @@ firebird = ["firebird-driver"]
|
|
|
56
56
|
oracle = ["oracledb"]
|
|
57
57
|
odbc = ["pyodbc"]
|
|
58
58
|
# Feature bundles
|
|
59
|
-
formats = ["odfpy", "xlrd", "openpyxl", "Jinja2", "polars", "tables"]
|
|
59
|
+
formats = ["odfpy", "xlrd", "openpyxl", "Jinja2", "polars", "tables", "PyYAML"]
|
|
60
60
|
auth = ["keyring"]
|
|
61
61
|
# Convenience groups
|
|
62
62
|
all-db = [
|
|
@@ -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.8.0"
|
|
162
162
|
commit = true
|
|
163
163
|
commit_args = "--no-verify"
|
|
164
164
|
tag = true
|
|
@@ -254,6 +254,11 @@ def main(
|
|
|
254
254
|
"--dump-keywords",
|
|
255
255
|
help="Dump all metacommand keywords as JSON and exit.",
|
|
256
256
|
),
|
|
257
|
+
profile: bool = typer.Option(
|
|
258
|
+
False,
|
|
259
|
+
"--profile",
|
|
260
|
+
help="Record per-statement execution times and print a timing summary after the script completes.",
|
|
261
|
+
),
|
|
257
262
|
version: bool | None = typer.Option(
|
|
258
263
|
None,
|
|
259
264
|
"--version",
|
|
@@ -415,6 +420,7 @@ def main(
|
|
|
415
420
|
dsn=dsn,
|
|
416
421
|
output_dir=output_dir,
|
|
417
422
|
progress=progress,
|
|
423
|
+
profile=profile,
|
|
418
424
|
)
|
|
419
425
|
|
|
420
426
|
|
|
@@ -20,11 +20,11 @@ from execsql.cli.dsn import _parse_connection_string
|
|
|
20
20
|
from execsql.cli.help import _console, _err_console
|
|
21
21
|
from execsql.config import ConfigData, StatObj
|
|
22
22
|
from execsql.exceptions import ConfigError, ErrInfo
|
|
23
|
-
from execsql.script import SubVarSet, current_script_line, read_sqlfile, read_sqlstring, runscripts
|
|
23
|
+
from execsql.script import SubVarSet, current_script_line, read_sqlfile, read_sqlstring, runscripts, substitute_vars
|
|
24
24
|
from execsql.utils.fileio import FileWriter, Logger, filewriter_end
|
|
25
25
|
from execsql.utils.gui import gui_connect, gui_console_isrunning, gui_console_off, gui_console_on, gui_console_wait_user
|
|
26
26
|
|
|
27
|
-
__all__ = ["_connect_initial_db", "_print_dry_run", "_run"]
|
|
27
|
+
__all__ = ["_connect_initial_db", "_print_dry_run", "_print_profile", "_run"]
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
# ---------------------------------------------------------------------------
|
|
@@ -33,7 +33,16 @@ __all__ = ["_connect_initial_db", "_print_dry_run", "_run"]
|
|
|
33
33
|
|
|
34
34
|
|
|
35
35
|
def _print_dry_run(cmdlist: object) -> None:
|
|
36
|
-
"""Print the parsed command list for --dry-run mode.
|
|
36
|
+
"""Print the parsed command list for --dry-run mode.
|
|
37
|
+
|
|
38
|
+
Substitution variables (``$VAR``, ``&ENV``, ``@COUNTER``) that are already
|
|
39
|
+
populated — from environment variables, ``--assign-arg`` values, or config —
|
|
40
|
+
are expanded in the displayed text. System variables that are set at
|
|
41
|
+
execution time (e.g. ``$CURRENT_TIME``, ``$DB_NAME``, ``$TIMER``) will
|
|
42
|
+
appear unexpanded because ``set_system_vars()`` has not yet been called.
|
|
43
|
+
Local ``~``-prefixed script-scope variables are also not expanded (no script
|
|
44
|
+
execution context exists in dry-run mode).
|
|
45
|
+
"""
|
|
37
46
|
if cmdlist is None or not cmdlist.cmdlist:
|
|
38
47
|
_console.print("[yellow]No commands found in script.[/yellow]")
|
|
39
48
|
return
|
|
@@ -43,7 +52,71 @@ def _print_dry_run(cmdlist: object) -> None:
|
|
|
43
52
|
for i, cmd in enumerate(cmdlist.cmdlist, 1):
|
|
44
53
|
ctype = "SQL " if cmd.command_type == "sql" else "METACMD"
|
|
45
54
|
source_info = f"[dim]{cmd.source}:{cmd.line_no}[/dim]"
|
|
46
|
-
|
|
55
|
+
raw = cmd.commandline()
|
|
56
|
+
try:
|
|
57
|
+
expanded = substitute_vars(raw)
|
|
58
|
+
except Exception:
|
|
59
|
+
# Cycle detection or other expansion errors — fall back to raw text.
|
|
60
|
+
expanded = raw
|
|
61
|
+
_console.print(f" [dim]{i:>4}[/dim] [bold green]{ctype}[/bold green] {source_info} {expanded}")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# ---------------------------------------------------------------------------
|
|
65
|
+
# Profile report helper
|
|
66
|
+
# ---------------------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _print_profile(profile_data: list[tuple]) -> None:
|
|
70
|
+
"""Print a per-statement timing summary to stdout.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
profile_data: List of ``(source, line_no, command_type, elapsed_secs,
|
|
74
|
+
command_text_preview)`` tuples collected during execution.
|
|
75
|
+
"""
|
|
76
|
+
if not profile_data:
|
|
77
|
+
_console.print("[dim]Profile: no statements recorded.[/dim]")
|
|
78
|
+
return
|
|
79
|
+
|
|
80
|
+
total_secs = sum(row[3] for row in profile_data)
|
|
81
|
+
n = len(profile_data)
|
|
82
|
+
|
|
83
|
+
# Sort descending by elapsed time; show top 20 (or all if <= 20).
|
|
84
|
+
sorted_data = sorted(profile_data, key=lambda r: r[3], reverse=True)
|
|
85
|
+
display = sorted_data[:20]
|
|
86
|
+
|
|
87
|
+
_console.print()
|
|
88
|
+
_console.print(f"[bold cyan]Profile:[/bold cyan] {n} statement{'s' if n != 1 else ''} in {total_secs:.3f}s")
|
|
89
|
+
_console.print()
|
|
90
|
+
|
|
91
|
+
header = f" {'Time (s)':<10} {'Pct':<7} {'Source:Line':<20} {'Type':<7} Command"
|
|
92
|
+
sep = f" {'-' * 10} {'-' * 7} {'-' * 20} {'-' * 7} {'-' * 40}"
|
|
93
|
+
_console.print(f"[dim]{header}[/dim]")
|
|
94
|
+
_console.print(f"[dim]{sep}[/dim]")
|
|
95
|
+
|
|
96
|
+
for source, line_no, command_type, elapsed, preview in display:
|
|
97
|
+
pct = (elapsed / total_secs * 100) if total_secs > 0 else 0.0
|
|
98
|
+
source_col = f"{source}:{line_no}"
|
|
99
|
+
if len(source_col) > 20:
|
|
100
|
+
source_col = "..." + source_col[-17:]
|
|
101
|
+
ctype_label = "SQL " if command_type == "sql" else "METACMD"
|
|
102
|
+
preview_short = preview[:50].replace("\n", " ").strip()
|
|
103
|
+
if len(preview) > 50:
|
|
104
|
+
preview_short += "..."
|
|
105
|
+
_console.print(
|
|
106
|
+
f" [yellow]{elapsed:<10.3f}[/yellow] "
|
|
107
|
+
f"[dim]{pct:<6.1f}%[/dim] "
|
|
108
|
+
f"[cyan]{source_col:<20}[/cyan] "
|
|
109
|
+
f"[green]{ctype_label:<7}[/green] "
|
|
110
|
+
f"{preview_short}",
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
if len(sorted_data) > 20:
|
|
114
|
+
omitted = len(sorted_data) - 20
|
|
115
|
+
_console.print(
|
|
116
|
+
f"[dim] ... {omitted} more statement{'s' if omitted != 1 else ''} not shown (top 20 by time)[/dim]",
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
_console.print()
|
|
47
120
|
|
|
48
121
|
|
|
49
122
|
# ---------------------------------------------------------------------------
|
|
@@ -76,6 +149,7 @@ def _run(
|
|
|
76
149
|
dsn: str | None = None,
|
|
77
150
|
output_dir: str | None = None,
|
|
78
151
|
progress: bool = False,
|
|
152
|
+
profile: bool = False,
|
|
79
153
|
) -> None:
|
|
80
154
|
"""Initialise state, connect to the database, load the script, and run it.
|
|
81
155
|
|
|
@@ -378,7 +452,10 @@ def _run(
|
|
|
378
452
|
atexit.register(_state.dbs.closeall)
|
|
379
453
|
_state.dbs.do_rollback = True
|
|
380
454
|
|
|
381
|
-
|
|
455
|
+
if profile:
|
|
456
|
+
_state.profile_data = []
|
|
457
|
+
|
|
458
|
+
_execute_script_direct(conf, profile=profile)
|
|
382
459
|
|
|
383
460
|
|
|
384
461
|
# ---------------------------------------------------------------------------
|
|
@@ -437,8 +514,15 @@ def _execute_script_textual_console(conf: ConfigData) -> None:
|
|
|
437
514
|
_state.exec_log.log_exit_end()
|
|
438
515
|
|
|
439
516
|
|
|
440
|
-
def _execute_script_direct(conf: ConfigData) -> None:
|
|
441
|
-
"""Run runscripts() in the current (main) thread — used when Textual is not active.
|
|
517
|
+
def _execute_script_direct(conf: ConfigData, *, profile: bool = False) -> None:
|
|
518
|
+
"""Run runscripts() in the current (main) thread — used when Textual is not active.
|
|
519
|
+
|
|
520
|
+
Args:
|
|
521
|
+
conf: The active configuration object.
|
|
522
|
+
profile: When ``True``, print a per-statement timing summary after the
|
|
523
|
+
script completes. Timing data must already have been activated on
|
|
524
|
+
``_state.profile_data`` before this function is called.
|
|
525
|
+
"""
|
|
442
526
|
import execsql.state as _state
|
|
443
527
|
import execsql.utils.gui as _gui
|
|
444
528
|
|
|
@@ -463,6 +547,8 @@ def _execute_script_direct(conf: ConfigData) -> None:
|
|
|
463
547
|
if gui_console_isrunning():
|
|
464
548
|
gui_console_off()
|
|
465
549
|
_state.exec_log.log_status_info(f"{_state.cmds_run} commands run")
|
|
550
|
+
if profile and _state.profile_data is not None:
|
|
551
|
+
_print_profile(_state.profile_data)
|
|
466
552
|
sys.exit(exc.code)
|
|
467
553
|
except ConfigError:
|
|
468
554
|
raise
|
|
@@ -489,6 +575,8 @@ def _execute_script_direct(conf: ConfigData) -> None:
|
|
|
489
575
|
if gui_console_isrunning():
|
|
490
576
|
gui_console_off()
|
|
491
577
|
_state.exec_log.log_status_info(f"{_state.cmds_run} commands run")
|
|
578
|
+
if profile and _state.profile_data is not None:
|
|
579
|
+
_print_profile(_state.profile_data)
|
|
492
580
|
_state.exec_log.log_exit_end()
|
|
493
581
|
|
|
494
582
|
|
|
@@ -9,9 +9,9 @@ handlers can access them via ``_state.write_query_to_csv`` etc. without
|
|
|
9
9
|
importing directly from here.
|
|
10
10
|
|
|
11
11
|
Sub-modules: ``base``, ``delimited``, ``json``, ``xml``, ``html``,
|
|
12
|
-
``latex``, ``ods``, ``xls``, ``zip``, ``raw``, ``pretty``,
|
|
13
|
-
``templates``, ``feather``, ``parquet``, ``duckdb``,
|
|
14
|
-
``protocol``.
|
|
12
|
+
``latex``, ``markdown``, ``ods``, ``xls``, ``zip``, ``raw``, ``pretty``,
|
|
13
|
+
``values``, ``templates``, ``feather``, ``parquet``, ``duckdb``,
|
|
14
|
+
``sqlite``, ``protocol``.
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
17
|
from execsql.exporters.protocol import QueryExporter, RowsetExporter
|
|
@@ -27,7 +27,7 @@ from execsql.exceptions import ErrInfo
|
|
|
27
27
|
from execsql.models import DataTable
|
|
28
28
|
from execsql.utils.errors import exception_desc
|
|
29
29
|
from execsql.utils.fileio import filewriter_close
|
|
30
|
-
from execsql.utils.strings import clean_words, fold_words
|
|
30
|
+
from execsql.utils.strings import clean_words, dedup_words, fold_words
|
|
31
31
|
|
|
32
32
|
__all__ = ["LineDelimiter", "CsvFile", "CsvWriter", "DelimitedWriter", "write_delimited_file"]
|
|
33
33
|
|
|
@@ -677,7 +677,7 @@ class CsvFile(EncodedFile):
|
|
|
677
677
|
if conf.fold_col_hdrs != "no":
|
|
678
678
|
colnames = fold_words(colnames, conf.fold_col_hdrs)
|
|
679
679
|
if conf.dedup_col_hdrs:
|
|
680
|
-
colnames =
|
|
680
|
+
colnames = dedup_words(colnames)
|
|
681
681
|
return colnames
|
|
682
682
|
|
|
683
683
|
def column_headers(self) -> list[str]:
|