execsql2 2.16.13__tar.gz → 2.16.15__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.16.13 → execsql2-2.16.15}/CHANGELOG.md +18 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/PKG-INFO +1 -1
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/about/divergence.md +15 -14
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/reference/metacommands.md +25 -22
- {execsql2-2.16.13 → execsql2-2.16.15}/extras/vscode-execsql/syntaxes/execsql.tmLanguage.json +1 -1
- {execsql2-2.16.13 → execsql2-2.16.15}/pyproject.toml +2 -2
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/db/base.py +2 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/format.py +0 -1
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/metacommands/debug.py +45 -41
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/metacommands/dispatch.py +2 -9
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/metacommands/io_export.py +14 -10
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/metacommands/io_fileops.py +4 -6
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/script/ast.py +3 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/script/executor.py +7 -3
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/script/parser.py +20 -14
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/metacommands/test_show_scripts.py +19 -9
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/test_ast_parser.py +56 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/test_executor.py +91 -16
- {execsql2-2.16.13 → execsql2-2.16.15}/uv.lock +1 -1
- {execsql2-2.16.13 → execsql2-2.16.15}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/.github/workflows/ci-cd.yml +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/.gitignore +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/.pre-commit-config.yaml +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/.pre-commit-hooks.yaml +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/.python-version +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/.readthedocs.yaml +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/CONTRIBUTING.md +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/LICENSE.txt +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/NOTICE +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/README.md +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/SECURITY.md +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/about/contributors.md +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/about/copyright.md +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/api/cli.md +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/api/db.md +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/api/exporters.md +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/api/importers.md +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/api/index.md +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/api/metacommands.md +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/dev/adding_db_adapters.md +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/dev/adding_exporters.md +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/dev/adding_importers.md +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/dev/adding_metacommands.md +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/dev/architecture.md +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/getting-started/installation.md +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/getting-started/requirements.md +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/getting-started/syntax.md +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/guides/debugging.md +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/guides/documentation.md +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/guides/encoding.md +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/guides/examples.md +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/guides/formatter.md +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/guides/logging.md +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/guides/sql_syntax.md +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/guides/usage.md +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/guides/using_scripts.md +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/images/Compare_planets.png +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/images/actions.png +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/images/actions2.png +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/images/checkboxes.png +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/images/connect.b64 +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/images/connect.png +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/images/create_conf.png +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/images/data_error1_screenshot.jpg +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/images/entry_form.png +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/images/execsql_console.png +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/images/execsql_logo_01.png +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/images/fatals.png +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/images/logo_small.png +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/images/pause_terminal.png +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/images/pause_terminal_sm.b64 +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/images/pause_terminal_sm.png +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/images/prompt_compare.png +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/images/set_build_commands.jpg +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/images/unit_conversions.b64 +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/images/unit_conversions_029.png +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/images/unmatched.png +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/images/vim_execsql_highlight.png +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/index.md +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/reference/configuration.md +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/reference/security.md +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/docs/reference/substitution_vars.md +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/extras/plugin-template/README.md +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/extras/plugin-template/pyproject.toml +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/extras/plugin-template/src/execsql_plugin_YOURNAME/__init__.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/extras/plugin-template/tests/test_plugin.py.example +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/extras/vscode-execsql/README.md +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/extras/vscode-execsql/package.json +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/justfile +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/scripts/generate_vscode_grammar.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/__init__.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/__main__.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/api.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/cli/__init__.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/cli/dsn.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/cli/help.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/cli/lint.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/cli/lint_ast.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/cli/run.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/config.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/db/__init__.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/db/access.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/db/dsn.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/db/duckdb.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/db/factory.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/db/firebird.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/db/mysql.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/db/oracle.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/db/postgres.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/db/sqlite.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/db/sqlserver.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/debug/__init__.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/debug/repl.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/exceptions.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/exporters/__init__.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/exporters/base.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/exporters/delimited.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/exporters/duckdb.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/exporters/feather.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/exporters/html.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/exporters/json.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/exporters/latex.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/exporters/markdown.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/exporters/ods.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/exporters/parquet.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/exporters/pretty.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/exporters/protocol.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/exporters/raw.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/exporters/sqlite.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/exporters/templates.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/exporters/values.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/exporters/xls.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/exporters/xlsx.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/exporters/xml.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/exporters/yaml.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/exporters/zip.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/gui/__init__.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/gui/base.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/gui/console.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/gui/desktop.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/gui/tui.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/importers/__init__.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/importers/base.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/importers/csv.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/importers/feather.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/importers/json.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/importers/ods.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/importers/xls.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/metacommands/__init__.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/metacommands/conditions.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/metacommands/connect.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/metacommands/control.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/metacommands/data.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/metacommands/io.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/metacommands/io_import.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/metacommands/io_write.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/metacommands/prompt.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/metacommands/script_ext.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/metacommands/system.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/metacommands/upsert.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/models.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/parser.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/plugins.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/py.typed +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/script/__init__.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/script/control.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/script/engine.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/script/variables.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/state.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/types.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/utils/__init__.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/utils/auth.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/utils/crypto.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/utils/datetime.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/utils/errors.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/utils/fileio.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/utils/gui.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/utils/mail.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/utils/numeric.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/utils/regex.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/utils/strings.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/utils/timer.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/templates/README.md +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/templates/config_settings.sqlite +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/templates/example_config_prompt.sql +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/templates/execsql.conf +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/templates/make_config_db.sql +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/templates/md_compare.sql +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/templates/md_glossary.sql +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/templates/md_upsert.sql +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/templates/pg_compare.sql +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/templates/pg_glossary.sql +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/templates/pg_upsert.sql +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/templates/script_template.sql +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/templates/ss_compare.sql +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/templates/ss_glossary.sql +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/templates/ss_upsert.sql +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/__init__.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/cli/__init__.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/cli/test_cli.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/cli/test_cli_e2e.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/cli/test_cli_run.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/cli/test_lint.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/cli/test_ping.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/cli/test_profile.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/conftest.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/db/__init__.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/db/test_base.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/db/test_db_adapters_mocked.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/db/test_dsn.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/db/test_duckdb.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/db/test_factory.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/db/test_postgres.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/db/test_sqlite.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/db/test_sqlite_extra.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/__init__.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_base.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_db.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_delimited.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_duckdb_exporter.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_exporters.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_feather.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_html_extended.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_html_latex.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_json.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_json_extended.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_latex_extended.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_markdown.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_ods.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_parquet.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_pretty_extended.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_raw_extended.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_sqlite_exporter.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_templates.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_templates_extended.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_values_extended.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_xls_xlsx.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_xlsx.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_xml.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_yaml.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_zip.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/gui/__init__.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/gui/test_backends.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/gui/test_compare_stats.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/gui/test_compute_row_diffs.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/importers/__init__.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/importers/test_base_extended.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/importers/test_csv_edge_cases.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/importers/test_csv_importer.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/importers/test_feather_importer.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/importers/test_json_importer.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/importers/test_ods_importer.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/importers/test_xls_importer.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/integration/__init__.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/integration/conftest.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/integration/test_dsn.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/integration/test_duckdb.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/integration/test_mysql.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/integration/test_postgres.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/integration/test_sqlite.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/metacommands/__init__.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/metacommands/test_assert.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/metacommands/test_breakpoint.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/metacommands/test_connect.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/metacommands/test_io_export.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/metacommands/test_io_import.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/metacommands/test_metacommands.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/metacommands/test_metacommands_connect.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/metacommands/test_metacommands_data.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/metacommands/test_metacommands_extended.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/metacommands/test_metacommands_fileops_extra.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/metacommands/test_metacommands_io.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/metacommands/test_metacommands_io_write_extra.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/metacommands/test_metacommands_script_ext.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/metacommands/test_metacommands_system.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/metacommands/test_metacommands_system_extra.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/metacommands/test_pg_upsert.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/metacommands/test_row_count.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/scripts/__init__.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/scripts/fixtures/control_flow.sql +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/scripts/fixtures/io_roundtrip.sql +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/scripts/fixtures/parse_only/parse_tree.sql +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/scripts/fixtures/smoke.sql +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/scripts/test_sql_scripts.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/test_api.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/test_ast.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/test_config.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/test_config_data.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/test_config_extended.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/test_debug_repl.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/test_engine.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/test_error_messages.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/test_exceptions.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/test_format.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/test_mail.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/test_models.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/test_package.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/test_parser.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/test_parser_params.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/test_plugins.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/test_registry.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/test_script.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/test_state.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/test_types.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/utils/__init__.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/utils/test_auth.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/utils/test_auth_extra.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/utils/test_crypto.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/utils/test_datetime.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/utils/test_errors.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/utils/test_errors_extra.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/utils/test_fileio.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/utils/test_fileio_extra.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/utils/test_numeric.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/utils/test_regex.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/utils/test_strings.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/utils/test_timer.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/tests/utils/test_timer_extra.py +0 -0
- {execsql2-2.16.13 → execsql2-2.16.15}/zensical.toml +0 -0
|
@@ -13,6 +13,24 @@ ______________________________________________________________________
|
|
|
13
13
|
|
|
14
14
|
______________________________________________________________________
|
|
15
15
|
|
|
16
|
+
## [2.16.15] - 2026-05-02
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- Merged `SHOW SCRIPTS` and `SHOW SCRIPT <name>` into a single `SHOW SCRIPTS [<name>]` metacommand. Without a name, lists all registered scripts; with a name, shows detail for that script.
|
|
21
|
+
|
|
22
|
+
______________________________________________________________________
|
|
23
|
+
|
|
24
|
+
## [2.16.14] - 2026-05-01
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- ELSEIF conditions now support ANDIF/ORIF modifiers. Previously, ANDIF/ORIF after an ELSEIF were silently attached to the parent IF condition instead of the ELSEIF clause, meaning the compound condition was never evaluated correctly. ELSEIF + ANDIF/ORIF now works the same way as IF + ANDIF/ORIF.
|
|
29
|
+
- Unknown AST node types now raise an error instead of being silently ignored during execution.
|
|
30
|
+
- Cursor leak in `select_rowsource()` and `select_rowdict()`: cursor is now closed on query execution failure. High-traffic callers (EXPORT, COPY) now explicitly close the row generator on error instead of relying on garbage collection.
|
|
31
|
+
|
|
32
|
+
______________________________________________________________________
|
|
33
|
+
|
|
16
34
|
## [2.16.13] - 2026-05-01
|
|
17
35
|
|
|
18
36
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: execsql2
|
|
3
|
-
Version: 2.16.
|
|
3
|
+
Version: 2.16.15
|
|
4
4
|
Summary: Runs a SQL script against a PostgreSQL, SQLite, MariaDB/MySQL, DuckDB, Firebird, MS-Access, MS-SQL-Server, or Oracle database, or an ODBC DSN. Provides metacommands to import and export data, copy data between databases, conditionally execute SQL and metacommands, and dynamically alter SQL and metacommands with substitution variables.
|
|
5
5
|
Project-URL: Homepage, https://execsql2.readthedocs.io
|
|
6
6
|
Project-URL: Repository, https://github.com/geocoug/execsql
|
|
@@ -44,23 +44,22 @@ ______________________________________________________________________
|
|
|
44
44
|
|
|
45
45
|
### Metacommands
|
|
46
46
|
|
|
47
|
-
| Metacommand
|
|
48
|
-
|
|
|
49
|
-
| `ASSERT`
|
|
50
|
-
| `BREAKPOINT`
|
|
51
|
-
| `CONFIG SHOW_PROGRESS`
|
|
52
|
-
| `CONFIG LOG_SQL`
|
|
53
|
-
| `PG_UPSERT`
|
|
54
|
-
| `IMPORT … FROM JSON`
|
|
55
|
-
| `SHOW SCRIPTS`
|
|
56
|
-
| `SHOW SCRIPT <name>` | Show detail for a single registered SCRIPT: name, parameters (with defaults), source file/line range, and docstring. |
|
|
47
|
+
| Metacommand | Description |
|
|
48
|
+
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
49
|
+
| `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. |
|
|
50
|
+
| `BREAKPOINT` | Pause script execution and drop into an interactive debug REPL. See [Debugging](#debugging) below for full details. |
|
|
51
|
+
| `CONFIG SHOW_PROGRESS` | Enable the Rich progress bar for IMPORT operations at runtime. |
|
|
52
|
+
| `CONFIG LOG_SQL` | Enable SQL query audit logging — writes executed SQL to the log file. |
|
|
53
|
+
| `PG_UPSERT` | QA-checked, FK-dependency-ordered upserts from staging to base schema on PostgreSQL. Integrates [pg-upsert](https://pg-upsert.readthedocs.io/) as an optional dependency. Three modes: full pipeline, QA-only, and schema check. Supports `EXPORT_FAILURES`, `EXPORT_FORMAT`, `EXPORT_MAX_ROWS`, and `STRICT_COLUMNS` keywords. `STRICT_COLUMNS` forces all missing columns to be errors (requires `pg-upsert>=1.22.0`). |
|
|
54
|
+
| `IMPORT … FROM JSON` | Import a JSON file (array of objects or NDJSON) into a database table. Nested objects are flattened with dot-separated column names; arrays are stored as JSON strings. |
|
|
55
|
+
| `SHOW SCRIPTS [<name>]` | Without a name, lists all registered SCRIPT definitions with parameter signatures and source locations. With a name, shows detail including parameters (with defaults), source file/line range, and docstring. |
|
|
57
56
|
|
|
58
57
|
### SCRIPT Enhancements
|
|
59
58
|
|
|
60
|
-
| Feature | Description
|
|
61
|
-
| ------------------ |
|
|
62
|
-
| Default parameters | `BEGIN SCRIPT load(schema, table, batch=1000)` — parameters with defaults can be omitted at call site. Required parameters must precede optional parameters.
|
|
63
|
-
| Docstrings | Comments (`--` or `/* */`) immediately following `BEGIN SCRIPT` are captured as documentation. A blank line terminates the docstring. Displayed by `SHOW
|
|
59
|
+
| Feature | Description |
|
|
60
|
+
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
61
|
+
| Default parameters | `BEGIN SCRIPT load(schema, table, batch=1000)` — parameters with defaults can be omitted at call site. Required parameters must precede optional parameters. |
|
|
62
|
+
| Docstrings | Comments (`--` or `/* */`) immediately following `BEGIN SCRIPT` are captured as documentation. A blank line terminates the docstring. Displayed by `SHOW SCRIPTS <name>` and `.scripts <name>` REPL command. |
|
|
64
63
|
|
|
65
64
|
### Bug Fixes
|
|
66
65
|
|
|
@@ -310,6 +309,8 @@ These are behavioral changes driven by security or correctness issues in the ups
|
|
|
310
309
|
| 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. |
|
|
311
310
|
| `PROMPT COMPARE` diff comparison | Diff engine uses native Python equality instead of string comparison — numeric types, Decimals, and booleans compare correctly. `None` is distinguished from empty string. Columns are matched by name (not position), key columns are excluded from comparison, and duplicate PKs keep the first row. |
|
|
312
311
|
| `win_config_file` broken | Checked `os.name == "windows"` which Python never returns (correct value is `"nt"`). Fixed to `os.name == "nt"`. Inherited from upstream. |
|
|
312
|
+
| ELSEIF + ANDIF/ORIF | ANDIF/ORIF after an ELSEIF were silently attached to the parent IF condition instead of the ELSEIF clause. The compound condition was never evaluated for the ELSEIF branch. Fixed in the AST parser and executor. |
|
|
313
|
+
| Cursor leak in `select_rowsource()` | Cursor was not closed on query execution failure. Row generators in EXPORT and COPY paths were not explicitly closed on error, relying on garbage collection for cleanup. |
|
|
313
314
|
| `NumericParser` right-associative | Arithmetic operators were parsed right-to-left. `10 - 3 - 2` evaluated as `9` instead of `5`. Fixed to left-associative parsing. Inherited from upstream. |
|
|
314
315
|
| Empty-column check precedence | `DataTable` and `Database.populate_table()` had an operator precedence bug in the extra-column emptiness check — a redundant `and conf.del_empty_cols` caused incorrect short-circuit evaluation. Inherited from upstream. |
|
|
315
316
|
| SQLite import string processing | `SQLiteDatabase.populate_table()` applied `trim_strings`, `replace_newlines`, and `empty_strings` after copying row data, so processing never reached the INSERT. Fixed to process before extraction. Inherited from upstream. |
|
|
@@ -1372,7 +1372,7 @@ ENDIF
|
|
|
1372
1372
|
|
|
1373
1373
|
Multiple ELSEIF clauses can be used within a single multi-line IF metacommand. An ELSE clause can be used in combination with ELSEIF clauses, but this is not recommended because the results are not likely to be what you expect---the ELSE keyword only inverts the current truth state, it does not provide an alternative to all preceding ELSEIF clauses. To achieve the effect of a case or switch statement, use only ELSEIF clauses without a final ELSE clause.
|
|
1374
1374
|
|
|
1375
|
-
The ANDIF metacommand allows you to test for the conjunction of two conditional expressions using two separate metacommands instead of one. This may be beneficial for clarity. The simplest form of usage of the ANDIF clause is:
|
|
1375
|
+
The ANDIF metacommand allows you to test for the conjunction of two conditional expressions using two separate metacommands instead of one. This may be beneficial for clarity. ANDIF and ORIF can follow either an IF or an ELSEIF metacommand. The simplest form of usage of the ANDIF clause is:
|
|
1376
1376
|
|
|
1377
1377
|
```
|
|
1378
1378
|
IF(<conditional expression>)
|
|
@@ -1381,7 +1381,7 @@ ANDIF(<conditional expression>)
|
|
|
1381
1381
|
ENDIF
|
|
1382
1382
|
```
|
|
1383
1383
|
|
|
1384
|
-
The ANDIF metacommand does not have to immediately follow the IF metacommand. It could instead follow an
|
|
1384
|
+
The ANDIF metacommand does not have to immediately follow the IF metacommand. It could instead follow an ELSEIF statement. Usage patterns other than those illustrated here may be difficult to interpret, however, and nested IF metacommands may be preferable to complex uses of the ANDIF clause.
|
|
1385
1385
|
|
|
1386
1386
|
The ORIF metacommand is similar to the ANDIF clause, but allows you to test for the disjunction of two conditional expressions using two different metacommands. The simplest form of usage of the ORIF clause is:
|
|
1387
1387
|
|
|
@@ -1392,6 +1392,17 @@ ORIF(<conditional expression>)
|
|
|
1392
1392
|
ENDIF
|
|
1393
1393
|
```
|
|
1394
1394
|
|
|
1395
|
+
ANDIF and ORIF can also compound an ELSEIF condition:
|
|
1396
|
+
|
|
1397
|
+
```
|
|
1398
|
+
IF(<conditional expression>)
|
|
1399
|
+
<SQL statements and metacommands>
|
|
1400
|
+
ELSEIF(<conditional expression>)
|
|
1401
|
+
ANDIF(<conditional expression>)
|
|
1402
|
+
<SQL statements and metacommands>
|
|
1403
|
+
ENDIF
|
|
1404
|
+
```
|
|
1405
|
+
|
|
1395
1406
|
The IF metacommands can be used not only to control a single stream of script commands, but also to loop over sets of SQL statements and metacommands, as shown in [Example 6](../guides/examples.md#example6).
|
|
1396
1407
|
|
|
1397
1408
|
The IF metacommands cannot be used within a SQL statement (nor can any other metacommands). This restriction prohibits constructions such as:
|
|
@@ -2756,12 +2767,18 @@ The numeric expression may consist of the simple algebraic operations of additio
|
|
|
2756
2767
|
## SHOW SCRIPTS { #show_scripts }
|
|
2757
2768
|
|
|
2758
2769
|
```
|
|
2759
|
-
SHOW SCRIPTS
|
|
2770
|
+
SHOW SCRIPTS [<name>]
|
|
2760
2771
|
```
|
|
2761
2772
|
|
|
2762
|
-
|
|
2773
|
+
Without a name, lists all registered SCRIPT definitions with their parameter signatures and source locations. With a name, shows detail for that script including parameters, source file/line range, and docstring.
|
|
2774
|
+
|
|
2775
|
+
This is useful for discovering what scripts are available at runtime, especially when scripts are loaded from INCLUDEEd files whose paths are determined dynamically.
|
|
2763
2776
|
|
|
2764
|
-
**
|
|
2777
|
+
**List all scripts:**
|
|
2778
|
+
|
|
2779
|
+
```sql
|
|
2780
|
+
-- !x! SHOW SCRIPTS
|
|
2781
|
+
```
|
|
2765
2782
|
|
|
2766
2783
|
```
|
|
2767
2784
|
Registered scripts (3):
|
|
@@ -2771,26 +2788,12 @@ Registered scripts (3):
|
|
|
2771
2788
|
validate(schema, table) pipeline.sql:62-80
|
|
2772
2789
|
```
|
|
2773
2790
|
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
If no scripts are registered, prints `No scripts registered.`
|
|
2777
|
-
|
|
2778
|
-
## SHOW SCRIPT { #show_script }
|
|
2779
|
-
|
|
2780
|
-
```
|
|
2781
|
-
SHOW SCRIPT <name>
|
|
2782
|
-
```
|
|
2783
|
-
|
|
2784
|
-
Shows detail for a single registered SCRIPT definition, including its parameter list (with required/optional status and defaults), source file/line range, and docstring.
|
|
2785
|
-
|
|
2786
|
-
**Example:**
|
|
2791
|
+
**Show detail for one script:**
|
|
2787
2792
|
|
|
2788
2793
|
```sql
|
|
2789
|
-
-- !x! SHOW
|
|
2794
|
+
-- !x! SHOW SCRIPTS load_data
|
|
2790
2795
|
```
|
|
2791
2796
|
|
|
2792
|
-
**Example output:**
|
|
2793
|
-
|
|
2794
2797
|
```
|
|
2795
2798
|
Script: load_data(schema, table, batch_size=1000)
|
|
2796
2799
|
Source: pipeline.sql:15-42
|
|
@@ -2802,7 +2805,7 @@ Parameters:
|
|
|
2802
2805
|
Load data from staging into the target table.
|
|
2803
2806
|
```
|
|
2804
2807
|
|
|
2805
|
-
If the script is not found, prints `No script named '<name>' is registered.`
|
|
2808
|
+
If no scripts are registered, prints `No scripts registered.` If the named script is not found, prints `No script named '<name>' is registered.`
|
|
2806
2809
|
|
|
2807
2810
|
!!! tip
|
|
2808
2811
|
In the debug REPL, use `.scripts` to list all scripts or `.scripts <name>` to show detail for one script.
|
{execsql2-2.16.13 → execsql2-2.16.15}/extras/vscode-execsql/syntaxes/execsql.tmLanguage.json
RENAMED
|
@@ -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|pg_upsert\\s+check|sub_querystring|execute\\s+script|append\\s+script|extend\\s+script|reset\\s+counter|export\\s+query|pg_upsert\\s+qa|show\\s+scripts|sub_tempfile|write\\s+script|import_file|set\\s+counter|
|
|
96
|
+
"match": "(?i)\\b(reset\\s+dialog_canceled|write\\s+create_table|export_metadata|pg_upsert\\s+check|sub_querystring|execute\\s+script|append\\s+script|extend\\s+script|reset\\s+counter|export\\s+query|pg_upsert\\s+qa|show\\s+scripts|sub_tempfile|write\\s+script|import_file|set\\s+counter|sub_decrypt|sub_encrypt|autocommit|breakpoint|copy\\s+query|disconnect|select_sub|sub_append|system_cmd|pg_upsert|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": {
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "execsql2"
|
|
7
|
-
version = "2.16.
|
|
7
|
+
version = "2.16.15"
|
|
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" }
|
|
@@ -165,7 +165,7 @@ skip-magic-trailing-comma = false
|
|
|
165
165
|
line-ending = "auto"
|
|
166
166
|
|
|
167
167
|
[tool.bumpversion]
|
|
168
|
-
current_version = "2.16.
|
|
168
|
+
current_version = "2.16.15"
|
|
169
169
|
commit = true
|
|
170
170
|
tag = true
|
|
171
171
|
tag_name = "v{new_version}"
|
|
@@ -226,6 +226,7 @@ class Database(ABC):
|
|
|
226
226
|
try:
|
|
227
227
|
curs.execute(sql)
|
|
228
228
|
except Exception:
|
|
229
|
+
curs.close()
|
|
229
230
|
self.rollback()
|
|
230
231
|
raise
|
|
231
232
|
try:
|
|
@@ -264,6 +265,7 @@ class Database(ABC):
|
|
|
264
265
|
try:
|
|
265
266
|
curs.execute(sql)
|
|
266
267
|
except Exception:
|
|
268
|
+
curs.close()
|
|
267
269
|
self.rollback()
|
|
268
270
|
raise
|
|
269
271
|
try:
|
|
@@ -7,8 +7,8 @@ Provides ``x_debug_write_metacommands``, which implements the
|
|
|
7
7
|
``WRITE METACOMMANDS`` debug metacommand that prints the full registered
|
|
8
8
|
metacommand list to the log/console for troubleshooting.
|
|
9
9
|
|
|
10
|
-
Also provides ``x_show_scripts``
|
|
11
|
-
|
|
10
|
+
Also provides ``x_show_scripts`` for runtime introspection of registered
|
|
11
|
+
SCRIPT blocks.
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
14
|
from pathlib import Path
|
|
@@ -214,50 +214,54 @@ def _format_script_source(span: Any) -> str:
|
|
|
214
214
|
|
|
215
215
|
|
|
216
216
|
# ---------------------------------------------------------------------------
|
|
217
|
-
# SHOW SCRIPTS
|
|
217
|
+
# SHOW SCRIPTS metacommand handler
|
|
218
218
|
# ---------------------------------------------------------------------------
|
|
219
219
|
|
|
220
220
|
|
|
221
221
|
def x_show_scripts(**kwargs: Any) -> None:
|
|
222
|
-
"""List all registered
|
|
223
|
-
scripts = _state.ast_scripts
|
|
224
|
-
if not scripts:
|
|
225
|
-
_state.output.write("No scripts registered.\n")
|
|
226
|
-
return
|
|
227
|
-
_state.output.write(f"Registered scripts ({len(scripts)}):\n\n")
|
|
228
|
-
# Compute column width for alignment
|
|
229
|
-
sigs = {name: _format_script_signature(name, block.param_defs) for name, block in scripts.items()}
|
|
230
|
-
max_sig = max(len(s) for s in sigs.values())
|
|
231
|
-
for name, block in scripts.items():
|
|
232
|
-
sig = sigs[name]
|
|
233
|
-
src = _format_script_source(block.span)
|
|
234
|
-
_state.output.write(f" {sig:<{max_sig}} {src}\n")
|
|
235
|
-
_state.output.write("\n")
|
|
222
|
+
"""List all registered scripts, or show detail for one script.
|
|
236
223
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
224
|
+
Without a name argument, lists all registered SCRIPT definitions with
|
|
225
|
+
their parameter signatures and source locations. With a name, shows
|
|
226
|
+
detail for that script including parameters, source, and docstring.
|
|
227
|
+
"""
|
|
228
|
+
script_name = (kwargs.get("script_id") or "").strip().lower()
|
|
241
229
|
scripts = _state.ast_scripts
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
_state.output.write("
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
230
|
+
|
|
231
|
+
if script_name:
|
|
232
|
+
# ---------- detail for one script ----------
|
|
233
|
+
if script_name not in scripts:
|
|
234
|
+
_state.output.write(f"No script named '{script_name}' is registered.\n")
|
|
235
|
+
return
|
|
236
|
+
block = scripts[script_name]
|
|
237
|
+
sig = _format_script_signature(block.name, block.param_defs)
|
|
238
|
+
src = _format_script_source(block.span)
|
|
239
|
+
_state.output.write(f"Script: {sig}\n")
|
|
240
|
+
_state.output.write(f"Source: {src}\n")
|
|
241
|
+
if block.param_defs:
|
|
242
|
+
_state.output.write("Parameters:\n")
|
|
243
|
+
max_name = max(len(p.name) for p in block.param_defs)
|
|
244
|
+
for p in block.param_defs:
|
|
245
|
+
if p.default is not None:
|
|
246
|
+
_state.output.write(f" {p.name:<{max_name}} (optional, default: {p.default})\n")
|
|
247
|
+
else:
|
|
248
|
+
_state.output.write(f" {p.name:<{max_name}} (required)\n")
|
|
249
|
+
else:
|
|
250
|
+
_state.output.write("Parameters: (none)\n")
|
|
251
|
+
if block.doc:
|
|
252
|
+
_state.output.write("\n")
|
|
253
|
+
for doc_line in block.doc.split("\n"):
|
|
254
|
+
_state.output.write(f" {doc_line}\n")
|
|
258
255
|
else:
|
|
259
|
-
|
|
260
|
-
|
|
256
|
+
# ---------- list all scripts ----------
|
|
257
|
+
if not scripts:
|
|
258
|
+
_state.output.write("No scripts registered.\n")
|
|
259
|
+
return
|
|
260
|
+
_state.output.write(f"Registered scripts ({len(scripts)}):\n\n")
|
|
261
|
+
sigs = {name: _format_script_signature(name, block.param_defs) for name, block in scripts.items()}
|
|
262
|
+
max_sig = max(len(s) for s in sigs.values())
|
|
263
|
+
for name, block in scripts.items():
|
|
264
|
+
sig = sigs[name]
|
|
265
|
+
src = _format_script_source(block.span)
|
|
266
|
+
_state.output.write(f" {sig:<{max_sig}} {src}\n")
|
|
261
267
|
_state.output.write("\n")
|
|
262
|
-
for doc_line in block.doc.split("\n"):
|
|
263
|
-
_state.output.write(f" {doc_line}\n")
|
|
@@ -99,7 +99,6 @@ from execsql.metacommands.debug import (
|
|
|
99
99
|
x_debug_write_metacommands,
|
|
100
100
|
x_debug_write_odbc_drivers,
|
|
101
101
|
x_debug_write_subvars,
|
|
102
|
-
x_show_script,
|
|
103
102
|
x_show_scripts,
|
|
104
103
|
)
|
|
105
104
|
from execsql.debug.repl import x_breakpoint
|
|
@@ -1750,20 +1749,14 @@ def build_dispatch_table() -> MetaCommandList:
|
|
|
1750
1749
|
)
|
|
1751
1750
|
|
|
1752
1751
|
# ------------------------------------------------------------------
|
|
1753
|
-
# SHOW SCRIPTS
|
|
1752
|
+
# SHOW SCRIPTS [<name>]
|
|
1754
1753
|
# ------------------------------------------------------------------
|
|
1755
1754
|
mcl.add(
|
|
1756
|
-
r"^\s*SHOW\s+SCRIPTS\s*$",
|
|
1755
|
+
r"^\s*SHOW\s+SCRIPTS\s*(?P<script_id>\w+)?\s*$",
|
|
1757
1756
|
x_show_scripts,
|
|
1758
1757
|
description="SHOW SCRIPTS",
|
|
1759
1758
|
category="action",
|
|
1760
1759
|
)
|
|
1761
|
-
mcl.add(
|
|
1762
|
-
r"^\s*SHOW\s+SCRIPT\s+(?P<script_id>\w+)\s*$",
|
|
1763
|
-
x_show_script,
|
|
1764
|
-
description="SHOW SCRIPT",
|
|
1765
|
-
category="action",
|
|
1766
|
-
)
|
|
1767
1760
|
|
|
1768
1761
|
# ------------------------------------------------------------------
|
|
1769
1762
|
# IF / ORIF / ANDIF / ELSEIF / ELSE / ENDIF
|
|
@@ -136,16 +136,20 @@ def _dispatch_format(
|
|
|
136
136
|
raise
|
|
137
137
|
except Exception as e:
|
|
138
138
|
raise ErrInfo("db", select_stmt, exception_msg=exception_desc()) from e
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
139
|
+
try:
|
|
140
|
+
if filefmt == "raw":
|
|
141
|
+
write_query_raw(outfile, rows, db.encoding, append, zipfile=zipfilename)
|
|
142
|
+
elif filefmt == "b64":
|
|
143
|
+
write_query_b64(outfile, rows, append, zipfile=zipfilename)
|
|
144
|
+
elif filefmt == "feather":
|
|
145
|
+
write_query_to_feather(outfile, hdrs, rows)
|
|
146
|
+
elif filefmt == "parquet":
|
|
147
|
+
write_query_to_parquet(outfile, hdrs, rows)
|
|
148
|
+
else:
|
|
149
|
+
write_delimited_file(outfile, filefmt, hdrs, rows, _state.conf.output_encoding, append, zipfilename)
|
|
150
|
+
except BaseException:
|
|
151
|
+
rows.close()
|
|
152
|
+
raise
|
|
149
153
|
|
|
150
154
|
|
|
151
155
|
# ---------------------------------------------------------------------------
|
|
@@ -106,10 +106,9 @@ def x_copy(**kwargs: Any) -> None:
|
|
|
106
106
|
try:
|
|
107
107
|
db2.populate_table(schema2, table2, rows, hdrs, get_ts)
|
|
108
108
|
db2.commit()
|
|
109
|
-
except
|
|
109
|
+
except BaseException:
|
|
110
|
+
rows.close()
|
|
110
111
|
raise
|
|
111
|
-
except Exception as e:
|
|
112
|
-
raise ErrInfo("db", select_stmt, exception_msg=exception_desc()) from e
|
|
113
112
|
|
|
114
113
|
|
|
115
114
|
def x_copy_query(**kwargs: Any) -> None:
|
|
@@ -181,10 +180,9 @@ def x_copy_query(**kwargs: Any) -> None:
|
|
|
181
180
|
try:
|
|
182
181
|
db2.populate_table(schema2, table2, rows, hdrs, get_ts)
|
|
183
182
|
db2.commit()
|
|
184
|
-
except
|
|
183
|
+
except BaseException:
|
|
184
|
+
rows.close()
|
|
185
185
|
raise
|
|
186
|
-
except Exception as e:
|
|
187
|
-
raise ErrInfo("db", select_stmt, exception_msg=exception_desc()) from e
|
|
188
186
|
|
|
189
187
|
|
|
190
188
|
def x_zip(**kwargs: Any) -> None:
|
|
@@ -209,11 +209,14 @@ class ElseIfClause:
|
|
|
209
209
|
Attributes:
|
|
210
210
|
condition: The condition expression text (e.g. ``"HAS_ROWS"``).
|
|
211
211
|
span: Source location of the ELSEIF line itself.
|
|
212
|
+
condition_modifiers: ANDIF/ORIF modifiers that compound the ELSEIF
|
|
213
|
+
condition, evaluated left-to-right at runtime.
|
|
212
214
|
body: Nodes executed when this condition is true.
|
|
213
215
|
"""
|
|
214
216
|
|
|
215
217
|
condition: str
|
|
216
218
|
span: SourceSpan
|
|
219
|
+
condition_modifiers: list[ConditionModifier] = field(default_factory=list)
|
|
217
220
|
body: list[Node] = field(default_factory=list)
|
|
218
221
|
|
|
219
222
|
|
|
@@ -384,6 +384,12 @@ def _execute_node(
|
|
|
384
384
|
ctx.last_command = _FakeScriptCmd(node)
|
|
385
385
|
_execute_include(ctx, node, localvars)
|
|
386
386
|
|
|
387
|
+
else:
|
|
388
|
+
raise ErrInfo(
|
|
389
|
+
type="error",
|
|
390
|
+
other_msg=f"Unhandled AST node type: {type(node).__name__} at {node.span}",
|
|
391
|
+
)
|
|
392
|
+
|
|
387
393
|
|
|
388
394
|
# ---------------------------------------------------------------------------
|
|
389
395
|
# Block executors
|
|
@@ -404,9 +410,7 @@ def _execute_if(
|
|
|
404
410
|
|
|
405
411
|
# Try ELSEIF clauses
|
|
406
412
|
for clause in node.elseif_clauses:
|
|
407
|
-
|
|
408
|
-
expanded = substitute_vars(clause.condition, effective_locals, ctx=ctx)
|
|
409
|
-
if xcmd_test(expanded):
|
|
413
|
+
if _eval_condition(ctx, clause.condition, clause.condition_modifiers):
|
|
410
414
|
_execute_nodes(ctx, clause.body, node.span.file, localvars, in_loop=in_loop)
|
|
411
415
|
return
|
|
412
416
|
|
|
@@ -580,14 +580,17 @@ def _parse_lines(lines: Iterable[str], source_name: str) -> Script:
|
|
|
580
580
|
command_text=line,
|
|
581
581
|
other_msg=f"ANDIF without matching IF on line {file_lineno} of {source_name}.",
|
|
582
582
|
)
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
condition=m.group("cond").strip(),
|
|
588
|
-
span=SourceSpan(source_name, file_lineno),
|
|
589
|
-
),
|
|
583
|
+
modifier = ConditionModifier(
|
|
584
|
+
kind="AND",
|
|
585
|
+
condition=m.group("cond").strip(),
|
|
586
|
+
span=SourceSpan(source_name, file_lineno),
|
|
590
587
|
)
|
|
588
|
+
frame = block_stack[-1]
|
|
589
|
+
if_node = frame.node
|
|
590
|
+
if frame._in_elseif and if_node.elseif_clauses: # type: ignore[union-attr]
|
|
591
|
+
if_node.elseif_clauses[-1].condition_modifiers.append(modifier) # type: ignore[union-attr]
|
|
592
|
+
else:
|
|
593
|
+
if_node.condition_modifiers.append(modifier) # type: ignore[union-attr]
|
|
591
594
|
continue
|
|
592
595
|
|
|
593
596
|
# -- ORIF --
|
|
@@ -599,14 +602,17 @@ def _parse_lines(lines: Iterable[str], source_name: str) -> Script:
|
|
|
599
602
|
command_text=line,
|
|
600
603
|
other_msg=f"ORIF without matching IF on line {file_lineno} of {source_name}.",
|
|
601
604
|
)
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
condition=m.group("cond").strip(),
|
|
607
|
-
span=SourceSpan(source_name, file_lineno),
|
|
608
|
-
),
|
|
605
|
+
modifier = ConditionModifier(
|
|
606
|
+
kind="OR",
|
|
607
|
+
condition=m.group("cond").strip(),
|
|
608
|
+
span=SourceSpan(source_name, file_lineno),
|
|
609
609
|
)
|
|
610
|
+
frame = block_stack[-1]
|
|
611
|
+
if_node = frame.node
|
|
612
|
+
if frame._in_elseif and if_node.elseif_clauses: # type: ignore[union-attr]
|
|
613
|
+
if_node.elseif_clauses[-1].condition_modifiers.append(modifier) # type: ignore[union-attr]
|
|
614
|
+
else:
|
|
615
|
+
if_node.condition_modifiers.append(modifier) # type: ignore[union-attr]
|
|
610
616
|
continue
|
|
611
617
|
|
|
612
618
|
# -- ELSE --
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Unit tests for SHOW SCRIPTS
|
|
1
|
+
"""Unit tests for SHOW SCRIPTS metacommand handler and helpers."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
@@ -10,7 +10,6 @@ import pytest
|
|
|
10
10
|
from execsql.metacommands.debug import (
|
|
11
11
|
_format_script_signature,
|
|
12
12
|
_format_script_source,
|
|
13
|
-
x_show_script,
|
|
14
13
|
x_show_scripts,
|
|
15
14
|
)
|
|
16
15
|
from execsql.script.ast import ParamDef, ScriptBlock, SourceSpan
|
|
@@ -93,7 +92,9 @@ def mock_state(monkeypatch):
|
|
|
93
92
|
return state
|
|
94
93
|
|
|
95
94
|
|
|
96
|
-
class
|
|
95
|
+
class TestShowScriptsListAll:
|
|
96
|
+
"""SHOW SCRIPTS with no name argument lists all scripts."""
|
|
97
|
+
|
|
97
98
|
def test_empty(self, mock_state):
|
|
98
99
|
x_show_scripts(metacommandline="SHOW SCRIPTS")
|
|
99
100
|
assert "No scripts registered" in mock_state.output.getvalue()
|
|
@@ -117,14 +118,16 @@ class TestShowScriptsHandler:
|
|
|
117
118
|
assert "load(schema, batch=1000)" in mock_state.output.getvalue()
|
|
118
119
|
|
|
119
120
|
|
|
120
|
-
class
|
|
121
|
+
class TestShowScriptsDetail:
|
|
122
|
+
"""SHOW SCRIPTS <name> shows detail for one script."""
|
|
123
|
+
|
|
121
124
|
def test_not_found(self, mock_state):
|
|
122
|
-
|
|
125
|
+
x_show_scripts(script_id="nonexistent", metacommandline="SHOW SCRIPTS nonexistent")
|
|
123
126
|
assert "No script named" in mock_state.output.getvalue()
|
|
124
127
|
|
|
125
128
|
def test_detail_no_params(self, mock_state):
|
|
126
129
|
mock_state.ast_scripts = {"proc": _make_script("proc")}
|
|
127
|
-
|
|
130
|
+
x_show_scripts(script_id="proc", metacommandline="SHOW SCRIPTS proc")
|
|
128
131
|
output = mock_state.output.getvalue()
|
|
129
132
|
assert "proc()" in output
|
|
130
133
|
assert "Parameters: (none)" in output
|
|
@@ -133,7 +136,7 @@ class TestShowScriptHandler:
|
|
|
133
136
|
mock_state.ast_scripts = {
|
|
134
137
|
"load": _make_script("load", [ParamDef("schema"), ParamDef("batch", "1000")]),
|
|
135
138
|
}
|
|
136
|
-
|
|
139
|
+
x_show_scripts(script_id="load", metacommandline="SHOW SCRIPTS load")
|
|
137
140
|
output = mock_state.output.getvalue()
|
|
138
141
|
assert "load(schema, batch=1000)" in output
|
|
139
142
|
assert "(required)" in output
|
|
@@ -143,12 +146,19 @@ class TestShowScriptHandler:
|
|
|
143
146
|
mock_state.ast_scripts = {
|
|
144
147
|
"proc": _make_script("proc", doc="This is the docstring.\nSecond line."),
|
|
145
148
|
}
|
|
146
|
-
|
|
149
|
+
x_show_scripts(script_id="proc", metacommandline="SHOW SCRIPTS proc")
|
|
147
150
|
output = mock_state.output.getvalue()
|
|
148
151
|
assert "This is the docstring." in output
|
|
149
152
|
assert "Second line." in output
|
|
150
153
|
|
|
151
154
|
def test_case_insensitive_lookup(self, mock_state):
|
|
152
155
|
mock_state.ast_scripts = {"myproc": _make_script("myproc")}
|
|
153
|
-
|
|
156
|
+
x_show_scripts(script_id="MYPROC", metacommandline="SHOW SCRIPTS MYPROC")
|
|
154
157
|
assert "myproc()" in mock_state.output.getvalue()
|
|
158
|
+
|
|
159
|
+
def test_no_script_id_kwarg_lists_all(self, mock_state):
|
|
160
|
+
"""When script_id is not provided at all, list all scripts."""
|
|
161
|
+
mock_state.ast_scripts = {"proc": _make_script("proc")}
|
|
162
|
+
x_show_scripts(metacommandline="SHOW SCRIPTS")
|
|
163
|
+
output = mock_state.output.getvalue()
|
|
164
|
+
assert "Registered scripts (1)" in output
|