execsql2 2.10.0__tar.gz → 2.11.0__tar.gz

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