execsql2 2.16.0__tar.gz → 2.16.1__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.0 → execsql2-2.16.1}/.gitignore +3 -2
- {execsql2-2.16.0 → execsql2-2.16.1}/CHANGELOG.md +28 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/PKG-INFO +38 -29
- {execsql2-2.16.0 → execsql2-2.16.1}/README.md +37 -28
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/about/contributors.md +3 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/about/divergence.md +38 -33
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/api/exporters.md +6 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/api/importers.md +2 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/api/index.md +24 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/dev/adding_metacommands.md +14 -10
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/dev/architecture.md +21 -9
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/getting-started/installation.md +2 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/getting-started/syntax.md +2 -1
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/guides/examples.md +303 -76
- execsql2-2.16.1/docs/guides/logging.md +106 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/reference/configuration.md +1 -1
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/reference/substitution_vars.md +12 -8
- {execsql2-2.16.0 → execsql2-2.16.1}/pyproject.toml +2 -2
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/cli/run.py +333 -173
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/config.py +1 -1
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/script/executor.py +140 -47
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/state.py +72 -51
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/test_config_data.py +2 -2
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/test_config_extended.py +1 -1
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/test_executor.py +189 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/test_state.py +108 -1
- {execsql2-2.16.0 → execsql2-2.16.1}/uv.lock +1 -1
- {execsql2-2.16.0 → execsql2-2.16.1}/zensical.toml +40 -2
- execsql2-2.16.0/AUDIT.md +0 -324
- execsql2-2.16.0/docs/guides/logging.md +0 -103
- {execsql2-2.16.0 → execsql2-2.16.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/.github/workflows/ci-cd.yml +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/.pre-commit-config.yaml +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/.pre-commit-hooks.yaml +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/.python-version +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/.readthedocs.yaml +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/CONTRIBUTING.md +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/LICENSE.txt +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/NOTICE +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/SECURITY.md +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/about/copyright.md +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/api/cli.md +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/api/db.md +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/api/metacommands.md +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/dev/adding_db_adapters.md +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/dev/adding_exporters.md +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/dev/adding_importers.md +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/getting-started/requirements.md +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/guides/debugging.md +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/guides/documentation.md +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/guides/encoding.md +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/guides/formatter.md +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/guides/sql_syntax.md +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/guides/usage.md +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/guides/using_scripts.md +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/images/Compare_planets.png +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/images/actions.png +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/images/actions2.png +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/images/checkboxes.png +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/images/connect.b64 +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/images/connect.png +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/images/create_conf.png +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/images/data_error1_screenshot.jpg +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/images/entry_form.png +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/images/execsql_console.png +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/images/execsql_logo_01.png +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/images/fatals.png +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/images/logo_small.png +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/images/pause_terminal.png +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/images/pause_terminal_sm.b64 +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/images/pause_terminal_sm.png +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/images/prompt_compare.png +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/images/set_build_commands.jpg +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/images/unit_conversions.b64 +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/images/unit_conversions_029.png +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/images/unmatched.png +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/images/vim_execsql_highlight.png +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/index.md +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/reference/metacommands.md +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/docs/reference/security.md +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/extras/plugin-template/README.md +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/extras/plugin-template/pyproject.toml +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/extras/plugin-template/src/execsql_plugin_YOURNAME/__init__.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/extras/plugin-template/tests/test_plugin.py.example +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/extras/vscode-execsql/README.md +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/extras/vscode-execsql/package.json +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/extras/vscode-execsql/syntaxes/execsql.tmLanguage.json +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/justfile +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/scripts/generate_vscode_grammar.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/__init__.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/__main__.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/api.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/cli/__init__.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/cli/dsn.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/cli/help.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/cli/lint.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/cli/lint_ast.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/db/__init__.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/db/access.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/db/base.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/db/dsn.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/db/duckdb.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/db/factory.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/db/firebird.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/db/mysql.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/db/oracle.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/db/postgres.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/db/sqlite.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/db/sqlserver.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/debug/__init__.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/debug/repl.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/exceptions.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/exporters/__init__.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/exporters/base.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/exporters/delimited.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/exporters/duckdb.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/exporters/feather.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/exporters/html.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/exporters/json.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/exporters/latex.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/exporters/markdown.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/exporters/ods.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/exporters/parquet.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/exporters/pretty.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/exporters/protocol.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/exporters/raw.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/exporters/sqlite.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/exporters/templates.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/exporters/values.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/exporters/xls.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/exporters/xlsx.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/exporters/xml.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/exporters/yaml.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/exporters/zip.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/format.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/gui/__init__.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/gui/base.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/gui/console.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/gui/desktop.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/gui/tui.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/importers/__init__.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/importers/base.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/importers/csv.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/importers/feather.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/importers/json.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/importers/ods.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/importers/xls.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/metacommands/__init__.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/metacommands/conditions.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/metacommands/connect.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/metacommands/control.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/metacommands/data.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/metacommands/debug.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/metacommands/dispatch.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/metacommands/io.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/metacommands/io_export.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/metacommands/io_fileops.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/metacommands/io_import.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/metacommands/io_write.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/metacommands/prompt.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/metacommands/script_ext.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/metacommands/system.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/metacommands/upsert.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/models.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/parser.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/plugins.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/py.typed +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/script/__init__.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/script/ast.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/script/control.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/script/engine.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/script/parser.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/script/variables.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/types.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/utils/__init__.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/utils/auth.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/utils/crypto.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/utils/datetime.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/utils/errors.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/utils/fileio.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/utils/gui.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/utils/mail.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/utils/numeric.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/utils/regex.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/utils/strings.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/src/execsql/utils/timer.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/templates/README.md +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/templates/config_settings.sqlite +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/templates/example_config_prompt.sql +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/templates/execsql.conf +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/templates/make_config_db.sql +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/templates/md_compare.sql +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/templates/md_glossary.sql +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/templates/md_upsert.sql +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/templates/pg_compare.sql +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/templates/pg_glossary.sql +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/templates/pg_upsert.sql +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/templates/script_template.sql +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/templates/ss_compare.sql +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/templates/ss_glossary.sql +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/templates/ss_upsert.sql +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/__init__.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/cli/__init__.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/cli/test_cli.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/cli/test_cli_e2e.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/cli/test_cli_run.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/cli/test_lint.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/cli/test_ping.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/cli/test_profile.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/conftest.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/db/__init__.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/db/test_base.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/db/test_db_adapters_mocked.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/db/test_dsn.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/db/test_duckdb.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/db/test_factory.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/db/test_postgres.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/db/test_sqlite.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/db/test_sqlite_extra.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/exporters/__init__.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/exporters/test_base.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/exporters/test_db.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/exporters/test_delimited.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/exporters/test_duckdb_exporter.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/exporters/test_exporters.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/exporters/test_feather.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/exporters/test_html_extended.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/exporters/test_html_latex.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/exporters/test_json.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/exporters/test_json_extended.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/exporters/test_latex_extended.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/exporters/test_markdown.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/exporters/test_ods.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/exporters/test_parquet.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/exporters/test_pretty_extended.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/exporters/test_raw_extended.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/exporters/test_sqlite_exporter.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/exporters/test_templates.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/exporters/test_templates_extended.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/exporters/test_values_extended.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/exporters/test_xls_xlsx.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/exporters/test_xlsx.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/exporters/test_xml.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/exporters/test_yaml.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/exporters/test_zip.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/gui/__init__.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/gui/test_backends.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/gui/test_compare_stats.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/gui/test_compute_row_diffs.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/importers/__init__.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/importers/test_base_extended.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/importers/test_csv_edge_cases.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/importers/test_csv_importer.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/importers/test_feather_importer.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/importers/test_json_importer.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/importers/test_ods_importer.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/importers/test_xls_importer.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/integration/__init__.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/integration/conftest.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/integration/test_dsn.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/integration/test_duckdb.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/integration/test_mysql.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/integration/test_postgres.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/integration/test_sqlite.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/metacommands/__init__.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/metacommands/test_assert.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/metacommands/test_breakpoint.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/metacommands/test_connect.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/metacommands/test_io_export.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/metacommands/test_io_import.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/metacommands/test_metacommands.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/metacommands/test_metacommands_connect.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/metacommands/test_metacommands_data.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/metacommands/test_metacommands_extended.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/metacommands/test_metacommands_fileops_extra.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/metacommands/test_metacommands_io.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/metacommands/test_metacommands_io_write_extra.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/metacommands/test_metacommands_script_ext.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/metacommands/test_metacommands_system.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/metacommands/test_metacommands_system_extra.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/metacommands/test_pg_upsert.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/metacommands/test_row_count.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/scripts/__init__.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/scripts/fixtures/control_flow.sql +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/scripts/fixtures/io_roundtrip.sql +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/scripts/fixtures/parse_only/parse_tree.sql +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/scripts/fixtures/smoke.sql +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/scripts/test_sql_scripts.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/test_api.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/test_ast.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/test_ast_parser.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/test_config.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/test_debug_repl.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/test_engine.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/test_error_messages.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/test_exceptions.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/test_format.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/test_mail.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/test_models.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/test_package.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/test_parser.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/test_plugins.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/test_registry.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/test_script.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/test_types.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/utils/__init__.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/utils/test_auth.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/utils/test_auth_extra.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/utils/test_crypto.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/utils/test_datetime.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/utils/test_errors.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/utils/test_errors_extra.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/utils/test_fileio.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/utils/test_fileio_extra.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/utils/test_numeric.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/utils/test_regex.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/utils/test_strings.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/utils/test_timer.py +0 -0
- {execsql2-2.16.0 → execsql2-2.16.1}/tests/utils/test_timer_extra.py +0 -0
|
@@ -13,6 +13,20 @@ ______________________________________________________________________
|
|
|
13
13
|
|
|
14
14
|
______________________________________________________________________
|
|
15
15
|
|
|
16
|
+
## [2.16.1] - 2026-04-30
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- AST executor now correctly handles `~` (local) and `+` (outer-scope) substitution variables inside SCRIPT blocks. Previously, `-- !x! SUB ~var value` inside a SCRIPT body would write to a disconnected scope, causing the variable to be invisible to subsequent SQL statements and producing spurious "potential un-substituted variable" warnings. The fix pushes proper `CommandList` frames onto `commandliststack` at script and top-level boundaries, bridging the AST executor with the legacy metacommand handlers (`x_sub`, `x_rm_sub`, `xf_sub_defined`, `SUB_LOCAL`, prompt handlers, REPL `.vars`/`.stack`, etc.).
|
|
21
|
+
- EXECUTE SCRIPT argument expressions (e.g., `val=!!#parent_param!!`) are now expanded in the caller's scope before the child script frame is created, fixing nested script calls that pass `~` or `#` variables as arguments.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- `RuntimeContext` is now stored in `threading.local()` instead of a module-level global, making `_state.foo` access thread-safe. Each thread gets its own isolated context via lazy initialization. The `active_context` context manager is now safe for concurrent use across threads. Enables future PARALLEL blocks and concurrent `from execsql import run` calls.
|
|
26
|
+
- `_run()` in `cli/run.py` decomposed into 8 standalone functions: `_seed_early_subvars()`, `_load_config()`, `_seed_script_subvars()`, `_load_script()`, `_apply_dsn()`, `_apply_cli_options()`, `_route_positionals()`, and `_setup_logging()`. Reduces `_run()` from ~380 lines to ~150 lines of orchestration with zero behavioral change.
|
|
27
|
+
|
|
28
|
+
______________________________________________________________________
|
|
29
|
+
|
|
16
30
|
## [2.16.0] - 2026-04-29
|
|
17
31
|
|
|
18
32
|
### Added
|
|
@@ -37,6 +51,7 @@ ______________________________________________________________________
|
|
|
37
51
|
- `--lint` now uses the AST parser for structural validation. Unmatched IF/LOOP/BATCH/SCRIPT blocks are caught at parse time with precise source line ranges. No database connection or runtime state initialization is required. All prior lint checks (variable analysis, INCLUDE file existence, EXECUTE SCRIPT resolution, SUB_INI reading) are preserved.
|
|
38
52
|
- Export format dispatch logic (`EXPORT` and `EXPORT QUERY` metacommands) refactored from duplicated ~180-line if/elif chains into shared `_dispatch_format()` function, eliminating code duplication and fixing missing zip-compatibility checks for `EXPORT QUERY`.
|
|
39
53
|
- `MailSpec.send()` refactored: extracted `_expand()` helper to replace 12 repetitive substitution lines.
|
|
54
|
+
- Default database type changed from Access (`-t a`) to SQLite (`-t l`). Upstream defaulted to Access, which requires Windows and pyodbc. Users targeting Access databases should pass `-t a` explicitly.
|
|
40
55
|
|
|
41
56
|
### Fixed
|
|
42
57
|
|
|
@@ -55,6 +70,19 @@ ______________________________________________________________________
|
|
|
55
70
|
- `JsonDatatype` attributes are now declared as class variables in the class body instead of assigned externally after class definition.
|
|
56
71
|
- `minimal_conf` test fixture expanded with commonly needed attributes (`import_encoding`, `script_encoding`, `export_output_dir`, `write_prefix`, `write_suffix`, `fold_col_hdrs`, `trim_col_hdrs`, etc.) to reduce ad-hoc attribute additions in individual tests.
|
|
57
72
|
|
|
73
|
+
### Documentation
|
|
74
|
+
|
|
75
|
+
- Fixed false `$ENV:` prefix claim in substitution variables reference — feature does not exist.
|
|
76
|
+
- Documented environment variable filtering (SECRET, TOKEN, PASSWORD, etc.) in substitution variables reference.
|
|
77
|
+
- Added missing exporter API docs (markdown, yaml, xlsx) and importer API docs (json).
|
|
78
|
+
- Added 8 missing CLI flags to README Options table (-b, -e, -g, -i, -o, -s, -y, -z).
|
|
79
|
+
- Added missing installation extras ([upsert], [firebird], [oracle]) to README and installation guide.
|
|
80
|
+
- Fixed broken `PROMPT.md` link in logging guide.
|
|
81
|
+
- Added explicit `{ #exampleN }` anchors to all 34 examples for reliable cross-referencing.
|
|
82
|
+
- Updated architecture doc: corrected metacommand count (~225), export format count (20+), added debug/notebook/server/lsp packages to module map.
|
|
83
|
+
- Updated metacommand developer guide to reflect io.py split into io_export.py, io_import.py, io_write.py, io_fileops.py.
|
|
84
|
+
- Noted SQLite as the default database type in syntax reference.
|
|
85
|
+
|
|
58
86
|
### Removed
|
|
59
87
|
|
|
60
88
|
- `--ast` / `--no-ast` CLI flag — the AST executor is now the only execution engine; no opt-out.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: execsql2
|
|
3
|
-
Version: 2.16.
|
|
3
|
+
Version: 2.16.1
|
|
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
|
|
@@ -173,7 +173,8 @@ pip install execsql2[oracle] # Oracle (oracledb)
|
|
|
173
173
|
pip install execsql2[odbc] # ODBC DSN (pyodbc)
|
|
174
174
|
|
|
175
175
|
# Feature bundles
|
|
176
|
-
pip install execsql2[formats]
|
|
176
|
+
pip install execsql2[formats] # ODS, Excel, Jinja2, Feather, Parquet, HDF5
|
|
177
|
+
pip install execsql2[upsert] # pg-upsert for PostgreSQL upsert operations
|
|
177
178
|
pip install execsql2[auth] # OS keyring integration
|
|
178
179
|
pip install execsql2[auth-plaintext] # Keyring + plaintext file backend (headless Linux)
|
|
179
180
|
pip install execsql2[auth-encrypted] # Keyring + encrypted file backend (headless Linux)
|
|
@@ -219,33 +220,41 @@ execsql script.sql # read connection from config file
|
|
|
219
220
|
|
|
220
221
|
## Options
|
|
221
222
|
|
|
222
|
-
| Flag
|
|
223
|
-
|
|
|
224
|
-
| `-t {p,m,s,l,k,a,f,o,d}`
|
|
225
|
-
| `-u USER`
|
|
226
|
-
| `-p PORT`
|
|
227
|
-
| `-a VALUE`
|
|
228
|
-
| `-
|
|
229
|
-
| `-
|
|
230
|
-
| `-
|
|
231
|
-
| `-
|
|
232
|
-
| `-
|
|
233
|
-
| `-
|
|
234
|
-
| `-
|
|
235
|
-
| `-
|
|
236
|
-
|
|
|
237
|
-
|
|
|
238
|
-
| `--
|
|
239
|
-
| `--
|
|
240
|
-
|
|
|
241
|
-
|
|
|
242
|
-
| `--
|
|
243
|
-
| `--
|
|
244
|
-
| `--
|
|
245
|
-
| `--
|
|
246
|
-
| `--
|
|
247
|
-
| `--
|
|
248
|
-
| `--
|
|
223
|
+
| Flag | Description |
|
|
224
|
+
| ------------------------------------- | ---------------------------------------------------------------- |
|
|
225
|
+
| `-t {p,m,s,l,k,a,f,o,d}` | Database type |
|
|
226
|
+
| `-u USER` | Database username |
|
|
227
|
+
| `-p PORT` | Server port |
|
|
228
|
+
| `-a VALUE` | Set substitution variable `$ARG_x` |
|
|
229
|
+
| `-b` / `--boolean-int` | Treat integers 0 and 1 as boolean values |
|
|
230
|
+
| `-c SCRIPT` | Execute inline SQL or metacommand string |
|
|
231
|
+
| `-d` | Auto-create export directories |
|
|
232
|
+
| `-e ENCODING` / `--database-encoding` | Character encoding used in the database |
|
|
233
|
+
| `-f ENCODING` | Script file encoding (default: UTF-8) |
|
|
234
|
+
| `-g ENCODING` / `--output-encoding` | Encoding for WRITE and EXPORT output |
|
|
235
|
+
| `-i ENCODING` / `--import-encoding` | Encoding for data files used with IMPORT |
|
|
236
|
+
| `-l` | Write run log to `~/execsql.log` |
|
|
237
|
+
| `-m` | List metacommands and exit |
|
|
238
|
+
| `-n` | Create a new SQLite or PostgreSQL database if it does not exist |
|
|
239
|
+
| `-o` / `--online-help` | Open the online documentation in the default browser |
|
|
240
|
+
| `-s N` / `--scan-lines` | Lines to scan for IMPORT format detection (0 = scan entire file) |
|
|
241
|
+
| `-v {0,1,2,3}` | GUI level (0=none, 1=password, 2=selection, 3=full) |
|
|
242
|
+
| `-w` | Skip password prompt when a username is supplied |
|
|
243
|
+
| `-y` / `--encodings` | List available encoding names and exit |
|
|
244
|
+
| `-z KB` / `--import-buffer` | Import buffer size in KB (default: 32) |
|
|
245
|
+
| `--dsn URL` | Connection string (e.g. `postgresql://user:pass@host/db`) |
|
|
246
|
+
| `--output-dir DIR` | Default base directory for EXPORT output files |
|
|
247
|
+
| `--dry-run` | Parse the script and report commands without executing |
|
|
248
|
+
| `--lint` | Static analysis: check structure and warn on issues (no DB) |
|
|
249
|
+
| `--parse-tree` | Print the script's AST structure and exit (no DB) |
|
|
250
|
+
| `--list-plugins` | List discovered plugins and exit |
|
|
251
|
+
| `--ping` | Test database connectivity and exit |
|
|
252
|
+
| `--profile` | Show per-statement timing summary after execution |
|
|
253
|
+
| `--progress` | Show a progress bar for long-running IMPORT operations |
|
|
254
|
+
| `--config FILE` | Load an explicit config file (highest priority after CLI args) |
|
|
255
|
+
| `--debug` | Start in step-through debug mode (REPL pauses before each stmt) |
|
|
256
|
+
| `--dump-keywords` | Print metacommand keywords as JSON and exit |
|
|
257
|
+
| `--gui-framework {tkinter,textual}` | GUI framework for interactive prompts |
|
|
249
258
|
|
|
250
259
|
Run `execsql --help` for the full option list, or `execsql -m` to list all metacommands.
|
|
251
260
|
|
|
@@ -51,7 +51,8 @@ pip install execsql2[oracle] # Oracle (oracledb)
|
|
|
51
51
|
pip install execsql2[odbc] # ODBC DSN (pyodbc)
|
|
52
52
|
|
|
53
53
|
# Feature bundles
|
|
54
|
-
pip install execsql2[formats]
|
|
54
|
+
pip install execsql2[formats] # ODS, Excel, Jinja2, Feather, Parquet, HDF5
|
|
55
|
+
pip install execsql2[upsert] # pg-upsert for PostgreSQL upsert operations
|
|
55
56
|
pip install execsql2[auth] # OS keyring integration
|
|
56
57
|
pip install execsql2[auth-plaintext] # Keyring + plaintext file backend (headless Linux)
|
|
57
58
|
pip install execsql2[auth-encrypted] # Keyring + encrypted file backend (headless Linux)
|
|
@@ -97,33 +98,41 @@ execsql script.sql # read connection from config file
|
|
|
97
98
|
|
|
98
99
|
## Options
|
|
99
100
|
|
|
100
|
-
| Flag
|
|
101
|
-
|
|
|
102
|
-
| `-t {p,m,s,l,k,a,f,o,d}`
|
|
103
|
-
| `-u USER`
|
|
104
|
-
| `-p PORT`
|
|
105
|
-
| `-a VALUE`
|
|
106
|
-
| `-
|
|
107
|
-
| `-
|
|
108
|
-
| `-
|
|
109
|
-
| `-
|
|
110
|
-
| `-
|
|
111
|
-
| `-
|
|
112
|
-
| `-
|
|
113
|
-
| `-
|
|
114
|
-
|
|
|
115
|
-
|
|
|
116
|
-
| `--
|
|
117
|
-
| `--
|
|
118
|
-
|
|
|
119
|
-
|
|
|
120
|
-
| `--
|
|
121
|
-
| `--
|
|
122
|
-
| `--
|
|
123
|
-
| `--
|
|
124
|
-
| `--
|
|
125
|
-
| `--
|
|
126
|
-
| `--
|
|
101
|
+
| Flag | Description |
|
|
102
|
+
| ------------------------------------- | ---------------------------------------------------------------- |
|
|
103
|
+
| `-t {p,m,s,l,k,a,f,o,d}` | Database type |
|
|
104
|
+
| `-u USER` | Database username |
|
|
105
|
+
| `-p PORT` | Server port |
|
|
106
|
+
| `-a VALUE` | Set substitution variable `$ARG_x` |
|
|
107
|
+
| `-b` / `--boolean-int` | Treat integers 0 and 1 as boolean values |
|
|
108
|
+
| `-c SCRIPT` | Execute inline SQL or metacommand string |
|
|
109
|
+
| `-d` | Auto-create export directories |
|
|
110
|
+
| `-e ENCODING` / `--database-encoding` | Character encoding used in the database |
|
|
111
|
+
| `-f ENCODING` | Script file encoding (default: UTF-8) |
|
|
112
|
+
| `-g ENCODING` / `--output-encoding` | Encoding for WRITE and EXPORT output |
|
|
113
|
+
| `-i ENCODING` / `--import-encoding` | Encoding for data files used with IMPORT |
|
|
114
|
+
| `-l` | Write run log to `~/execsql.log` |
|
|
115
|
+
| `-m` | List metacommands and exit |
|
|
116
|
+
| `-n` | Create a new SQLite or PostgreSQL database if it does not exist |
|
|
117
|
+
| `-o` / `--online-help` | Open the online documentation in the default browser |
|
|
118
|
+
| `-s N` / `--scan-lines` | Lines to scan for IMPORT format detection (0 = scan entire file) |
|
|
119
|
+
| `-v {0,1,2,3}` | GUI level (0=none, 1=password, 2=selection, 3=full) |
|
|
120
|
+
| `-w` | Skip password prompt when a username is supplied |
|
|
121
|
+
| `-y` / `--encodings` | List available encoding names and exit |
|
|
122
|
+
| `-z KB` / `--import-buffer` | Import buffer size in KB (default: 32) |
|
|
123
|
+
| `--dsn URL` | Connection string (e.g. `postgresql://user:pass@host/db`) |
|
|
124
|
+
| `--output-dir DIR` | Default base directory for EXPORT output files |
|
|
125
|
+
| `--dry-run` | Parse the script and report commands without executing |
|
|
126
|
+
| `--lint` | Static analysis: check structure and warn on issues (no DB) |
|
|
127
|
+
| `--parse-tree` | Print the script's AST structure and exit (no DB) |
|
|
128
|
+
| `--list-plugins` | List discovered plugins and exit |
|
|
129
|
+
| `--ping` | Test database connectivity and exit |
|
|
130
|
+
| `--profile` | Show per-statement timing summary after execution |
|
|
131
|
+
| `--progress` | Show a progress bar for long-running IMPORT operations |
|
|
132
|
+
| `--config FILE` | Load an explicit config file (highest priority after CLI args) |
|
|
133
|
+
| `--debug` | Start in step-through debug mode (REPL pauses before each stmt) |
|
|
134
|
+
| `--dump-keywords` | Print metacommand keywords as JSON and exit |
|
|
135
|
+
| `--gui-framework {tkinter,textual}` | GUI framework for interactive prompts |
|
|
127
136
|
|
|
128
137
|
Run `execsql --help` for the full option list, or `execsql -m` to list all metacommands.
|
|
129
138
|
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
# Contributors
|
|
2
2
|
|
|
3
|
+
R. Dreas Nielsen
|
|
4
|
+
: Original author of [execsql](https://execsql.readthedocs.io/) (v1.0 through v1.130.1). Designed and implemented the metacommand language, substitution variable system, database adapters, export/import framework, GUI prompt system, and all core functionality that execsql2 is built on.
|
|
5
|
+
|
|
3
6
|
Elizabeth Shea
|
|
4
7
|
: Brainstorming, testing, coding of the conditional expression parser, coding of the 'with arguments' and 'with parameters' clauses of the [SCRIPT](../reference/metacommands.md#beginscript) metacommand, coding of the WHILE and UNTIL clauses of the [EXECUTE SCRIPT](../reference/metacommands.md#executescript) metacommand, coding of [deferred variable substitution](../reference/substitution_vars.md#deferred_substitution), corrections to the [PROMPT ENTRY_FORM](../reference/metacommands.md#prompt_entry) metacommand, examples, implementation of the ["+" prefix](../reference/substitution_vars.md#outer_scope_referent) for substitution variables, and other minor corrections.
|
|
@@ -185,7 +185,7 @@ print(result.variables) # final substitution variable state
|
|
|
185
185
|
- **DSN or connection** — pass a DSN string (`dsn="sqlite:///my.db"`) or a pre-existing `Database` object (`connection=conn`).
|
|
186
186
|
- **Substitution variables** — pass a `variables` dict; keys are automatically `$`-prefixed.
|
|
187
187
|
- **Error control** — `halt_on_error=True` (default) stops on the first error; `halt_on_error=False` captures errors and continues.
|
|
188
|
-
- **Isolation** — each `run()` call uses an isolated `RuntimeContext
|
|
188
|
+
- **Isolation** — each `run()` call uses an isolated `RuntimeContext` stored in thread-local storage. Multiple calls do not share state, and concurrent calls from different threads are safe.
|
|
189
189
|
- **Result object** — `ScriptResult` is a frozen dataclass with `success`, `commands_run`, `elapsed`, `errors`, and `variables`.
|
|
190
190
|
- **Exception convenience** — call `result.raise_on_error()` to raise `ExecSqlError` if the script failed.
|
|
191
191
|
|
|
@@ -197,13 +197,17 @@ ______________________________________________________________________
|
|
|
197
197
|
|
|
198
198
|
The CLI framework changed from `optparse` to [Typer](https://typer.tiangolo.com/) with Rich-formatted help text. All original short flags (`-a` through `-z`) are preserved. The tool can be invoked as either `execsql` or `execsql2`.
|
|
199
199
|
|
|
200
|
+
### Default Database Type
|
|
201
|
+
|
|
202
|
+
The default database type (`-t`) changed from Access (`a`) to SQLite (`l`). Upstream defaulted to Access, which requires Windows and pyodbc. SQLite is cross-platform, ships with Python, and is the most common use case. Users targeting Access databases should pass `-t a` explicitly.
|
|
203
|
+
|
|
200
204
|
### Configuration
|
|
201
205
|
|
|
202
206
|
- **`linux_config_file`** — now only active on Linux (`sys.platform == "linux"`). Upstream applied it to all POSIX systems, including macOS. A new `macos_config_file` option handles macOS specifically.
|
|
203
207
|
|
|
204
208
|
### Internal State Management
|
|
205
209
|
|
|
206
|
-
All 33 mutable runtime globals in `state.py` have been consolidated into a `RuntimeContext` object. The module uses a transparent proxy so existing code is unaffected
|
|
210
|
+
All 33 mutable runtime globals in `state.py` have been consolidated into a `RuntimeContext` object stored in `threading.local()`. The module uses a transparent proxy so existing code is unaffected. Each thread gets its own isolated context, enabling concurrent `from execsql import run` calls and future PARALLEL blocks.
|
|
207
211
|
|
|
208
212
|
### Substitution Variables
|
|
209
213
|
|
|
@@ -276,37 +280,38 @@ These are behavioral changes driven by security or correctness issues in the ups
|
|
|
276
280
|
|
|
277
281
|
### Bug Fixes
|
|
278
282
|
|
|
279
|
-
| Area | Fix
|
|
280
|
-
| --------------------------------------------------- |
|
|
281
|
-
| Oracle default port | Corrected from `5432` (PostgreSQL) to `1521`.
|
|
282
|
-
| MySQL `LOAD DATA INFILE` encoding | Python encoding names are now mapped to MySQL charset names.
|
|
283
|
-
| `dt_cast` type converters | Base `Database` class auto-populates 8 type converters that were previously left empty after the refactor.
|
|
284
|
-
| `FileWriter` CPU busy-loop | Uses blocking `queue.get(timeout=0.1)` instead of `get_nowait()` in a tight loop.
|
|
285
|
-
| Substitution variable cycles | 100-iteration limit prevents infinite loops on cyclic variable references.
|
|
286
|
-
| Script location in error messages | `ErrInfo.script_file` and `script_line_no` are now populated via `stamp_errinfo()` so error output includes "Line N of script foo.sql" context — restoring behavior present in the monolith.
|
|
287
|
-
| `$ERROR_MESSAGE` not updated | `$ERROR_MESSAGE` is now set on every error path: `exit_now()`, non-halting SQL errors, and non-halting metacommand errors. Previously it was initialized to `""` and never changed.
|
|
288
|
-
| Metacommand error message lost | When `halt_on_metacommand_err` is `ON`, the original handler `ErrInfo` is now re-raised; the generic "Unknown metacommand" message no longer replaces the specific error from the handler.
|
|
289
|
-
| 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.
|
|
290
|
-
| `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.
|
|
291
|
-
| `win_config_file` broken | Checked `os.name == "windows"` which Python never returns (correct value is `"nt"`). Fixed to `os.name == "nt"`. Inherited from upstream.
|
|
292
|
-
| `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.
|
|
293
|
-
| 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.
|
|
294
|
-
| 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.
|
|
295
|
-
| `$CURRENT_DATABASE`/`$CURRENT_DBMS` stale after USE | These variables were only set at startup and on CONNECT, not refreshed when `USE` switched the active database. Now set in `set_static_system_vars()` so they update on any connection change.
|
|
296
|
-
| `DT_Text.data_type_name` wrong | Was `"character"` (same as `DT_Character`), making error messages indistinguishable. Corrected to `"text"`. Inherited from upstream.
|
|
297
|
-
| `DT_Varchar` non-string data unchecked | `_from_data()` only enforced the 255-char limit for `str` inputs; non-string values passed through without conversion or length check. Inherited from upstream.
|
|
298
|
-
| `WriteHooks.write_err()` crash on empty string | `strval[-1]` raised `IndexError` on empty input. Fixed to use `str.endswith()`. Inherited from upstream.
|
|
299
|
-
| `NumericParser` division by zero | `NumericAstNode.eval()` raised unhandled `ZeroDivisionError`. Now raises `NumericParserError` with a clear message. Inherited from upstream.
|
|
300
|
-
| `CondAstNode.eval()` could return `None` | Missing fallthrough for unknown node types silently returned `None`. Now raises `CondParserError`. Inherited from upstream.
|
|
301
|
-
| `DT_Timestamp` claims time-only values | `dateutil.parser.parse()` silently fills in today's date for bare time strings like `"13:15:45"`, so `DT_Timestamp` matched before `DT_Time` in the inference order. `parse_datetime()` now rejects time-only strings. Inherited from upstream.
|
|
302
|
-
| `CounterVars.substitute` skipped pos 0–1 | `re.I` was passed as the positional `pos` argument, skipping the first 2 characters of every string during counter variable expansion. Counter variables at the very start of a line were never matched. Inherited from upstream.
|
|
303
|
-
| `exec_cmd` (SQLite/DuckDB) always raised TypeError | Passed bytes to `curs.execute()` via `.encode()`, which Python 3 `sqlite3`/`duckdb` reject. `EXECUTE PROCEDURE` metacommand was non-functional on these backends. Inherited from upstream.
|
|
304
|
-
| MySQL `LOAD DATA INFILE` injection | File path, delimiter, and quotechar were interpolated without escaping. Now single-quotes are escaped consistent with the PostgreSQL COPY path. Inherited from upstream.
|
|
305
|
-
| Config file chain infinite loop | The `config_file` directive could chain config files without limit. A circular reference (via symlinks or different relative paths) caused an infinite loop at startup. Now capped at 20 files. Inherited from upstream.
|
|
306
|
-
| Cursor leaks in database adapters | ~15 methods across all adapters used `curs = self.cursor()` / `curs.close()` without `try/finally`. If the query raised, the cursor leaked. Converted to `with self._cursor() as curs:`. Inherited from upstream.
|
|
307
|
-
| JSON export malformed on special column names | Column names containing `"` or `\` produced invalid JSON. Now uses `json.dumps()` for all field names. Inherited from upstream.
|
|
308
|
-
| Temp file creation TOCTOU race | `TempFileMgr.new_temp_fn()` discarded the `NamedTemporaryFile` handle, creating a race window. Now uses `tempfile.mkstemp()` for secure creation. Inherited from upstream.
|
|
309
|
-
| `shlex.split` on Windows incorrect mode | Called without `posix=False` on Windows, mishandling backslash-heavy paths in SHELL commands. Inherited from upstream.
|
|
283
|
+
| Area | Fix |
|
|
284
|
+
| --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
285
|
+
| Oracle default port | Corrected from `5432` (PostgreSQL) to `1521`. |
|
|
286
|
+
| MySQL `LOAD DATA INFILE` encoding | Python encoding names are now mapped to MySQL charset names. |
|
|
287
|
+
| `dt_cast` type converters | Base `Database` class auto-populates 8 type converters that were previously left empty after the refactor. |
|
|
288
|
+
| `FileWriter` CPU busy-loop | Uses blocking `queue.get(timeout=0.1)` instead of `get_nowait()` in a tight loop. |
|
|
289
|
+
| Substitution variable cycles | 100-iteration limit prevents infinite loops on cyclic variable references. |
|
|
290
|
+
| Script location in error messages | `ErrInfo.script_file` and `script_line_no` are now populated via `stamp_errinfo()` so error output includes "Line N of script foo.sql" context — restoring behavior present in the monolith. |
|
|
291
|
+
| `$ERROR_MESSAGE` not updated | `$ERROR_MESSAGE` is now set on every error path: `exit_now()`, non-halting SQL errors, and non-halting metacommand errors. Previously it was initialized to `""` and never changed. |
|
|
292
|
+
| Metacommand error message lost | When `halt_on_metacommand_err` is `ON`, the original handler `ErrInfo` is now re-raised; the generic "Unknown metacommand" message no longer replaces the specific error from the handler. |
|
|
293
|
+
| 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. |
|
|
294
|
+
| `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. |
|
|
295
|
+
| `win_config_file` broken | Checked `os.name == "windows"` which Python never returns (correct value is `"nt"`). Fixed to `os.name == "nt"`. Inherited from upstream. |
|
|
296
|
+
| `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. |
|
|
297
|
+
| 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. |
|
|
298
|
+
| 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. |
|
|
299
|
+
| `$CURRENT_DATABASE`/`$CURRENT_DBMS` stale after USE | These variables were only set at startup and on CONNECT, not refreshed when `USE` switched the active database. Now set in `set_static_system_vars()` so they update on any connection change. |
|
|
300
|
+
| `DT_Text.data_type_name` wrong | Was `"character"` (same as `DT_Character`), making error messages indistinguishable. Corrected to `"text"`. Inherited from upstream. |
|
|
301
|
+
| `DT_Varchar` non-string data unchecked | `_from_data()` only enforced the 255-char limit for `str` inputs; non-string values passed through without conversion or length check. Inherited from upstream. |
|
|
302
|
+
| `WriteHooks.write_err()` crash on empty string | `strval[-1]` raised `IndexError` on empty input. Fixed to use `str.endswith()`. Inherited from upstream. |
|
|
303
|
+
| `NumericParser` division by zero | `NumericAstNode.eval()` raised unhandled `ZeroDivisionError`. Now raises `NumericParserError` with a clear message. Inherited from upstream. |
|
|
304
|
+
| `CondAstNode.eval()` could return `None` | Missing fallthrough for unknown node types silently returned `None`. Now raises `CondParserError`. Inherited from upstream. |
|
|
305
|
+
| `DT_Timestamp` claims time-only values | `dateutil.parser.parse()` silently fills in today's date for bare time strings like `"13:15:45"`, so `DT_Timestamp` matched before `DT_Time` in the inference order. `parse_datetime()` now rejects time-only strings. Inherited from upstream. |
|
|
306
|
+
| `CounterVars.substitute` skipped pos 0–1 | `re.I` was passed as the positional `pos` argument, skipping the first 2 characters of every string during counter variable expansion. Counter variables at the very start of a line were never matched. Inherited from upstream. |
|
|
307
|
+
| `exec_cmd` (SQLite/DuckDB) always raised TypeError | Passed bytes to `curs.execute()` via `.encode()`, which Python 3 `sqlite3`/`duckdb` reject. `EXECUTE PROCEDURE` metacommand was non-functional on these backends. Inherited from upstream. |
|
|
308
|
+
| MySQL `LOAD DATA INFILE` injection | File path, delimiter, and quotechar were interpolated without escaping. Now single-quotes are escaped consistent with the PostgreSQL COPY path. Inherited from upstream. |
|
|
309
|
+
| Config file chain infinite loop | The `config_file` directive could chain config files without limit. A circular reference (via symlinks or different relative paths) caused an infinite loop at startup. Now capped at 20 files. Inherited from upstream. |
|
|
310
|
+
| Cursor leaks in database adapters | ~15 methods across all adapters used `curs = self.cursor()` / `curs.close()` without `try/finally`. If the query raised, the cursor leaked. Converted to `with self._cursor() as curs:`. Inherited from upstream. |
|
|
311
|
+
| JSON export malformed on special column names | Column names containing `"` or `\` produced invalid JSON. Now uses `json.dumps()` for all field names. Inherited from upstream. |
|
|
312
|
+
| Temp file creation TOCTOU race | `TempFileMgr.new_temp_fn()` discarded the `NamedTemporaryFile` handle, creating a race window. Now uses `tempfile.mkstemp()` for secure creation. Inherited from upstream. |
|
|
313
|
+
| `shlex.split` on Windows incorrect mode | Called without `posix=False` on Windows, mishandling backslash-heavy paths in SHELL commands. Inherited from upstream. |
|
|
314
|
+
| AST executor `~`/`+` variable scoping broken | The AST executor passed `localvars` through function parameters but never pushed `CommandList` frames onto `commandliststack`. Legacy metacommand handlers (`x_sub`, `x_rm_sub`, `xf_sub_defined`, `SUB_LOCAL`, prompt handlers, REPL) access `commandliststack[-1]` for `~` local and `+` outer-scope variables. This caused `~` vars to be invisible to SQL, `SUB_DEFINED(~var)` to always return false, and the REPL `.vars`/`.stack` to show empty state. Fixed by pushing/popping `CommandList` frames in `execute()` and `_execute_script_native()`. |
|
|
310
315
|
|
|
311
316
|
______________________________________________________________________
|
|
312
317
|
|
|
@@ -28,10 +28,16 @@ Each module below implements one or more output formats. Every writer follows th
|
|
|
28
28
|
|
|
29
29
|
::: execsql.exporters.latex
|
|
30
30
|
|
|
31
|
+
::: execsql.exporters.markdown
|
|
32
|
+
|
|
31
33
|
::: execsql.exporters.ods
|
|
32
34
|
|
|
33
35
|
::: execsql.exporters.xls
|
|
34
36
|
|
|
37
|
+
::: execsql.exporters.xlsx
|
|
38
|
+
|
|
39
|
+
::: execsql.exporters.yaml
|
|
40
|
+
|
|
35
41
|
::: execsql.exporters.feather
|
|
36
42
|
|
|
37
43
|
::: execsql.exporters.parquet
|
|
@@ -24,6 +24,30 @@ result: ScriptResult = run(
|
|
|
24
24
|
|
|
25
25
|
See the [README](https://github.com/geocoug/execsql#library-api) for full examples.
|
|
26
26
|
|
|
27
|
+
### Thread Safety
|
|
28
|
+
|
|
29
|
+
`run()` is **thread-safe**. Each call creates an isolated `RuntimeContext` stored in thread-local storage, so concurrent calls from different threads do not share database connections, substitution variables, or execution state.
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
import threading
|
|
33
|
+
from execsql import run
|
|
34
|
+
|
|
35
|
+
def etl_worker(script, dsn):
|
|
36
|
+
result = run(script=script, dsn=dsn)
|
|
37
|
+
print(f"{script}: {'OK' if result.success else 'FAIL'}")
|
|
38
|
+
|
|
39
|
+
threads = [
|
|
40
|
+
threading.Thread(target=etl_worker, args=("load_us.sql", "postgresql://host/us_db")),
|
|
41
|
+
threading.Thread(target=etl_worker, args=("load_eu.sql", "postgresql://host/eu_db")),
|
|
42
|
+
]
|
|
43
|
+
for t in threads:
|
|
44
|
+
t.start()
|
|
45
|
+
for t in threads:
|
|
46
|
+
t.join()
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Each thread gets its own database connections, IF/LOOP stacks, substitution variables, and error state. No locking is required.
|
|
50
|
+
|
|
27
51
|
## Extension Guides
|
|
28
52
|
|
|
29
53
|
| Extension type | Guide | API reference |
|
|
@@ -42,16 +42,20 @@ ______________________________________________________________________
|
|
|
42
42
|
|
|
43
43
|
Add the handler to whichever sibling module fits best:
|
|
44
44
|
|
|
45
|
-
| Module | Handles
|
|
46
|
-
| --------------- |
|
|
47
|
-
| `connect.py` | Database connections, `USE`, `DISCONNECT`
|
|
48
|
-
| `control.py` | Control flow: `IF`, `LOOP`, `BREAK`, `HALT`, batch
|
|
49
|
-
| `data.py` | Variable manipulation: `SUB`, `SUBDATA`, counters
|
|
50
|
-
| `io.py` |
|
|
51
|
-
| `
|
|
52
|
-
| `
|
|
53
|
-
| `
|
|
54
|
-
| `
|
|
45
|
+
| Module | Handles |
|
|
46
|
+
| --------------- | ---------------------------------------------------------------------- |
|
|
47
|
+
| `connect.py` | Database connections, `USE`, `DISCONNECT` |
|
|
48
|
+
| `control.py` | Control flow: `IF`, `LOOP`, `BREAK`, `HALT`, batch |
|
|
49
|
+
| `data.py` | Variable manipulation: `SUB`, `SUBDATA`, counters |
|
|
50
|
+
| `io.py` | Re-export façade only -- imports and re-exports from the modules below |
|
|
51
|
+
| `io_export.py` | `EXPORT` handlers |
|
|
52
|
+
| `io_import.py` | `IMPORT` handlers |
|
|
53
|
+
| `io_write.py` | `WRITE` and `WRITESCRIPT` handlers |
|
|
54
|
+
| `io_fileops.py` | `INCLUDE`, `ZIP`, `CD`, `SERVE`, and other file operations |
|
|
55
|
+
| `prompt.py` | User interaction: `PROMPT`, `ASK`, `PAUSE`, `MSG` |
|
|
56
|
+
| `system.py` | OS interaction: `SYSTEM_CMD`, `LOG`, `EMAIL`, console |
|
|
57
|
+
| `debug.py` | Debug output |
|
|
58
|
+
| `script_ext.py` | Script extensions |
|
|
55
59
|
|
|
56
60
|
If none of these fit, create a new module and follow the same structure.
|
|
57
61
|
|
|
@@ -61,17 +61,25 @@ flowchart LR
|
|
|
61
61
|
CONFIG["config.py<br/>ConfigData,<br/>StatObj,<br/>WriteHooks"]
|
|
62
62
|
STATE["state.py<br/>Global runtime<br/>singletons"]
|
|
63
63
|
SCRIPT["script/<br/>engine, control,<br/>variables"]
|
|
64
|
-
META["metacommands/<br/>Dispatch table,<br/>~
|
|
64
|
+
META["metacommands/<br/>Dispatch table,<br/>~225 handlers"]
|
|
65
65
|
DB["db/<br/>Database ABC,<br/>9 adapters,<br/>DatabasePool"]
|
|
66
|
-
EXPORT["exporters/<br/>
|
|
66
|
+
EXPORT["exporters/<br/>20+ output<br/>formats"]
|
|
67
67
|
IMPORT["importers/<br/>CSV, ODS,<br/>XLS, Feather"]
|
|
68
68
|
GUI["gui/<br/>Tkinter, Textual,<br/>Console backends"]
|
|
69
69
|
UTILS["utils/<br/>auth, crypto,<br/>fileio, mail,<br/>regex, strings"]
|
|
70
70
|
PARSER["parser.py<br/>CondParser,<br/>NumericParser"]
|
|
71
|
+
DEBUG["debug/<br/>REPL debugger"]
|
|
72
|
+
NOTEBOOK["notebook/<br/>Jupyter integration"]
|
|
73
|
+
SERVER["server/<br/>Daemon"]
|
|
74
|
+
LSP["lsp/<br/>Language Server<br/>Protocol"]
|
|
71
75
|
|
|
72
76
|
CLI --> CONFIG
|
|
73
77
|
CLI --> STATE
|
|
74
78
|
CLI --> SCRIPT
|
|
79
|
+
CLI --> DEBUG
|
|
80
|
+
CLI --> NOTEBOOK
|
|
81
|
+
CLI --> SERVER
|
|
82
|
+
CLI --> LSP
|
|
75
83
|
SCRIPT --> STATE
|
|
76
84
|
SCRIPT --> META
|
|
77
85
|
META --> STATE
|
|
@@ -92,11 +100,11 @@ flowchart LR
|
|
|
92
100
|
| --------------- | ------------------------------------------------------------------------------------------------ |
|
|
93
101
|
| `cli/` | Typer app, `_run()` orchestration, DSN URL parsing, Rich help output |
|
|
94
102
|
| `config.py` | `ConfigData` (INI merging), `StatObj` (runtime flags), `WriteHooks` (stdout/stderr redirection) |
|
|
95
|
-
| `state.py` |
|
|
103
|
+
| `state.py` | Thread-local runtime store -- all shared mutable state lives here, isolated per-thread |
|
|
96
104
|
| `script/` | `CommandList`, `MetaCommandList`, `SubVarSet`, `ScriptFile`, `runscripts()` |
|
|
97
105
|
| `metacommands/` | `build_dispatch_table()`, all `x_*` handlers, `build_conditional_table()`, all `xf_*` predicates |
|
|
98
106
|
| `db/` | `Database` ABC, `DatabasePool`, 9 adapter modules (postgres, sqlite, duckdb, mysql, etc.) |
|
|
99
|
-
| `exporters/` | `ExportRecord`, `ExportMetadata`, `WriteSpec`,
|
|
107
|
+
| `exporters/` | `ExportRecord`, `ExportMetadata`, `WriteSpec`, 20+ format writers (CSV, JSON, XML, HTML, etc.) |
|
|
100
108
|
| `importers/` | `CsvFile`, `OdsFile`, `XlsFile`, `FeatherFile` -- data import backends |
|
|
101
109
|
| `gui/` | `GuiBackend` ABC, `TkinterBackend`, `TextualBackend`, `ConsoleBackend` |
|
|
102
110
|
| `utils/` | Shared utilities: file I/O, encryption, mail, regex helpers, string manipulation, timers |
|
|
@@ -105,6 +113,10 @@ flowchart LR
|
|
|
105
113
|
| `models.py` | `Column`, `DataTable`, `JsonDatatype` |
|
|
106
114
|
| `exceptions.py` | `ExecSqlError` base, `ErrInfo`, `ConfigError`, `DataTypeError`, `DbTypeError`, etc. |
|
|
107
115
|
| `plugins.py` | Entry-point plugin discovery for metacommands, exporters, and importers |
|
|
116
|
+
| `debug/` | Interactive REPL debugger for stepping through script execution |
|
|
117
|
+
| `notebook/` | Jupyter notebook integration -- run execsql scripts from notebook cells |
|
|
118
|
+
| `server/` | Daemon mode -- run execsql as a long-lived process accepting script requests |
|
|
119
|
+
| `lsp/` | Language Server Protocol server -- powers editor features such as hover docs and autocomplete |
|
|
108
120
|
|
|
109
121
|
______________________________________________________________________
|
|
110
122
|
|
|
@@ -121,7 +133,7 @@ The AST executor is the only execution engine. Use `--parse-tree` to visualize t
|
|
|
121
133
|
|
|
122
134
|
### RuntimeContext Isolation
|
|
123
135
|
|
|
124
|
-
The executor accepts an explicit `RuntimeContext` parameter (`ctx`). The `active_context()` context manager in `state.py` installs a context as the active
|
|
136
|
+
The executor accepts an explicit `RuntimeContext` parameter (`ctx`). The `active_context()` context manager in `state.py` installs a context as the active thread-local so all metacommand handlers and database adapters automatically resolve against it. Each thread gets its own isolated context via `threading.local()`, enabling concurrent `from execsql import run` calls and future PARALLEL blocks. The RuntimeContext carries AST-specific state: `ast_scripts` (script block registry) and `include_chain` (circular include detection).
|
|
125
137
|
|
|
126
138
|
______________________________________________________________________
|
|
127
139
|
|
|
@@ -199,12 +211,12 @@ ______________________________________________________________________
|
|
|
199
211
|
|
|
200
212
|
## Metacommand Dispatch
|
|
201
213
|
|
|
202
|
-
Metacommands are lines in SQL scripts prefixed with `-- !x!`. At import time, `metacommands/__init__.py` calls `build_dispatch_table()`, which populates a `MetaCommandList` with all `mcl.add()` registrations (~
|
|
214
|
+
Metacommands are lines in SQL scripts prefixed with `-- !x!`. At import time, `metacommands/__init__.py` calls `build_dispatch_table()`, which populates a `MetaCommandList` with all `mcl.add()` registrations (~225 regex patterns). This singleton is stored as `_state.metacommandlist`.
|
|
203
215
|
|
|
204
216
|
### How dispatch works
|
|
205
217
|
|
|
206
218
|
1. `MetacommandStmt.run()` calls `_state.metacommandlist.eval(cmd_str)`.
|
|
207
|
-
1. `MetaCommandList.eval()` extracts the leading keyword from the command string and looks up candidate `MetaCommand` entries via a keyword index (reducing the search space from ~
|
|
219
|
+
1. `MetaCommandList.eval()` extracts the leading keyword from the command string and looks up candidate `MetaCommand` entries via a keyword index (reducing the search space from ~225 to typically 1-5 entries).
|
|
208
220
|
1. Each candidate's compiled regex is tested against the full command string.
|
|
209
221
|
1. On a match, the handler function is called with all named regex groups as keyword arguments, plus `metacommandline` (the original unmodified line).
|
|
210
222
|
1. On success, the matched entry's hit counter is incremented.
|
|
@@ -352,13 +364,13 @@ ______________________________________________________________________
|
|
|
352
364
|
|
|
353
365
|
## Global State
|
|
354
366
|
|
|
355
|
-
`state.py` is the
|
|
367
|
+
`state.py` is the thread-local mutable state store for the runtime. All other modules access it via:
|
|
356
368
|
|
|
357
369
|
```python
|
|
358
370
|
import execsql.state as _state
|
|
359
371
|
```
|
|
360
372
|
|
|
361
|
-
This import pattern (always as `_state`, always accessed inside function/method bodies) avoids circular imports at load time. The module
|
|
373
|
+
This import pattern (always as `_state`, always accessed inside function/method bodies) avoids circular imports at load time. The module stores all mutable state in a `threading.local()` instance, so each thread gets its own isolated `RuntimeContext`. The module provides two management functions:
|
|
362
374
|
|
|
363
375
|
- **`initialize(config, dispatch_table, conditional_table)`** -- Called once from `_run()` to create runtime singletons (`DatabasePool`, `IfLevels`, `CounterVars`, etc.).
|
|
364
376
|
- **`reset()`** -- Tears down all state for test isolation.
|