execsql2 2.11.1__tar.gz → 2.12.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (301) hide show
  1. {execsql2-2.11.1 → execsql2-2.12.1}/.claude/project_context.md +3 -0
  2. {execsql2-2.11.1 → execsql2-2.12.1}/CHANGELOG.md +34 -0
  3. {execsql2-2.11.1 → execsql2-2.12.1}/CLAUDE.md +2 -2
  4. {execsql2-2.11.1 → execsql2-2.12.1}/PKG-INFO +1 -1
  5. {execsql2-2.11.1 → execsql2-2.12.1}/docs/about/divergence.md +48 -7
  6. {execsql2-2.11.1 → execsql2-2.12.1}/docs/guides/debugging.md +41 -0
  7. {execsql2-2.11.1 → execsql2-2.12.1}/pyproject.toml +3 -3
  8. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/cli/__init__.py +6 -0
  9. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/cli/run.py +15 -10
  10. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/db/base.py +37 -23
  11. execsql2-2.12.1/src/execsql/debug/__init__.py +6 -0
  12. execsql2-2.12.1/src/execsql/debug/repl.py +472 -0
  13. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/metacommands/__init__.py +1 -1
  14. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/metacommands/dispatch.py +1 -1
  15. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/script/engine.py +15 -6
  16. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/script/variables.py +2 -25
  17. {execsql2-2.11.1 → execsql2-2.12.1}/tests/cli/test_profile.py +52 -1
  18. {execsql2-2.11.1 → execsql2-2.12.1}/tests/db/test_base.py +5 -5
  19. {execsql2-2.11.1 → execsql2-2.12.1}/tests/gui/test_backends.py +422 -0
  20. execsql2-2.12.1/tests/importers/test_csv_importer.py +436 -0
  21. execsql2-2.12.1/tests/metacommands/test_breakpoint.py +980 -0
  22. execsql2-2.12.1/tests/metacommands/test_io_export.py +1377 -0
  23. execsql2-2.12.1/tests/metacommands/test_io_import.py +1585 -0
  24. execsql2-2.12.1/tests/metacommands/test_metacommands_data.py +802 -0
  25. {execsql2-2.11.1 → execsql2-2.12.1}/tests/metacommands/test_metacommands_extended.py +361 -0
  26. {execsql2-2.11.1 → execsql2-2.12.1}/tests/test_engine.py +3 -4
  27. {execsql2-2.11.1 → execsql2-2.12.1}/tests/test_script.py +12 -16
  28. {execsql2-2.11.1 → execsql2-2.12.1}/uv.lock +1 -1
  29. execsql2-2.11.1/src/execsql/metacommands/debug_repl.py +0 -289
  30. execsql2-2.11.1/tests/importers/test_csv_importer.py +0 -193
  31. execsql2-2.11.1/tests/metacommands/test_breakpoint.py +0 -524
  32. execsql2-2.11.1/tests/metacommands/test_metacommands_data.py +0 -381
  33. {execsql2-2.11.1 → execsql2-2.12.1}/.claude/agents/dba.md +0 -0
  34. {execsql2-2.11.1 → execsql2-2.12.1}/.claude/agents/herald.md +0 -0
  35. {execsql2-2.11.1 → execsql2-2.12.1}/.claude/agents/inspector.md +0 -0
  36. {execsql2-2.11.1 → execsql2-2.12.1}/.claude/agents/oracle.md +0 -0
  37. {execsql2-2.11.1 → execsql2-2.12.1}/.claude/agents/patcher.md +0 -0
  38. {execsql2-2.11.1 → execsql2-2.12.1}/.claude/agents/qa.md +0 -0
  39. {execsql2-2.11.1 → execsql2-2.12.1}/.claude/agents/scribe.md +0 -0
  40. {execsql2-2.11.1 → execsql2-2.12.1}/.claude/commands/code-oracle.md +0 -0
  41. {execsql2-2.11.1 → execsql2-2.12.1}/.claude/commands/migrate.md +0 -0
  42. {execsql2-2.11.1 → execsql2-2.12.1}/.claude/commands/review-changes.md +0 -0
  43. {execsql2-2.11.1 → execsql2-2.12.1}/.claude/commands/test-module.md +0 -0
  44. {execsql2-2.11.1 → execsql2-2.12.1}/.claude/commands/update-changelog.md +0 -0
  45. {execsql2-2.11.1 → execsql2-2.12.1}/.claude/commands/where-is.md +0 -0
  46. {execsql2-2.11.1 → execsql2-2.12.1}/.claude/state/status.md +0 -0
  47. {execsql2-2.11.1 → execsql2-2.12.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  48. {execsql2-2.11.1 → execsql2-2.12.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  49. {execsql2-2.11.1 → execsql2-2.12.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  50. {execsql2-2.11.1 → execsql2-2.12.1}/.github/workflows/ci-cd.yml +0 -0
  51. {execsql2-2.11.1 → execsql2-2.12.1}/.gitignore +0 -0
  52. {execsql2-2.11.1 → execsql2-2.12.1}/.pre-commit-config.yaml +0 -0
  53. {execsql2-2.11.1 → execsql2-2.12.1}/.pre-commit-hooks.yaml +0 -0
  54. {execsql2-2.11.1 → execsql2-2.12.1}/.python-version +0 -0
  55. {execsql2-2.11.1 → execsql2-2.12.1}/.readthedocs.yaml +0 -0
  56. {execsql2-2.11.1 → execsql2-2.12.1}/CONTRIBUTING.md +0 -0
  57. {execsql2-2.11.1 → execsql2-2.12.1}/LICENSE.txt +0 -0
  58. {execsql2-2.11.1 → execsql2-2.12.1}/NOTICE +0 -0
  59. {execsql2-2.11.1 → execsql2-2.12.1}/README.md +0 -0
  60. {execsql2-2.11.1 → execsql2-2.12.1}/SECURITY.md +0 -0
  61. {execsql2-2.11.1 → execsql2-2.12.1}/docs/about/contributors.md +0 -0
  62. {execsql2-2.11.1 → execsql2-2.12.1}/docs/about/copyright.md +0 -0
  63. {execsql2-2.11.1 → execsql2-2.12.1}/docs/api/cli.md +0 -0
  64. {execsql2-2.11.1 → execsql2-2.12.1}/docs/api/db.md +0 -0
  65. {execsql2-2.11.1 → execsql2-2.12.1}/docs/api/exporters.md +0 -0
  66. {execsql2-2.11.1 → execsql2-2.12.1}/docs/api/importers.md +0 -0
  67. {execsql2-2.11.1 → execsql2-2.12.1}/docs/api/index.md +0 -0
  68. {execsql2-2.11.1 → execsql2-2.12.1}/docs/api/metacommands.md +0 -0
  69. {execsql2-2.11.1 → execsql2-2.12.1}/docs/dev/adding_db_adapters.md +0 -0
  70. {execsql2-2.11.1 → execsql2-2.12.1}/docs/dev/adding_exporters.md +0 -0
  71. {execsql2-2.11.1 → execsql2-2.12.1}/docs/dev/adding_importers.md +0 -0
  72. {execsql2-2.11.1 → execsql2-2.12.1}/docs/dev/adding_metacommands.md +0 -0
  73. {execsql2-2.11.1 → execsql2-2.12.1}/docs/dev/architecture.md +0 -0
  74. {execsql2-2.11.1 → execsql2-2.12.1}/docs/getting-started/installation.md +0 -0
  75. {execsql2-2.11.1 → execsql2-2.12.1}/docs/getting-started/requirements.md +0 -0
  76. {execsql2-2.11.1 → execsql2-2.12.1}/docs/getting-started/syntax.md +0 -0
  77. {execsql2-2.11.1 → execsql2-2.12.1}/docs/guides/documentation.md +0 -0
  78. {execsql2-2.11.1 → execsql2-2.12.1}/docs/guides/encoding.md +0 -0
  79. {execsql2-2.11.1 → execsql2-2.12.1}/docs/guides/examples.md +0 -0
  80. {execsql2-2.11.1 → execsql2-2.12.1}/docs/guides/formatter.md +0 -0
  81. {execsql2-2.11.1 → execsql2-2.12.1}/docs/guides/logging.md +0 -0
  82. {execsql2-2.11.1 → execsql2-2.12.1}/docs/guides/sql_syntax.md +0 -0
  83. {execsql2-2.11.1 → execsql2-2.12.1}/docs/guides/usage.md +0 -0
  84. {execsql2-2.11.1 → execsql2-2.12.1}/docs/guides/using_scripts.md +0 -0
  85. {execsql2-2.11.1 → execsql2-2.12.1}/docs/images/Compare_planets.png +0 -0
  86. {execsql2-2.11.1 → execsql2-2.12.1}/docs/images/actions.png +0 -0
  87. {execsql2-2.11.1 → execsql2-2.12.1}/docs/images/actions2.png +0 -0
  88. {execsql2-2.11.1 → execsql2-2.12.1}/docs/images/checkboxes.png +0 -0
  89. {execsql2-2.11.1 → execsql2-2.12.1}/docs/images/connect.b64 +0 -0
  90. {execsql2-2.11.1 → execsql2-2.12.1}/docs/images/connect.png +0 -0
  91. {execsql2-2.11.1 → execsql2-2.12.1}/docs/images/create_conf.png +0 -0
  92. {execsql2-2.11.1 → execsql2-2.12.1}/docs/images/data_error1_screenshot.jpg +0 -0
  93. {execsql2-2.11.1 → execsql2-2.12.1}/docs/images/entry_form.png +0 -0
  94. {execsql2-2.11.1 → execsql2-2.12.1}/docs/images/execsql_console.png +0 -0
  95. {execsql2-2.11.1 → execsql2-2.12.1}/docs/images/execsql_logo_01.png +0 -0
  96. {execsql2-2.11.1 → execsql2-2.12.1}/docs/images/fatals.png +0 -0
  97. {execsql2-2.11.1 → execsql2-2.12.1}/docs/images/logo_small.png +0 -0
  98. {execsql2-2.11.1 → execsql2-2.12.1}/docs/images/pause_terminal.png +0 -0
  99. {execsql2-2.11.1 → execsql2-2.12.1}/docs/images/pause_terminal_sm.b64 +0 -0
  100. {execsql2-2.11.1 → execsql2-2.12.1}/docs/images/pause_terminal_sm.png +0 -0
  101. {execsql2-2.11.1 → execsql2-2.12.1}/docs/images/prompt_compare.png +0 -0
  102. {execsql2-2.11.1 → execsql2-2.12.1}/docs/images/set_build_commands.jpg +0 -0
  103. {execsql2-2.11.1 → execsql2-2.12.1}/docs/images/unit_conversions.b64 +0 -0
  104. {execsql2-2.11.1 → execsql2-2.12.1}/docs/images/unit_conversions_029.png +0 -0
  105. {execsql2-2.11.1 → execsql2-2.12.1}/docs/images/unmatched.png +0 -0
  106. {execsql2-2.11.1 → execsql2-2.12.1}/docs/images/vim_execsql_highlight.png +0 -0
  107. {execsql2-2.11.1 → execsql2-2.12.1}/docs/index.md +0 -0
  108. {execsql2-2.11.1 → execsql2-2.12.1}/docs/reference/configuration.md +0 -0
  109. {execsql2-2.11.1 → execsql2-2.12.1}/docs/reference/metacommands.md +0 -0
  110. {execsql2-2.11.1 → execsql2-2.12.1}/docs/reference/security.md +0 -0
  111. {execsql2-2.11.1 → execsql2-2.12.1}/docs/reference/substitution_vars.md +0 -0
  112. {execsql2-2.11.1 → execsql2-2.12.1}/extras/vscode-execsql/README.md +0 -0
  113. {execsql2-2.11.1 → execsql2-2.12.1}/extras/vscode-execsql/package.json +0 -0
  114. {execsql2-2.11.1 → execsql2-2.12.1}/extras/vscode-execsql/syntaxes/execsql.tmLanguage.json +0 -0
  115. {execsql2-2.11.1 → execsql2-2.12.1}/justfile +0 -0
  116. {execsql2-2.11.1 → execsql2-2.12.1}/scripts/generate_vscode_grammar.py +0 -0
  117. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/__init__.py +0 -0
  118. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/__main__.py +0 -0
  119. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/cli/dsn.py +0 -0
  120. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/cli/help.py +0 -0
  121. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/cli/lint.py +0 -0
  122. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/config.py +0 -0
  123. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/constants.py +0 -0
  124. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/db/__init__.py +0 -0
  125. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/db/access.py +0 -0
  126. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/db/dsn.py +0 -0
  127. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/db/duckdb.py +0 -0
  128. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/db/factory.py +0 -0
  129. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/db/firebird.py +0 -0
  130. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/db/mysql.py +0 -0
  131. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/db/oracle.py +0 -0
  132. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/db/postgres.py +0 -0
  133. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/db/sqlite.py +0 -0
  134. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/db/sqlserver.py +0 -0
  135. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/exceptions.py +0 -0
  136. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/exporters/__init__.py +0 -0
  137. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/exporters/base.py +0 -0
  138. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/exporters/delimited.py +0 -0
  139. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/exporters/duckdb.py +0 -0
  140. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/exporters/feather.py +0 -0
  141. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/exporters/html.py +0 -0
  142. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/exporters/json.py +0 -0
  143. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/exporters/latex.py +0 -0
  144. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/exporters/markdown.py +0 -0
  145. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/exporters/ods.py +0 -0
  146. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/exporters/parquet.py +0 -0
  147. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/exporters/pretty.py +0 -0
  148. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/exporters/protocol.py +0 -0
  149. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/exporters/raw.py +0 -0
  150. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/exporters/sqlite.py +0 -0
  151. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/exporters/templates.py +0 -0
  152. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/exporters/values.py +0 -0
  153. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/exporters/xls.py +0 -0
  154. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/exporters/xlsx.py +0 -0
  155. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/exporters/xml.py +0 -0
  156. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/exporters/yaml.py +0 -0
  157. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/exporters/zip.py +0 -0
  158. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/format.py +0 -0
  159. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/gui/__init__.py +0 -0
  160. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/gui/base.py +0 -0
  161. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/gui/console.py +0 -0
  162. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/gui/desktop.py +0 -0
  163. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/gui/tui.py +0 -0
  164. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/importers/__init__.py +0 -0
  165. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/importers/base.py +0 -0
  166. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/importers/csv.py +0 -0
  167. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/importers/feather.py +0 -0
  168. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/importers/ods.py +0 -0
  169. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/importers/xls.py +0 -0
  170. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/metacommands/conditions.py +0 -0
  171. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/metacommands/connect.py +0 -0
  172. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/metacommands/control.py +0 -0
  173. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/metacommands/data.py +0 -0
  174. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/metacommands/debug.py +0 -0
  175. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/metacommands/io.py +0 -0
  176. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/metacommands/io_export.py +0 -0
  177. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/metacommands/io_fileops.py +0 -0
  178. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/metacommands/io_import.py +0 -0
  179. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/metacommands/io_write.py +0 -0
  180. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/metacommands/prompt.py +0 -0
  181. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/metacommands/script_ext.py +0 -0
  182. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/metacommands/system.py +0 -0
  183. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/models.py +0 -0
  184. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/parser.py +0 -0
  185. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/py.typed +0 -0
  186. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/script/__init__.py +0 -0
  187. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/script/control.py +0 -0
  188. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/state.py +0 -0
  189. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/types.py +0 -0
  190. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/utils/__init__.py +0 -0
  191. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/utils/auth.py +0 -0
  192. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/utils/crypto.py +0 -0
  193. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/utils/datetime.py +0 -0
  194. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/utils/errors.py +0 -0
  195. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/utils/fileio.py +0 -0
  196. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/utils/gui.py +0 -0
  197. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/utils/mail.py +0 -0
  198. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/utils/numeric.py +0 -0
  199. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/utils/regex.py +0 -0
  200. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/utils/strings.py +0 -0
  201. {execsql2-2.11.1 → execsql2-2.12.1}/src/execsql/utils/timer.py +0 -0
  202. {execsql2-2.11.1 → execsql2-2.12.1}/templates/README.md +0 -0
  203. {execsql2-2.11.1 → execsql2-2.12.1}/templates/config_settings.sqlite +0 -0
  204. {execsql2-2.11.1 → execsql2-2.12.1}/templates/example_config_prompt.sql +0 -0
  205. {execsql2-2.11.1 → execsql2-2.12.1}/templates/execsql.conf +0 -0
  206. {execsql2-2.11.1 → execsql2-2.12.1}/templates/make_config_db.sql +0 -0
  207. {execsql2-2.11.1 → execsql2-2.12.1}/templates/md_compare.sql +0 -0
  208. {execsql2-2.11.1 → execsql2-2.12.1}/templates/md_glossary.sql +0 -0
  209. {execsql2-2.11.1 → execsql2-2.12.1}/templates/md_upsert.sql +0 -0
  210. {execsql2-2.11.1 → execsql2-2.12.1}/templates/pg_compare.sql +0 -0
  211. {execsql2-2.11.1 → execsql2-2.12.1}/templates/pg_glossary.sql +0 -0
  212. {execsql2-2.11.1 → execsql2-2.12.1}/templates/pg_upsert.sql +0 -0
  213. {execsql2-2.11.1 → execsql2-2.12.1}/templates/script_template.sql +0 -0
  214. {execsql2-2.11.1 → execsql2-2.12.1}/templates/ss_compare.sql +0 -0
  215. {execsql2-2.11.1 → execsql2-2.12.1}/templates/ss_glossary.sql +0 -0
  216. {execsql2-2.11.1 → execsql2-2.12.1}/templates/ss_upsert.sql +0 -0
  217. {execsql2-2.11.1 → execsql2-2.12.1}/tests/__init__.py +0 -0
  218. {execsql2-2.11.1 → execsql2-2.12.1}/tests/cli/__init__.py +0 -0
  219. {execsql2-2.11.1 → execsql2-2.12.1}/tests/cli/test_cli.py +0 -0
  220. {execsql2-2.11.1 → execsql2-2.12.1}/tests/cli/test_cli_e2e.py +0 -0
  221. {execsql2-2.11.1 → execsql2-2.12.1}/tests/cli/test_cli_run.py +0 -0
  222. {execsql2-2.11.1 → execsql2-2.12.1}/tests/cli/test_lint.py +0 -0
  223. {execsql2-2.11.1 → execsql2-2.12.1}/tests/cli/test_ping.py +0 -0
  224. {execsql2-2.11.1 → execsql2-2.12.1}/tests/conftest.py +0 -0
  225. {execsql2-2.11.1 → execsql2-2.12.1}/tests/db/__init__.py +0 -0
  226. {execsql2-2.11.1 → execsql2-2.12.1}/tests/db/test_duckdb.py +0 -0
  227. {execsql2-2.11.1 → execsql2-2.12.1}/tests/db/test_factory.py +0 -0
  228. {execsql2-2.11.1 → execsql2-2.12.1}/tests/db/test_postgres.py +0 -0
  229. {execsql2-2.11.1 → execsql2-2.12.1}/tests/db/test_sqlite.py +0 -0
  230. {execsql2-2.11.1 → execsql2-2.12.1}/tests/db/test_sqlite_extra.py +0 -0
  231. {execsql2-2.11.1 → execsql2-2.12.1}/tests/exporters/__init__.py +0 -0
  232. {execsql2-2.11.1 → execsql2-2.12.1}/tests/exporters/test_base.py +0 -0
  233. {execsql2-2.11.1 → execsql2-2.12.1}/tests/exporters/test_db.py +0 -0
  234. {execsql2-2.11.1 → execsql2-2.12.1}/tests/exporters/test_delimited.py +0 -0
  235. {execsql2-2.11.1 → execsql2-2.12.1}/tests/exporters/test_duckdb_exporter.py +0 -0
  236. {execsql2-2.11.1 → execsql2-2.12.1}/tests/exporters/test_exporters.py +0 -0
  237. {execsql2-2.11.1 → execsql2-2.12.1}/tests/exporters/test_feather.py +0 -0
  238. {execsql2-2.11.1 → execsql2-2.12.1}/tests/exporters/test_html_latex.py +0 -0
  239. {execsql2-2.11.1 → execsql2-2.12.1}/tests/exporters/test_json.py +0 -0
  240. {execsql2-2.11.1 → execsql2-2.12.1}/tests/exporters/test_markdown.py +0 -0
  241. {execsql2-2.11.1 → execsql2-2.12.1}/tests/exporters/test_ods.py +0 -0
  242. {execsql2-2.11.1 → execsql2-2.12.1}/tests/exporters/test_parquet.py +0 -0
  243. {execsql2-2.11.1 → execsql2-2.12.1}/tests/exporters/test_sqlite_exporter.py +0 -0
  244. {execsql2-2.11.1 → execsql2-2.12.1}/tests/exporters/test_templates.py +0 -0
  245. {execsql2-2.11.1 → execsql2-2.12.1}/tests/exporters/test_xls_xlsx.py +0 -0
  246. {execsql2-2.11.1 → execsql2-2.12.1}/tests/exporters/test_xlsx.py +0 -0
  247. {execsql2-2.11.1 → execsql2-2.12.1}/tests/exporters/test_xml.py +0 -0
  248. {execsql2-2.11.1 → execsql2-2.12.1}/tests/exporters/test_yaml.py +0 -0
  249. {execsql2-2.11.1 → execsql2-2.12.1}/tests/exporters/test_zip.py +0 -0
  250. {execsql2-2.11.1 → execsql2-2.12.1}/tests/gui/__init__.py +0 -0
  251. {execsql2-2.11.1 → execsql2-2.12.1}/tests/importers/__init__.py +0 -0
  252. {execsql2-2.11.1 → execsql2-2.12.1}/tests/importers/test_feather_importer.py +0 -0
  253. {execsql2-2.11.1 → execsql2-2.12.1}/tests/importers/test_ods_importer.py +0 -0
  254. {execsql2-2.11.1 → execsql2-2.12.1}/tests/importers/test_xls_importer.py +0 -0
  255. {execsql2-2.11.1 → execsql2-2.12.1}/tests/integration/__init__.py +0 -0
  256. {execsql2-2.11.1 → execsql2-2.12.1}/tests/integration/conftest.py +0 -0
  257. {execsql2-2.11.1 → execsql2-2.12.1}/tests/integration/test_dsn.py +0 -0
  258. {execsql2-2.11.1 → execsql2-2.12.1}/tests/integration/test_duckdb.py +0 -0
  259. {execsql2-2.11.1 → execsql2-2.12.1}/tests/integration/test_mysql.py +0 -0
  260. {execsql2-2.11.1 → execsql2-2.12.1}/tests/integration/test_postgres.py +0 -0
  261. {execsql2-2.11.1 → execsql2-2.12.1}/tests/integration/test_sqlite.py +0 -0
  262. {execsql2-2.11.1 → execsql2-2.12.1}/tests/metacommands/__init__.py +0 -0
  263. {execsql2-2.11.1 → execsql2-2.12.1}/tests/metacommands/test_assert.py +0 -0
  264. {execsql2-2.11.1 → execsql2-2.12.1}/tests/metacommands/test_connect.py +0 -0
  265. {execsql2-2.11.1 → execsql2-2.12.1}/tests/metacommands/test_metacommands.py +0 -0
  266. {execsql2-2.11.1 → execsql2-2.12.1}/tests/metacommands/test_metacommands_connect.py +0 -0
  267. {execsql2-2.11.1 → execsql2-2.12.1}/tests/metacommands/test_metacommands_fileops_extra.py +0 -0
  268. {execsql2-2.11.1 → execsql2-2.12.1}/tests/metacommands/test_metacommands_io.py +0 -0
  269. {execsql2-2.11.1 → execsql2-2.12.1}/tests/metacommands/test_metacommands_io_write_extra.py +0 -0
  270. {execsql2-2.11.1 → execsql2-2.12.1}/tests/metacommands/test_metacommands_script_ext.py +0 -0
  271. {execsql2-2.11.1 → execsql2-2.12.1}/tests/metacommands/test_metacommands_system.py +0 -0
  272. {execsql2-2.11.1 → execsql2-2.12.1}/tests/metacommands/test_metacommands_system_extra.py +0 -0
  273. {execsql2-2.11.1 → execsql2-2.12.1}/tests/metacommands/test_row_count.py +0 -0
  274. {execsql2-2.11.1 → execsql2-2.12.1}/tests/test_config.py +0 -0
  275. {execsql2-2.11.1 → execsql2-2.12.1}/tests/test_config_data.py +0 -0
  276. {execsql2-2.11.1 → execsql2-2.12.1}/tests/test_constants.py +0 -0
  277. {execsql2-2.11.1 → execsql2-2.12.1}/tests/test_error_messages.py +0 -0
  278. {execsql2-2.11.1 → execsql2-2.12.1}/tests/test_exceptions.py +0 -0
  279. {execsql2-2.11.1 → execsql2-2.12.1}/tests/test_format.py +0 -0
  280. {execsql2-2.11.1 → execsql2-2.12.1}/tests/test_mail.py +0 -0
  281. {execsql2-2.11.1 → execsql2-2.12.1}/tests/test_models.py +0 -0
  282. {execsql2-2.11.1 → execsql2-2.12.1}/tests/test_package.py +0 -0
  283. {execsql2-2.11.1 → execsql2-2.12.1}/tests/test_parser.py +0 -0
  284. {execsql2-2.11.1 → execsql2-2.12.1}/tests/test_registry.py +0 -0
  285. {execsql2-2.11.1 → execsql2-2.12.1}/tests/test_state.py +0 -0
  286. {execsql2-2.11.1 → execsql2-2.12.1}/tests/test_types.py +0 -0
  287. {execsql2-2.11.1 → execsql2-2.12.1}/tests/utils/__init__.py +0 -0
  288. {execsql2-2.11.1 → execsql2-2.12.1}/tests/utils/test_auth.py +0 -0
  289. {execsql2-2.11.1 → execsql2-2.12.1}/tests/utils/test_auth_extra.py +0 -0
  290. {execsql2-2.11.1 → execsql2-2.12.1}/tests/utils/test_crypto.py +0 -0
  291. {execsql2-2.11.1 → execsql2-2.12.1}/tests/utils/test_datetime.py +0 -0
  292. {execsql2-2.11.1 → execsql2-2.12.1}/tests/utils/test_errors.py +0 -0
  293. {execsql2-2.11.1 → execsql2-2.12.1}/tests/utils/test_errors_extra.py +0 -0
  294. {execsql2-2.11.1 → execsql2-2.12.1}/tests/utils/test_fileio.py +0 -0
  295. {execsql2-2.11.1 → execsql2-2.12.1}/tests/utils/test_fileio_extra.py +0 -0
  296. {execsql2-2.11.1 → execsql2-2.12.1}/tests/utils/test_numeric.py +0 -0
  297. {execsql2-2.11.1 → execsql2-2.12.1}/tests/utils/test_regex.py +0 -0
  298. {execsql2-2.11.1 → execsql2-2.12.1}/tests/utils/test_strings.py +0 -0
  299. {execsql2-2.11.1 → execsql2-2.12.1}/tests/utils/test_timer.py +0 -0
  300. {execsql2-2.11.1 → execsql2-2.12.1}/tests/utils/test_timer_extra.py +0 -0
  301. {execsql2-2.11.1 → execsql2-2.12.1}/zensical.toml +0 -0
@@ -67,6 +67,9 @@ src/execsql/ # active codebase — all new work goes here
67
67
  importers/
68
68
  __init__.py
69
69
  base.py / csv.py / ods.py / xls.py / feather.py
70
+ debug/
71
+ __init__.py # debug package
72
+ repl.py # x_breakpoint, _debug_repl, REPL helpers (BREAKPOINT metacommand)
70
73
  metacommands/
71
74
  __init__.py # DISPATCH_TABLE, format/db constants, re-exports all handlers
72
75
  dispatch.py # build_dispatch_table() — all mcl.add() regex registrations
@@ -13,6 +13,40 @@ ______________________________________________________________________
13
13
 
14
14
  ______________________________________________________________________
15
15
 
16
+ ## [2.12.1] - 2026-04-02
17
+
18
+ ### Changed
19
+
20
+ - Performance: removed dead `_compiled_patterns` dict from `SubVarSet` — eliminated 3 unused regex compilations per `add_substitution` call (~20 calls per statement in typical scripts).
21
+ - Performance: cached `source_dir` and `source_name` on `ScriptCmd` at construction time — eliminated per-statement `Path.resolve()` filesystem calls.
22
+ - Performance: `select_rowdict()` now uses batched `fetchmany()` instead of row-at-a-time `fetchone()`, matching `select_rowsource()` behavior for template exports.
23
+ - Performance: removed redundant `$CURRENT_TIME` set in `set_system_vars()` — now set once per statement in `run_and_increment()`.
24
+ - Performance: removed no-op `copy.copy()` on immutable string in `substitute_vars()`.
25
+
26
+ ### Fixed
27
+
28
+ - Fixed cursor leak in `select_rowsource()` — generator now closes the cursor in a `finally` block when exhausted or abandoned.
29
+
30
+ ______________________________________________________________________
31
+
32
+ ## [2.12.0] - 2026-04-01
33
+
34
+ ### Added
35
+
36
+ - Debug REPL `.where` / `.w` command — shows current script file, line number, and the upcoming statement text (truncated to 120 chars). The entry banner now includes the location (`[Breakpoint] myscript.sql:42`) and `_print_where()` is called automatically on REPL entry via `BREAKPOINT` or step mode.
37
+ - Debug REPL `.set VAR VAL` / `.s VAR VAL` command — sets or updates a substitution variable interactively during a `BREAKPOINT` session. Prints a confirmation line (`VAR = VAL`) on success; prints an error if substitution variables are not initialised.
38
+ - Debug REPL ANSI color output — horizontal rule separators, colored labels ("Breakpoint"/"Step" in bold yellow, filename:line in cyan, type tags in dim green), cyan variable names, dim `=` signs, red error messages, bold SQL column headers, and dim row-count and table borders. Color is auto-detected via TTY and suppressed when `NO_COLOR` or `EXECSQL_NO_COLOR` environment variables are set. Falls back to plain text in non-interactive contexts (CI, piped output). Help text is also colorized with cyan command names and consistent column alignment.
39
+ - Debug REPL shortcut aliases — `.h` for `.help`, `.v` for `.vars`, `.v all` for `.vars all`.
40
+ - Debug REPL step mode banner — when the REPL is re-entered via `.next` / step mode, the entry banner now shows "Step" instead of "Breakpoint" to make it clear the pause is from stepping rather than an explicit `BREAKPOINT` metacommand.
41
+ - `--profile-limit N` CLI option — controls how many top statements appear in the `--profile` timing summary (default: 20). The "not shown" footer message now includes the active limit for clarity.
42
+ - Test coverage raised from 86% to 91% — 274 new tests across `metacommands/io_import.py`, `metacommands/io_export.py`, `metacommands/control.py`, `metacommands/data.py`, `importers/csv.py`, and `gui/console.py`. Coverage floor raised from 85% to 90% in `pyproject.toml`.
43
+
44
+ ### Changed
45
+
46
+ - `execsql.debug.repl` is now a dedicated package (`src/execsql/debug/repl.py`); previously the REPL lived at `execsql.metacommands.debug_repl`. Internal import paths have been updated throughout. No public API change.
47
+
48
+ ______________________________________________________________________
49
+
16
50
  ## [2.11.1] - 2026-04-01
17
51
 
18
52
  ### Fixed
@@ -37,13 +37,13 @@ A multi-agent system where specialized agents collaborate to improve, extend, de
37
37
  1. **Research** — Oracle investigates codebase, finds relevant code paths and impact
38
38
  1. **Plan** — DBA synthesizes research into implementation approach, aligns with human
39
39
  1. **Implement** — Patcher writes code, Oracle advises on architecture
40
- 1. **Test** — QA writes/runs tests, verifies coverage stays above 80%
40
+ 1. **Test** — QA writes/runs tests, verifies coverage stays above 90%
41
41
  1. **Document** — Scribe updates docs, Herald updates changelog
42
42
  1. **Review** — Inspector does final code review before human merge
43
43
 
44
44
  ## Constraints
45
45
 
46
- - Coverage floor (80%) must be maintained — QA blocks any change that drops it
46
+ - Coverage floor (90%) must be maintained — QA blocks any change that drops it
47
47
  - Backwards compatibility with upstream execsql v1.130.1 unless explicitly approved
48
48
  - No destructive git operations without human approval
49
49
  - Agents should always read `.claude/project_context.md` before starting work
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: execsql2
3
- Version: 2.11.1
3
+ Version: 2.12.1
4
4
  Summary: Runs a SQL script against a PostgreSQL, SQLite, MariaDB/MySQL, DuckDB, Firebird, MS-Access, MS-SQL-Server, or Oracle database, or an ODBC DSN. Provides metacommands to import and export data, copy data between databases, conditionally execute SQL and metacommands, and dynamically alter SQL and metacommands with substitution variables.
5
5
  Project-URL: Repository, https://github.com/geocoug/execsql
6
6
  Project-URL: Issues, https://github.com/geocoug/execsql/issues
@@ -24,7 +24,8 @@ ______________________________________________________________________
24
24
  | `--gui-framework` | Select GUI backend: `tkinter` (default) or `textual` (terminal UI). |
25
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. |
26
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. |
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
+ | `--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. |
28
+ | `--profile-limit N` | Number of top statements to display in the `--profile` summary (default: 20). Remaining statements are counted and noted in the output footer. |
28
29
  | `--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. |
29
30
  | `--lint` | Parse the script and perform static analysis without connecting to a database. Reports unmatched IF/ENDIF, LOOP/END LOOP, and BEGIN BATCH/END BATCH blocks (errors), potentially undefined `!!$VAR!!` references (warnings), and missing INCLUDE file targets (warnings). Exits 0 if no errors, 1 if errors found. |
30
31
 
@@ -40,12 +41,12 @@ ______________________________________________________________________
40
41
 
41
42
  ### Metacommands
42
43
 
43
- | Metacommand | Description |
44
- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
45
- | `ASSERT` | Evaluate a condition and raise an error (halting the script) if it is false. Supports all IF conditions. Optional quoted failure message. Skipped in false IF blocks. |
46
- | `BREAKPOINT` | Pause script execution and drop into an interactive debug REPL. Inspect variables, run ad-hoc SQL, and step through the script. Silently skipped in non-TTY (CI) environments. |
47
- | `CONFIG SHOW_PROGRESS` | Enable the Rich progress bar for IMPORT operations at runtime. |
48
- | `CONFIG LOG_SQL` | Enable SQL query audit logging — writes executed SQL to the log file. |
44
+ | Metacommand | Description |
45
+ | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
46
+ | `ASSERT` | Evaluate a condition and raise an error (halting the script) if it is false. Supports all IF conditions. Optional quoted failure message. Skipped in false IF blocks. |
47
+ | `BREAKPOINT` | Pause script execution and drop into an interactive debug REPL. See [Debugging](#debugging) below for full details. |
48
+ | `CONFIG SHOW_PROGRESS` | Enable the Rich progress bar for IMPORT operations at runtime. |
49
+ | `CONFIG LOG_SQL` | Enable SQL query audit logging — writes executed SQL to the log file. |
49
50
 
50
51
  ### Conditional Tests
51
52
 
@@ -106,6 +107,46 @@ New options in `execsql.conf`:
106
107
  | `py.typed` marker | PEP 561 marker enabling downstream static type checking. |
107
108
  | Structured keyword registry | `--dump-keywords` introspects the dispatch table and outputs JSON used by the grammar generator and test suite. |
108
109
 
110
+ ### Debugging { #debugging }
111
+
112
+ execsql2 adds a full interactive debugging system that has no equivalent in upstream execsql.
113
+
114
+ **`BREAKPOINT` metacommand** — insert `-- !x! BREAKPOINT` anywhere in a script to pause execution and drop into a debug REPL. The REPL provides a `execsql debug>` prompt where you can inspect state and interact with the database before resuming.
115
+
116
+ **`--debug` CLI flag** — start the script in step-through mode, pausing before every statement as if `BREAKPOINT` were inserted at the top with `.next` always active.
117
+
118
+ **REPL commands** (all dot-prefixed to avoid collisions with variable names and SQL):
119
+
120
+ | Command | Description |
121
+ | ---------------------- | ------------------------------------------------------------------------- |
122
+ | `.continue` / `.c` | Resume normal script execution |
123
+ | `.abort` / `.q` | Halt the script with exit status 1 |
124
+ | `.vars` / `.v` | List all user, system, local, and counter variables (grouped by type) |
125
+ | `.vars all` / `.v all` | Include environment variables (`&`) in the listing |
126
+ | `.next` / `.n` | Execute the next statement, then pause again (step mode) |
127
+ | `.where` / `.w` | Show the current script file, line number, and upcoming statement text |
128
+ | `.stack` | Show the command-list stack (script name, cursor position, nesting depth) |
129
+ | `.set VAR VAL` / `.s` | Set or update a substitution variable; prints confirmation on success |
130
+ | `.help` / `.h` | Show available commands |
131
+
132
+ **Non-prefixed input** is interpreted as either a variable lookup or ad-hoc SQL:
133
+
134
+ | Input | Behavior |
135
+ | ------------------------------ | -------------------------------------------------------------------------------- |
136
+ | `logfile` | Print the value of the `logfile` substitution variable |
137
+ | `$ARG_1` | Print the value of a system/built-in variable |
138
+ | `SELECT count(*) FROM orders;` | Execute SQL against the current database and pretty-print the results in a table |
139
+
140
+ **Key behaviors:**
141
+
142
+ - **Non-interactive safety** — `BREAKPOINT` is silently skipped when `sys.stdin` is not a TTY (CI pipelines, piped input, cron). Scripts never hang in automation.
143
+ - **Ad-hoc SQL** — any input ending with `;` is executed against the current database connection using the same cursor and transaction state as the script. Queries return a formatted table; DML statements (INSERT, UPDATE, DELETE) execute and commit/rollback according to the current autocommit and batch settings.
144
+ - **Variable inspection** — bare names look up user-defined variables (e.g., `logfile`); sigil-prefixed names look up system (`$`), environment (`&`), local (`~`), or counter (`@`) variables. If a `$`-prefixed name isn't found, the REPL strips the sigil and retries (since `SUB` stores keys without a prefix).
145
+ - **Step mode** — `.next` executes exactly one statement then re-enters the REPL. When stepping, the entry banner shows "Step" instead of "Breakpoint" to distinguish stepping from an explicit `BREAKPOINT`. Combined with `.where`, `.vars`, and SQL queries, this allows line-by-line script debugging with full state visibility.
146
+ - **Location display** — on entry to the REPL (via `BREAKPOINT` or step mode) the banner shows a horizontal rule with the label ("Breakpoint" or "Step"), the current filename and line number, and the upcoming statement. Use `.where` (or `.w`) at any time to re-display this information.
147
+ - **ANSI color output** — the REPL uses ANSI color on TTY outputs: bold yellow for section labels, cyan for filenames and variable names, dim for separators and `=` signs, red for error messages, bold for SQL column headers, and dim italic for `NULL` values. Color is suppressed when `NO_COLOR` or `EXECSQL_NO_COLOR` environment variables are set, or when the output stream is not a TTY.
148
+ - **Readline support** — on platforms where `readline` is available (macOS, Linux), the REPL supports arrow-key history navigation and line editing.
149
+
109
150
  ______________________________________________________________________
110
151
 
111
152
  ## Changed Behavior
@@ -38,6 +38,47 @@ DEBUG WRITE <script_name> [[APPEND] TO <filename>]
38
38
 
39
39
  This is an alias for the [WRITE SCRIPT](../reference/metacommands.md#write_script) metacommand.
40
40
 
41
+ # Interactive Debug REPL (BREAKPOINT)
42
+
43
+ Insert `-- !x! BREAKPOINT` anywhere in a script to pause execution and drop into the interactive debug REPL:
44
+
45
+ ```sql
46
+ -- !x! BREAKPOINT
47
+ SELECT * FROM orders WHERE status = 'pending';
48
+ ```
49
+
50
+ The REPL prints the current file name, line number, and the upcoming statement when it opens:
51
+
52
+ ```
53
+ [Breakpoint] myscript.sql:42 — Script paused. Type '.help' for commands, '.c' to resume.
54
+ myscript.sql:42 (sql)
55
+ → SELECT * FROM orders WHERE status = 'pending';
56
+ execsql debug>
57
+ ```
58
+
59
+ **Available commands:**
60
+
61
+ | Command | Shortcut | Description |
62
+ | -------------- | -------- | ------------------------------------------------------------------------- |
63
+ | `.continue` | `.c` | Resume normal script execution |
64
+ | `.abort` | `.q` | Halt the script with exit status 1 |
65
+ | `.vars` | `.v` | List user, system, local, and counter substitution variables |
66
+ | `.vars all` | `.v all` | Include environment variables (`&`) in the listing |
67
+ | `.next` | `.n` | Execute the next statement, then pause again (step mode) |
68
+ | `.where` | `.w` | Re-display the current script location and upcoming statement |
69
+ | `.stack` | | Show the command-list stack (script name, cursor position, nesting depth) |
70
+ | `.set VAR VAL` | `.s` | Set or update a substitution variable |
71
+ | `.help` | `.h` | Show available commands |
72
+
73
+ Anything not starting with `.` is treated as a variable lookup or SQL:
74
+
75
+ - A bare name (e.g. `logfile`) prints the value of that substitution variable.
76
+ - Any input ending with `;` is executed as SQL against the current database (expects columns returned, e.g. SELECT).
77
+
78
+ The `--debug` CLI flag starts execution in step mode, pausing before every statement.
79
+
80
+ In non-interactive environments (CI, piped input) `BREAKPOINT` is silently skipped so automated pipelines are never blocked.
81
+
41
82
  The ON ERROR_HALT metacommands allow custom reporting (or cleanup) actions to be taken when errors occur.
42
83
 
43
84
  Setting the configuration setting [write_warnings](../reference/configuration.md#write_warnings) to "Yes" can also assist with debugging by displaying conditions that may result from errors in the script.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "execsql2"
7
- version = "2.11.1"
7
+ version = "2.12.1"
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.11.1"
161
+ current_version = "2.12.1"
162
162
  commit = true
163
163
  commit_args = "--no-verify"
164
164
  tag = true
@@ -182,7 +182,7 @@ addopts = [
182
182
  "--cov=execsql",
183
183
  "--cov-branch",
184
184
  "--cov-report=xml",
185
- "--cov-fail-under=85",
185
+ "--cov-fail-under=90",
186
186
  "--strict-markers",
187
187
  "--strict-config",
188
188
  "--color=yes",
@@ -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
+ profile_limit: int = typer.Option(
282
+ 20,
283
+ "--profile-limit",
284
+ help="Number of top statements to show in the --profile timing summary (default: 20).",
285
+ ),
281
286
  debug: bool = typer.Option(
282
287
  False,
283
288
  "--debug",
@@ -449,6 +454,7 @@ def main(
449
454
  output_dir=output_dir,
450
455
  progress=progress,
451
456
  profile=profile,
457
+ profile_limit=profile_limit,
452
458
  ping=ping,
453
459
  lint=lint,
454
460
  debug=debug,
@@ -71,12 +71,14 @@ def _print_dry_run(cmdlist: object) -> None:
71
71
  # ---------------------------------------------------------------------------
72
72
 
73
73
 
74
- def _print_profile(profile_data: list[tuple]) -> None:
74
+ def _print_profile(profile_data: list[tuple], limit: int = 20) -> None:
75
75
  """Print a per-statement timing summary to stdout.
76
76
 
77
77
  Args:
78
78
  profile_data: List of ``(source, line_no, command_type, elapsed_secs,
79
79
  command_text_preview)`` tuples collected during execution.
80
+ limit: Maximum number of top statements to display (default: 20).
81
+ All statements are included in totals regardless of this value.
80
82
  """
81
83
  if not profile_data:
82
84
  _console.print("[dim]Profile: no statements recorded.[/dim]")
@@ -85,9 +87,9 @@ def _print_profile(profile_data: list[tuple]) -> None:
85
87
  total_secs = sum(row[3] for row in profile_data)
86
88
  n = len(profile_data)
87
89
 
88
- # Sort descending by elapsed time; show top 20 (or all if <= 20).
90
+ # Sort descending by elapsed time; show top `limit` (or all if <= limit).
89
91
  sorted_data = sorted(profile_data, key=lambda r: r[3], reverse=True)
90
- display = sorted_data[:20]
92
+ display = sorted_data[:limit]
91
93
 
92
94
  _console.print()
93
95
  _console.print(f"[bold cyan]Profile:[/bold cyan] {n} statement{'s' if n != 1 else ''} in {total_secs:.3f}s")
@@ -115,10 +117,10 @@ def _print_profile(profile_data: list[tuple]) -> None:
115
117
  f"{preview_short}",
116
118
  )
117
119
 
118
- if len(sorted_data) > 20:
119
- omitted = len(sorted_data) - 20
120
+ if len(sorted_data) > limit:
121
+ omitted = len(sorted_data) - limit
120
122
  _console.print(
121
- f"[dim] ... {omitted} more statement{'s' if omitted != 1 else ''} not shown (top 20 by time)[/dim]",
123
+ f"[dim] ... {omitted} more statement{'s' if omitted != 1 else ''} not shown (top {limit} by time)[/dim]",
122
124
  )
123
125
 
124
126
  _console.print()
@@ -213,6 +215,7 @@ def _run(
213
215
  output_dir: str | None = None,
214
216
  progress: bool = False,
215
217
  profile: bool = False,
218
+ profile_limit: int = 20,
216
219
  ping: bool = False,
217
220
  lint: bool = False,
218
221
  debug: bool = False,
@@ -552,7 +555,7 @@ def _run(
552
555
  if debug:
553
556
  _state.step_mode = True
554
557
 
555
- _execute_script_direct(conf, profile=profile)
558
+ _execute_script_direct(conf, profile=profile, profile_limit=profile_limit)
556
559
 
557
560
 
558
561
  # ---------------------------------------------------------------------------
@@ -611,7 +614,7 @@ def _execute_script_textual_console(conf: ConfigData) -> None:
611
614
  _state.exec_log.log_exit_end()
612
615
 
613
616
 
614
- def _execute_script_direct(conf: ConfigData, *, profile: bool = False) -> None:
617
+ def _execute_script_direct(conf: ConfigData, *, profile: bool = False, profile_limit: int = 20) -> None:
615
618
  """Run runscripts() in the current (main) thread — used when Textual is not active.
616
619
 
617
620
  Args:
@@ -619,6 +622,8 @@ def _execute_script_direct(conf: ConfigData, *, profile: bool = False) -> None:
619
622
  profile: When ``True``, print a per-statement timing summary after the
620
623
  script completes. Timing data must already have been activated on
621
624
  ``_state.profile_data`` before this function is called.
625
+ profile_limit: Maximum number of top statements to display in the
626
+ profile summary (default: 20).
622
627
  """
623
628
  import execsql.state as _state
624
629
  import execsql.utils.gui as _gui
@@ -645,7 +650,7 @@ def _execute_script_direct(conf: ConfigData, *, profile: bool = False) -> None:
645
650
  gui_console_off()
646
651
  _state.exec_log.log_status_info(f"{_state.cmds_run} commands run")
647
652
  if profile and _state.profile_data is not None:
648
- _print_profile(_state.profile_data)
653
+ _print_profile(_state.profile_data, limit=profile_limit)
649
654
  sys.exit(exc.code)
650
655
  except ConfigError:
651
656
  raise
@@ -673,7 +678,7 @@ def _execute_script_direct(conf: ConfigData, *, profile: bool = False) -> None:
673
678
  gui_console_off()
674
679
  _state.exec_log.log_status_info(f"{_state.cmds_run} commands run")
675
680
  if profile and _state.profile_data is not None:
676
- _print_profile(_state.profile_data)
681
+ _print_profile(_state.profile_data, limit=profile_limit)
677
682
  _state.exec_log.log_exit_end()
678
683
 
679
684
 
@@ -234,18 +234,22 @@ class Database(ABC):
234
234
  pass # Non-critical: some drivers lack rowcount support.
235
235
 
236
236
  def decode_row() -> Generator:
237
- while True:
238
- rows = curs.fetchmany()
239
- if not rows:
240
- break
241
- else:
242
- for row in rows:
243
- if self.encoding:
244
- yield [
245
- c.decode(self.encoding, "backslashreplace") if isinstance(c, bytes) else c for c in row
246
- ]
247
- else:
248
- yield row
237
+ try:
238
+ while True:
239
+ rows = curs.fetchmany()
240
+ if not rows:
241
+ break
242
+ else:
243
+ for row in rows:
244
+ if self.encoding:
245
+ yield [
246
+ c.decode(self.encoding, "backslashreplace") if isinstance(c, bytes) else c
247
+ for c in row
248
+ ]
249
+ else:
250
+ yield row
251
+ finally:
252
+ curs.close()
249
253
 
250
254
  return [d[0] for d in curs.description], decode_row()
251
255
 
@@ -253,6 +257,10 @@ class Database(ABC):
253
257
  """Execute *sql* and return ``(column_names, row_iterator)`` where each row is a ``dict``."""
254
258
  # Return an iterable that yields dictionaries of row data
255
259
  curs = self.cursor()
260
+ try:
261
+ curs.arraysize = _state.conf.export_row_buffer
262
+ except Exception:
263
+ pass # Non-critical: not all drivers support arraysize.
256
264
  try:
257
265
  curs.execute(sql)
258
266
  except Exception:
@@ -264,18 +272,24 @@ class Database(ABC):
264
272
  pass # Non-critical: some drivers lack rowcount support.
265
273
  hdrs = [d[0] for d in curs.description]
266
274
 
267
- def dict_row() -> dict | None:
268
- row = curs.fetchone()
269
- if row:
270
- if self.encoding:
271
- r = [c.decode(self.encoding, "backslashreplace") if isinstance(c, bytes) else c for c in row]
272
- else:
273
- r = row
274
- return dict(zip(hdrs, r))
275
- else:
276
- return None
275
+ def dict_rows() -> Generator:
276
+ try:
277
+ while True:
278
+ rows = curs.fetchmany()
279
+ if not rows:
280
+ break
281
+ for row in rows:
282
+ if self.encoding:
283
+ r = [
284
+ c.decode(self.encoding, "backslashreplace") if isinstance(c, bytes) else c for c in row
285
+ ]
286
+ else:
287
+ r = row
288
+ yield dict(zip(hdrs, r))
289
+ finally:
290
+ curs.close()
277
291
 
278
- return hdrs, iter(dict_row, None)
292
+ return hdrs, dict_rows()
279
293
 
280
294
  def schema_exists(self, schema_name: str) -> bool:
281
295
  """Return ``True`` if *schema_name* exists in this database."""
@@ -0,0 +1,6 @@
1
+ """Debug utilities for execsql.
2
+
3
+ This package contains interactive debugging tools for execsql scripts:
4
+
5
+ - :mod:`execsql.debug.repl` — the ``BREAKPOINT`` metacommand REPL.
6
+ """