execsql2 2.15.1__tar.gz → 2.15.4__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.15.1 → execsql2-2.15.4}/CHANGELOG.md +34 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/PKG-INFO +11 -2
- {execsql2-2.15.1 → execsql2-2.15.4}/README.md +3 -1
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/about/divergence.md +5 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/reference/security.md +2 -2
- {execsql2-2.15.1 → execsql2-2.15.4}/pyproject.toml +6 -2
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/config.py +1 -1
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/db/duckdb.py +6 -7
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/db/sqlite.py +46 -46
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/parser.py +3 -2
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/types.py +28 -15
- execsql2-2.15.4/tests/importers/test_csv_edge_cases.py +209 -0
- execsql2-2.15.4/tests/test_debug_repl.py +555 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/test_format.py +119 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/test_parser.py +81 -2
- {execsql2-2.15.1 → execsql2-2.15.4}/uv.lock +64 -2
- execsql2-2.15.1/CLAUDE.md +0 -56
- {execsql2-2.15.1 → execsql2-2.15.4}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/.github/workflows/ci-cd.yml +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/.gitignore +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/.pre-commit-config.yaml +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/.pre-commit-hooks.yaml +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/.python-version +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/.readthedocs.yaml +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/CONTRIBUTING.md +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/LICENSE.txt +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/NOTICE +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/SECURITY.md +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/about/contributors.md +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/about/copyright.md +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/api/cli.md +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/api/db.md +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/api/exporters.md +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/api/importers.md +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/api/index.md +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/api/metacommands.md +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/dev/adding_db_adapters.md +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/dev/adding_exporters.md +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/dev/adding_importers.md +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/dev/adding_metacommands.md +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/dev/architecture.md +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/getting-started/installation.md +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/getting-started/requirements.md +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/getting-started/syntax.md +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/guides/debugging.md +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/guides/documentation.md +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/guides/encoding.md +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/guides/examples.md +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/guides/formatter.md +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/guides/logging.md +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/guides/sql_syntax.md +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/guides/usage.md +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/guides/using_scripts.md +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/images/Compare_planets.png +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/images/actions.png +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/images/actions2.png +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/images/checkboxes.png +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/images/connect.b64 +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/images/connect.png +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/images/create_conf.png +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/images/data_error1_screenshot.jpg +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/images/entry_form.png +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/images/execsql_console.png +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/images/execsql_logo_01.png +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/images/fatals.png +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/images/logo_small.png +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/images/pause_terminal.png +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/images/pause_terminal_sm.b64 +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/images/pause_terminal_sm.png +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/images/prompt_compare.png +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/images/set_build_commands.jpg +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/images/unit_conversions.b64 +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/images/unit_conversions_029.png +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/images/unmatched.png +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/images/vim_execsql_highlight.png +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/index.md +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/reference/configuration.md +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/reference/metacommands.md +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/docs/reference/substitution_vars.md +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/extras/vscode-execsql/README.md +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/extras/vscode-execsql/package.json +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/extras/vscode-execsql/syntaxes/execsql.tmLanguage.json +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/justfile +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/scripts/generate_vscode_grammar.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/__init__.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/__main__.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/cli/__init__.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/cli/dsn.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/cli/help.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/cli/lint.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/cli/run.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/db/__init__.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/db/access.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/db/base.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/db/dsn.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/db/factory.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/db/firebird.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/db/mysql.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/db/oracle.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/db/postgres.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/db/sqlserver.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/debug/__init__.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/debug/repl.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/exceptions.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/exporters/__init__.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/exporters/base.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/exporters/delimited.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/exporters/duckdb.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/exporters/feather.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/exporters/html.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/exporters/json.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/exporters/latex.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/exporters/markdown.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/exporters/ods.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/exporters/parquet.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/exporters/pretty.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/exporters/protocol.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/exporters/raw.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/exporters/sqlite.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/exporters/templates.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/exporters/values.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/exporters/xls.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/exporters/xlsx.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/exporters/xml.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/exporters/yaml.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/exporters/zip.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/format.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/gui/__init__.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/gui/base.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/gui/console.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/gui/desktop.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/gui/tui.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/importers/__init__.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/importers/base.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/importers/csv.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/importers/feather.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/importers/json.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/importers/ods.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/importers/xls.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/metacommands/__init__.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/metacommands/conditions.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/metacommands/connect.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/metacommands/control.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/metacommands/data.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/metacommands/debug.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/metacommands/dispatch.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/metacommands/io.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/metacommands/io_export.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/metacommands/io_fileops.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/metacommands/io_import.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/metacommands/io_write.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/metacommands/prompt.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/metacommands/script_ext.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/metacommands/system.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/metacommands/upsert.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/models.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/py.typed +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/script/__init__.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/script/control.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/script/engine.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/script/variables.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/state.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/utils/__init__.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/utils/auth.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/utils/crypto.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/utils/datetime.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/utils/errors.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/utils/fileio.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/utils/gui.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/utils/mail.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/utils/numeric.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/utils/regex.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/utils/strings.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/src/execsql/utils/timer.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/templates/README.md +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/templates/config_settings.sqlite +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/templates/example_config_prompt.sql +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/templates/execsql.conf +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/templates/make_config_db.sql +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/templates/md_compare.sql +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/templates/md_glossary.sql +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/templates/md_upsert.sql +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/templates/pg_compare.sql +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/templates/pg_glossary.sql +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/templates/pg_upsert.sql +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/templates/script_template.sql +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/templates/ss_compare.sql +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/templates/ss_glossary.sql +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/templates/ss_upsert.sql +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/__init__.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/cli/__init__.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/cli/test_cli.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/cli/test_cli_e2e.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/cli/test_cli_run.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/cli/test_lint.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/cli/test_ping.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/cli/test_profile.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/conftest.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/db/__init__.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/db/test_base.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/db/test_duckdb.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/db/test_factory.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/db/test_postgres.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/db/test_sqlite.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/db/test_sqlite_extra.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/exporters/__init__.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/exporters/test_base.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/exporters/test_db.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/exporters/test_delimited.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/exporters/test_duckdb_exporter.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/exporters/test_exporters.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/exporters/test_feather.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/exporters/test_html_extended.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/exporters/test_html_latex.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/exporters/test_json.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/exporters/test_json_extended.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/exporters/test_latex_extended.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/exporters/test_markdown.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/exporters/test_ods.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/exporters/test_parquet.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/exporters/test_pretty_extended.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/exporters/test_raw_extended.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/exporters/test_sqlite_exporter.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/exporters/test_templates.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/exporters/test_templates_extended.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/exporters/test_values_extended.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/exporters/test_xls_xlsx.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/exporters/test_xlsx.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/exporters/test_xml.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/exporters/test_yaml.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/exporters/test_zip.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/gui/__init__.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/gui/test_backends.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/gui/test_compare_stats.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/gui/test_compute_row_diffs.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/importers/__init__.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/importers/test_base_extended.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/importers/test_csv_importer.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/importers/test_feather_importer.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/importers/test_json_importer.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/importers/test_ods_importer.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/importers/test_xls_importer.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/integration/__init__.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/integration/conftest.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/integration/test_dsn.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/integration/test_duckdb.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/integration/test_mysql.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/integration/test_postgres.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/integration/test_sqlite.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/metacommands/__init__.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/metacommands/test_assert.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/metacommands/test_breakpoint.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/metacommands/test_connect.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/metacommands/test_io_export.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/metacommands/test_io_import.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/metacommands/test_metacommands.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/metacommands/test_metacommands_connect.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/metacommands/test_metacommands_data.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/metacommands/test_metacommands_extended.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/metacommands/test_metacommands_fileops_extra.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/metacommands/test_metacommands_io.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/metacommands/test_metacommands_io_write_extra.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/metacommands/test_metacommands_script_ext.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/metacommands/test_metacommands_system.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/metacommands/test_metacommands_system_extra.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/metacommands/test_pg_upsert.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/metacommands/test_row_count.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/test_config.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/test_config_data.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/test_config_extended.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/test_engine.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/test_error_messages.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/test_exceptions.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/test_mail.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/test_models.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/test_package.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/test_registry.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/test_script.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/test_state.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/test_types.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/utils/__init__.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/utils/test_auth.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/utils/test_auth_extra.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/utils/test_crypto.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/utils/test_datetime.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/utils/test_errors.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/utils/test_errors_extra.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/utils/test_fileio.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/utils/test_fileio_extra.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/utils/test_numeric.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/utils/test_regex.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/utils/test_strings.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/utils/test_timer.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/tests/utils/test_timer_extra.py +0 -0
- {execsql2-2.15.1 → execsql2-2.15.4}/zensical.toml +0 -0
|
@@ -13,6 +13,40 @@ ______________________________________________________________________
|
|
|
13
13
|
|
|
14
14
|
______________________________________________________________________
|
|
15
15
|
|
|
16
|
+
## [2.15.4] - 2026-04-15
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- Fixed typo in `test_latin1_encoding` test data (`calf\xe9` → `calf\xe9`) that caused assertion failure on Windows CI.
|
|
21
|
+
|
|
22
|
+
______________________________________________________________________
|
|
23
|
+
|
|
24
|
+
## [2.15.3] - 2026-04-15
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- New optional dependency extras `auth-plaintext` and `auth-encrypted` for headless Linux keyring backends. `pip install execsql2[auth-plaintext]` installs `keyring` + `keyrings.alt`; `pip install execsql2[auth-encrypted]` adds `pycryptodome` for the encrypted file backend.
|
|
29
|
+
|
|
30
|
+
______________________________________________________________________
|
|
31
|
+
|
|
32
|
+
## [2.15.2] - 2026-04-14
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
|
|
36
|
+
- `DT_Integer`, `DT_Float`, and `DT_Decimal` data type matchers now use pre-compiled regex class attributes instead of recompiling on every call — reduces overhead during large imports.
|
|
37
|
+
- `DT_Boolean` match tuples are now cached and only rebuilt when the `boolean_words`/`boolean_int` config changes, instead of on every `_is_match()`/`_from_data()` call.
|
|
38
|
+
- SQLite and DuckDB adapter methods (`table_exists`, `table_columns`, `view_exists`, `schema_exists`) now use the `_cursor()` context manager to prevent cursor leaks on exceptions.
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
|
|
42
|
+
- `DT_Text.data_type_name` corrected from `"character"` to `"text"` — error messages now correctly identify the text data type instead of showing "character".
|
|
43
|
+
- `DT_Varchar._from_data()` now converts non-string data to string and enforces the 255-character length limit. Previously, non-string values passed through without conversion or length check.
|
|
44
|
+
- `WriteHooks.write_err()` no longer crashes on empty string input.
|
|
45
|
+
- `CondAstNode.eval()` now raises `CondParserError` for unknown node types instead of silently returning `None`.
|
|
46
|
+
- `NumericAstNode.eval()` now raises `NumericParserError` on division by zero instead of an unhandled `ZeroDivisionError`.
|
|
47
|
+
|
|
48
|
+
______________________________________________________________________
|
|
49
|
+
|
|
16
50
|
## [2.15.1] - 2026-04-14
|
|
17
51
|
|
|
18
52
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: execsql2
|
|
3
|
-
Version: 2.15.
|
|
3
|
+
Version: 2.15.4
|
|
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
|
|
@@ -69,6 +69,13 @@ Requires-Dist: pymysql; extra == 'all-db'
|
|
|
69
69
|
Requires-Dist: pyodbc; extra == 'all-db'
|
|
70
70
|
Provides-Extra: auth
|
|
71
71
|
Requires-Dist: keyring; extra == 'auth'
|
|
72
|
+
Provides-Extra: auth-encrypted
|
|
73
|
+
Requires-Dist: keyring; extra == 'auth-encrypted'
|
|
74
|
+
Requires-Dist: keyrings-alt; extra == 'auth-encrypted'
|
|
75
|
+
Requires-Dist: pycryptodome; extra == 'auth-encrypted'
|
|
76
|
+
Provides-Extra: auth-plaintext
|
|
77
|
+
Requires-Dist: keyring; extra == 'auth-plaintext'
|
|
78
|
+
Requires-Dist: keyrings-alt; extra == 'auth-plaintext'
|
|
72
79
|
Provides-Extra: dev
|
|
73
80
|
Requires-Dist: build>=1.2.2.post1; extra == 'dev'
|
|
74
81
|
Requires-Dist: bump-my-version>=1.2.7; extra == 'dev'
|
|
@@ -167,7 +174,9 @@ pip install execsql2[odbc] # ODBC DSN (pyodbc)
|
|
|
167
174
|
|
|
168
175
|
# Feature bundles
|
|
169
176
|
pip install execsql2[formats] # ODS, Excel, Jinja2, Feather, Parquet, HDF5
|
|
170
|
-
pip install execsql2[auth]
|
|
177
|
+
pip install execsql2[auth] # OS keyring integration
|
|
178
|
+
pip install execsql2[auth-plaintext] # Keyring + plaintext file backend (headless Linux)
|
|
179
|
+
pip install execsql2[auth-encrypted] # Keyring + encrypted file backend (headless Linux)
|
|
171
180
|
|
|
172
181
|
# Convenience
|
|
173
182
|
pip install execsql2[all-db] # All database drivers
|
|
@@ -52,7 +52,9 @@ pip install execsql2[odbc] # ODBC DSN (pyodbc)
|
|
|
52
52
|
|
|
53
53
|
# Feature bundles
|
|
54
54
|
pip install execsql2[formats] # ODS, Excel, Jinja2, Feather, Parquet, HDF5
|
|
55
|
-
pip install execsql2[auth]
|
|
55
|
+
pip install execsql2[auth] # OS keyring integration
|
|
56
|
+
pip install execsql2[auth-plaintext] # Keyring + plaintext file backend (headless Linux)
|
|
57
|
+
pip install execsql2[auth-encrypted] # Keyring + encrypted file backend (headless Linux)
|
|
56
58
|
|
|
57
59
|
# Convenience
|
|
58
60
|
pip install execsql2[all-db] # All database drivers
|
|
@@ -248,6 +248,11 @@ These are behavioral changes driven by security or correctness issues in the ups
|
|
|
248
248
|
| 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. |
|
|
249
249
|
| 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. |
|
|
250
250
|
| `$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. |
|
|
251
|
+
| `DT_Text.data_type_name` wrong | Was `"character"` (same as `DT_Character`), making error messages indistinguishable. Corrected to `"text"`. Inherited from upstream. |
|
|
252
|
+
| `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. |
|
|
253
|
+
| `WriteHooks.write_err()` crash on empty string | `strval[-1]` raised `IndexError` on empty input. Fixed to use `str.endswith()`. Inherited from upstream. |
|
|
254
|
+
| `NumericParser` division by zero | `NumericAstNode.eval()` raised unhandled `ZeroDivisionError`. Now raises `NumericParserError` with a clear message. Inherited from upstream. |
|
|
255
|
+
| `CondAstNode.eval()` could return `None` | Missing fallthrough for unknown node types silently returned `None`. Now raises `CondParserError`. Inherited from upstream. |
|
|
251
256
|
|
|
252
257
|
______________________________________________________________________
|
|
253
258
|
|
|
@@ -52,7 +52,7 @@ To disable keyring integration, set `use_keyring = No` in the `[connect]` sectio
|
|
|
52
52
|
Passwords are stored encrypted on disk. Requires a master password the first time keyring is used per session.
|
|
53
53
|
|
|
54
54
|
```bash
|
|
55
|
-
pip install
|
|
55
|
+
pip install execsql2[auth-encrypted]
|
|
56
56
|
mkdir -p ~/.config/python_keyring
|
|
57
57
|
cat > ~/.config/python_keyring/keyringrc.cfg << 'EOF'
|
|
58
58
|
[backend]
|
|
@@ -67,7 +67,7 @@ The encrypted keyring file is stored at `~/.local/share/python_keyring/crypted_p
|
|
|
67
67
|
Passwords are stored in plain text on disk. No master password is needed — execsql will never prompt for a password after the first successful entry.
|
|
68
68
|
|
|
69
69
|
```bash
|
|
70
|
-
pip install
|
|
70
|
+
pip install execsql2[auth-plaintext]
|
|
71
71
|
mkdir -p ~/.config/python_keyring
|
|
72
72
|
cat > ~/.config/python_keyring/keyringrc.cfg << 'EOF'
|
|
73
73
|
[backend]
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "execsql2"
|
|
7
|
-
version = "2.15.
|
|
7
|
+
version = "2.15.4"
|
|
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" }
|
|
@@ -59,6 +59,8 @@ odbc = ["pyodbc"]
|
|
|
59
59
|
# Feature bundles
|
|
60
60
|
formats = ["odfpy", "xlrd", "openpyxl", "Jinja2", "polars", "tables", "PyYAML"]
|
|
61
61
|
auth = ["keyring"]
|
|
62
|
+
auth-plaintext = ["keyring", "keyrings.alt"]
|
|
63
|
+
auth-encrypted = ["keyring", "keyrings.alt", "pycryptodome"]
|
|
62
64
|
upsert = ["pg-upsert>=1.21.0"]
|
|
63
65
|
# Convenience groups
|
|
64
66
|
all-db = [
|
|
@@ -162,7 +164,7 @@ skip-magic-trailing-comma = false
|
|
|
162
164
|
line-ending = "auto"
|
|
163
165
|
|
|
164
166
|
[tool.bumpversion]
|
|
165
|
-
current_version = "2.15.
|
|
167
|
+
current_version = "2.15.4"
|
|
166
168
|
commit = true
|
|
167
169
|
commit_args = "--no-verify"
|
|
168
170
|
tag = true
|
|
@@ -231,6 +233,8 @@ hel = "hel"
|
|
|
231
233
|
fo = "fo"
|
|
232
234
|
# odfpy package imports as 'odf', not 'of'
|
|
233
235
|
odf = "odf"
|
|
236
|
+
# Latin-1 test data: b"caf\xe9" decodes to "café"
|
|
237
|
+
caf = "caf"
|
|
234
238
|
|
|
235
239
|
[tool.tox]
|
|
236
240
|
required = ["tox-uv"]
|
|
@@ -569,7 +569,7 @@ class WriteHooks:
|
|
|
569
569
|
|
|
570
570
|
def write_err(self, strval: str) -> None:
|
|
571
571
|
"""Write an error string to the error-output hook, or to sys.stderr if unset."""
|
|
572
|
-
if strval
|
|
572
|
+
if not strval.endswith("\n"):
|
|
573
573
|
strval += "\n"
|
|
574
574
|
if self.err_func:
|
|
575
575
|
self.err_func(strval)
|
|
@@ -81,11 +81,10 @@ class DuckDBDatabase(Database):
|
|
|
81
81
|
def schema_exists(self, schema_name: str) -> bool:
|
|
82
82
|
"""Return True if the named schema exists in the current DuckDB catalog."""
|
|
83
83
|
# In DuckDB, the 'schemata' view is not limited to the current database.
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
curs.close()
|
|
84
|
+
with self._cursor() as curs:
|
|
85
|
+
curs.execute(
|
|
86
|
+
"SELECT schema_name FROM information_schema.schemata WHERE schema_name = ? and catalog_name = ?;",
|
|
87
|
+
(schema_name, self.catalog_name),
|
|
88
|
+
)
|
|
89
|
+
rows = curs.fetchall()
|
|
91
90
|
return len(rows) > 0
|
|
@@ -82,21 +82,21 @@ class SQLiteDatabase(Database):
|
|
|
82
82
|
|
|
83
83
|
def table_exists(self, table_name: str, schema_name: str | None = None) -> bool:
|
|
84
84
|
"""Return True if the named table exists in the SQLite database."""
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
85
|
+
with self._cursor() as curs:
|
|
86
|
+
sql = "select name from sqlite_master where type='table' and name=?;"
|
|
87
|
+
try:
|
|
88
|
+
curs.execute(sql, (table_name,))
|
|
89
|
+
except ErrInfo:
|
|
90
|
+
raise
|
|
91
|
+
except Exception as e:
|
|
92
|
+
self.rollback()
|
|
93
|
+
raise ErrInfo(
|
|
94
|
+
type="db",
|
|
95
|
+
command_text=sql,
|
|
96
|
+
exception_msg=exception_desc(),
|
|
97
|
+
other_msg=f'Failed test for existence of SQLite table "{table_name}";',
|
|
98
|
+
) from e
|
|
99
|
+
rows = curs.fetchall()
|
|
100
100
|
return len(rows) > 0
|
|
101
101
|
|
|
102
102
|
def column_exists(
|
|
@@ -111,40 +111,40 @@ class SQLiteDatabase(Database):
|
|
|
111
111
|
|
|
112
112
|
def table_columns(self, table_name: str, schema_name: str | None = None) -> list[str]:
|
|
113
113
|
"""Return a list of column names for the given SQLite table."""
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
114
|
+
with self._cursor() as curs:
|
|
115
|
+
quoted_tbl = self.quote_identifier(table_name)
|
|
116
|
+
sql = f"select * from {quoted_tbl} where 1=0;"
|
|
117
|
+
try:
|
|
118
|
+
curs.execute(sql)
|
|
119
|
+
except ErrInfo:
|
|
120
|
+
raise
|
|
121
|
+
except Exception as e:
|
|
122
|
+
self.rollback()
|
|
123
|
+
raise ErrInfo(
|
|
124
|
+
type="db",
|
|
125
|
+
command_text=sql,
|
|
126
|
+
exception_msg=exception_desc(),
|
|
127
|
+
other_msg=f"Failed to get column names for table {table_name} of {self.name()}",
|
|
128
|
+
) from e
|
|
129
|
+
return [d[0] for d in curs.description]
|
|
130
130
|
|
|
131
131
|
def view_exists(self, view_name: str) -> bool:
|
|
132
132
|
"""Return True if the named view exists in the SQLite database."""
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
133
|
+
with self._cursor() as curs:
|
|
134
|
+
sql = "select name from sqlite_master where type='view' and name=?;"
|
|
135
|
+
try:
|
|
136
|
+
curs.execute(sql, (view_name,))
|
|
137
|
+
except ErrInfo:
|
|
138
|
+
raise
|
|
139
|
+
except Exception as e:
|
|
140
|
+
self.rollback()
|
|
141
|
+
raise ErrInfo(
|
|
142
|
+
type="db",
|
|
143
|
+
command_text=sql,
|
|
144
|
+
exception_msg=exception_desc(),
|
|
145
|
+
other_msg=f'Failed test for existence of SQLite view "{view_name}";',
|
|
146
|
+
) from e
|
|
147
|
+
rows = curs.fetchall()
|
|
148
148
|
return len(rows) > 0
|
|
149
149
|
|
|
150
150
|
def schema_exists(self, schema_name: str) -> bool:
|
|
@@ -151,8 +151,6 @@ class CondAstNode(CondTokens):
|
|
|
151
151
|
|
|
152
152
|
def eval(self) -> bool:
|
|
153
153
|
"""Evaluate this subtree and return a boolean result."""
|
|
154
|
-
# Evaluates the subtrees and/or conditional value for this node,
|
|
155
|
-
# returning True or False.
|
|
156
154
|
if self.type == self.CONDITIONAL:
|
|
157
155
|
exec_fn = self.left[0].exec_fn
|
|
158
156
|
cmdargs = self.left[1]
|
|
@@ -168,6 +166,7 @@ class CondAstNode(CondTokens):
|
|
|
168
166
|
if lcond:
|
|
169
167
|
return True
|
|
170
168
|
return self.right.eval()
|
|
169
|
+
raise CondParserError(f"Unknown conditional node type: {self.type}")
|
|
171
170
|
|
|
172
171
|
|
|
173
172
|
# AST for numeric expressions
|
|
@@ -200,6 +199,8 @@ class NumericAstNode(NumTokens):
|
|
|
200
199
|
if self.type == self.MUL:
|
|
201
200
|
return lnum * rnum
|
|
202
201
|
elif self.type == self.DIV:
|
|
202
|
+
if rnum == 0:
|
|
203
|
+
raise NumericParserError("Division by zero.")
|
|
203
204
|
return lnum / rnum
|
|
204
205
|
elif self.type == self.ADD:
|
|
205
206
|
return lnum + rnum
|
|
@@ -296,14 +296,24 @@ class DT_Boolean(DataType):
|
|
|
296
296
|
data_type_name = "boolean"
|
|
297
297
|
data_type = bool
|
|
298
298
|
|
|
299
|
+
def __init__(self) -> None:
|
|
300
|
+
"""Initialise with empty match caches; populated on first use."""
|
|
301
|
+
self.true: tuple = ()
|
|
302
|
+
self.false: tuple = ()
|
|
303
|
+
self.bool_repr: tuple = ()
|
|
304
|
+
self._cache_key: tuple | None = None
|
|
305
|
+
|
|
299
306
|
def __repr__(self) -> str:
|
|
300
307
|
return "DT_Boolean()"
|
|
301
308
|
|
|
302
|
-
def
|
|
303
|
-
"""
|
|
309
|
+
def _ensure_bool_matches(self) -> None:
|
|
310
|
+
"""Rebuild true/false match tuples only when the config has changed."""
|
|
304
311
|
import execsql.state as _state
|
|
305
312
|
|
|
306
313
|
conf = _state.conf
|
|
314
|
+
key = (conf.boolean_words, conf.boolean_int)
|
|
315
|
+
if key == self._cache_key:
|
|
316
|
+
return
|
|
307
317
|
self.true = ("yes", "true")
|
|
308
318
|
self.false = ("no", "false")
|
|
309
319
|
if not conf.boolean_words:
|
|
@@ -313,6 +323,7 @@ class DT_Boolean(DataType):
|
|
|
313
323
|
self.true += ("1",)
|
|
314
324
|
self.false += ("0",)
|
|
315
325
|
self.bool_repr = self.true + self.false
|
|
326
|
+
self._cache_key = key
|
|
316
327
|
|
|
317
328
|
def _is_match(self, data: object) -> bool:
|
|
318
329
|
import execsql.state as _state
|
|
@@ -320,7 +331,7 @@ class DT_Boolean(DataType):
|
|
|
320
331
|
conf = _state.conf
|
|
321
332
|
if data is None:
|
|
322
333
|
return False
|
|
323
|
-
self.
|
|
334
|
+
self._ensure_bool_matches()
|
|
324
335
|
return bool(
|
|
325
336
|
isinstance(data, bool)
|
|
326
337
|
or conf.boolean_int
|
|
@@ -336,7 +347,7 @@ class DT_Boolean(DataType):
|
|
|
336
347
|
conf = _state.conf
|
|
337
348
|
if data is None:
|
|
338
349
|
raise DataTypeError(self.data_type_name, self._CONV_ERR % "NULL")
|
|
339
|
-
self.
|
|
350
|
+
self._ensure_bool_matches()
|
|
340
351
|
if isinstance(data, bool):
|
|
341
352
|
return data
|
|
342
353
|
elif conf.boolean_int and type(data) is int and data in (0, 1):
|
|
@@ -352,6 +363,7 @@ class DT_Integer(DataType):
|
|
|
352
363
|
|
|
353
364
|
data_type_name = "integer"
|
|
354
365
|
data_type = int
|
|
366
|
+
_INT_RX = re.compile(r"^\s*[+-]?\d+\s*$")
|
|
355
367
|
|
|
356
368
|
def __repr__(self) -> str:
|
|
357
369
|
return "DT_Integer()"
|
|
@@ -367,7 +379,7 @@ class DT_Integer(DataType):
|
|
|
367
379
|
elif isinstance(data, str):
|
|
368
380
|
if leading_zero_num(data):
|
|
369
381
|
return False
|
|
370
|
-
if not
|
|
382
|
+
if not self._INT_RX.match(data):
|
|
371
383
|
return False
|
|
372
384
|
try:
|
|
373
385
|
i = int(data)
|
|
@@ -387,7 +399,7 @@ class DT_Integer(DataType):
|
|
|
387
399
|
return int(data)
|
|
388
400
|
else:
|
|
389
401
|
raise DataTypeError(self.data_type_name, self._CONV_ERR % data)
|
|
390
|
-
if isinstance(data, str) and not
|
|
402
|
+
if isinstance(data, str) and not self._INT_RX.match(data):
|
|
391
403
|
raise DataTypeError(self.data_type_name, self._CONV_ERR % data)
|
|
392
404
|
try:
|
|
393
405
|
i = int(data)
|
|
@@ -439,6 +451,7 @@ class DT_Float(DataType):
|
|
|
439
451
|
|
|
440
452
|
data_type_name = "float"
|
|
441
453
|
data_type = float
|
|
454
|
+
_FLOAT_RX = re.compile(r"^[+-]?(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?$")
|
|
442
455
|
|
|
443
456
|
def __repr__(self) -> str:
|
|
444
457
|
return "DT_Float()"
|
|
@@ -450,7 +463,7 @@ class DT_Float(DataType):
|
|
|
450
463
|
return True
|
|
451
464
|
if leading_zero_num(data):
|
|
452
465
|
return False
|
|
453
|
-
if isinstance(data, str) and not
|
|
466
|
+
if isinstance(data, str) and not self._FLOAT_RX.match(data):
|
|
454
467
|
return False
|
|
455
468
|
try:
|
|
456
469
|
float(data)
|
|
@@ -465,7 +478,7 @@ class DT_Float(DataType):
|
|
|
465
478
|
return data
|
|
466
479
|
if leading_zero_num(data):
|
|
467
480
|
raise DataTypeError(self.data_type_name, self._CONV_ERR % data)
|
|
468
|
-
if isinstance(data, str) and not
|
|
481
|
+
if isinstance(data, str) and not self._FLOAT_RX.match(data):
|
|
469
482
|
raise DataTypeError(self.data_type_name, self._CONV_ERR % data)
|
|
470
483
|
try:
|
|
471
484
|
i = float(data)
|
|
@@ -480,6 +493,7 @@ class DT_Decimal(DataType):
|
|
|
480
493
|
data_type_name = "decimal"
|
|
481
494
|
data_type = Decimal
|
|
482
495
|
precspec = True
|
|
496
|
+
_DECIMAL_RX = re.compile(r"^[+-]?(\d+(\.\d*)?|\.\d+)$")
|
|
483
497
|
|
|
484
498
|
def __repr__(self) -> str:
|
|
485
499
|
return "DT_Decimal()"
|
|
@@ -504,7 +518,7 @@ class DT_Decimal(DataType):
|
|
|
504
518
|
self.set_scale_prec(data)
|
|
505
519
|
return data
|
|
506
520
|
elif isinstance(data, str):
|
|
507
|
-
if not
|
|
521
|
+
if not self._DECIMAL_RX.match(data):
|
|
508
522
|
raise DataTypeError(self.data_type_name, self._CONV_ERR % data)
|
|
509
523
|
try:
|
|
510
524
|
dec = Decimal(data)
|
|
@@ -565,21 +579,20 @@ class DT_Varchar(DataType):
|
|
|
565
579
|
# This varchar data type is the same as the character data type.
|
|
566
580
|
if data is None:
|
|
567
581
|
raise DataTypeError(self.data_type_name, self._CONV_ERR % "NULL")
|
|
568
|
-
|
|
569
|
-
if isinstance(data, str):
|
|
582
|
+
if not isinstance(data, str):
|
|
570
583
|
try:
|
|
571
|
-
data =
|
|
584
|
+
data = str(data)
|
|
572
585
|
except ValueError as e:
|
|
573
586
|
raise DataTypeError(self.data_type_name, self._CONV_ERR % data) from e
|
|
574
|
-
|
|
575
|
-
|
|
587
|
+
if len(data) > 255:
|
|
588
|
+
raise DataTypeError(self.data_type_name, self._CONV_ERR % data)
|
|
576
589
|
return data
|
|
577
590
|
|
|
578
591
|
|
|
579
592
|
class DT_Text(DataType):
|
|
580
593
|
"""Unbounded text string data type."""
|
|
581
594
|
|
|
582
|
-
data_type_name = "
|
|
595
|
+
data_type_name = "text"
|
|
583
596
|
|
|
584
597
|
def __repr__(self) -> str:
|
|
585
598
|
return "DT_Text()"
|