execsql2 2.8.0__tar.gz → 2.10.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 (293) hide show
  1. {execsql2-2.8.0 → execsql2-2.10.0}/.claude/project_context.md +66 -7
  2. {execsql2-2.8.0 → execsql2-2.10.0}/CHANGELOG.md +21 -0
  3. {execsql2-2.8.0 → execsql2-2.10.0}/PKG-INFO +2 -1
  4. {execsql2-2.8.0 → execsql2-2.10.0}/README.md +1 -0
  5. {execsql2-2.8.0 → execsql2-2.10.0}/docs/about/divergence.md +17 -5
  6. {execsql2-2.8.0 → execsql2-2.10.0}/docs/getting-started/syntax.md +28 -0
  7. {execsql2-2.8.0 → execsql2-2.10.0}/docs/reference/configuration.md +11 -7
  8. {execsql2-2.8.0 → execsql2-2.10.0}/docs/reference/metacommands.md +112 -11
  9. {execsql2-2.8.0 → execsql2-2.10.0}/extras/vscode-execsql/syntaxes/execsql.tmLanguage.json +2 -2
  10. {execsql2-2.8.0 → execsql2-2.10.0}/pyproject.toml +2 -2
  11. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/cli/__init__.py +25 -0
  12. execsql2-2.10.0/src/execsql/cli/lint.py +459 -0
  13. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/cli/run.py +101 -9
  14. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/metacommands/__init__.py +3 -0
  15. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/metacommands/conditions.py +148 -0
  16. execsql2-2.10.0/src/execsql/metacommands/debug_repl.py +223 -0
  17. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/metacommands/dispatch.py +12 -0
  18. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/script/engine.py +5 -0
  19. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/state.py +9 -0
  20. execsql2-2.10.0/tests/cli/test_lint.py +473 -0
  21. execsql2-2.10.0/tests/cli/test_ping.py +315 -0
  22. execsql2-2.10.0/tests/metacommands/test_breakpoint.py +464 -0
  23. execsql2-2.10.0/tests/metacommands/test_row_count.py +496 -0
  24. {execsql2-2.8.0 → execsql2-2.10.0}/uv.lock +1 -1
  25. {execsql2-2.8.0 → execsql2-2.10.0}/.claude/agents/dba.md +0 -0
  26. {execsql2-2.8.0 → execsql2-2.10.0}/.claude/agents/herald.md +0 -0
  27. {execsql2-2.8.0 → execsql2-2.10.0}/.claude/agents/inspector.md +0 -0
  28. {execsql2-2.8.0 → execsql2-2.10.0}/.claude/agents/oracle.md +0 -0
  29. {execsql2-2.8.0 → execsql2-2.10.0}/.claude/agents/patcher.md +0 -0
  30. {execsql2-2.8.0 → execsql2-2.10.0}/.claude/agents/qa.md +0 -0
  31. {execsql2-2.8.0 → execsql2-2.10.0}/.claude/agents/scribe.md +0 -0
  32. {execsql2-2.8.0 → execsql2-2.10.0}/.claude/commands/code-oracle.md +0 -0
  33. {execsql2-2.8.0 → execsql2-2.10.0}/.claude/commands/migrate.md +0 -0
  34. {execsql2-2.8.0 → execsql2-2.10.0}/.claude/commands/review-changes.md +0 -0
  35. {execsql2-2.8.0 → execsql2-2.10.0}/.claude/commands/test-module.md +0 -0
  36. {execsql2-2.8.0 → execsql2-2.10.0}/.claude/commands/update-changelog.md +0 -0
  37. {execsql2-2.8.0 → execsql2-2.10.0}/.claude/commands/where-is.md +0 -0
  38. {execsql2-2.8.0 → execsql2-2.10.0}/.claude/state/status.md +0 -0
  39. {execsql2-2.8.0 → execsql2-2.10.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  40. {execsql2-2.8.0 → execsql2-2.10.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  41. {execsql2-2.8.0 → execsql2-2.10.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  42. {execsql2-2.8.0 → execsql2-2.10.0}/.github/workflows/ci-cd.yml +0 -0
  43. {execsql2-2.8.0 → execsql2-2.10.0}/.gitignore +0 -0
  44. {execsql2-2.8.0 → execsql2-2.10.0}/.pre-commit-config.yaml +0 -0
  45. {execsql2-2.8.0 → execsql2-2.10.0}/.pre-commit-hooks.yaml +0 -0
  46. {execsql2-2.8.0 → execsql2-2.10.0}/.python-version +0 -0
  47. {execsql2-2.8.0 → execsql2-2.10.0}/.readthedocs.yaml +0 -0
  48. {execsql2-2.8.0 → execsql2-2.10.0}/CLAUDE.md +0 -0
  49. {execsql2-2.8.0 → execsql2-2.10.0}/CONTRIBUTING.md +0 -0
  50. {execsql2-2.8.0 → execsql2-2.10.0}/LICENSE.txt +0 -0
  51. {execsql2-2.8.0 → execsql2-2.10.0}/NOTICE +0 -0
  52. {execsql2-2.8.0 → execsql2-2.10.0}/SECURITY.md +0 -0
  53. {execsql2-2.8.0 → execsql2-2.10.0}/docs/about/contributors.md +0 -0
  54. {execsql2-2.8.0 → execsql2-2.10.0}/docs/about/copyright.md +0 -0
  55. {execsql2-2.8.0 → execsql2-2.10.0}/docs/api/cli.md +0 -0
  56. {execsql2-2.8.0 → execsql2-2.10.0}/docs/api/db.md +0 -0
  57. {execsql2-2.8.0 → execsql2-2.10.0}/docs/api/exporters.md +0 -0
  58. {execsql2-2.8.0 → execsql2-2.10.0}/docs/api/importers.md +0 -0
  59. {execsql2-2.8.0 → execsql2-2.10.0}/docs/api/index.md +0 -0
  60. {execsql2-2.8.0 → execsql2-2.10.0}/docs/api/metacommands.md +0 -0
  61. {execsql2-2.8.0 → execsql2-2.10.0}/docs/dev/adding_db_adapters.md +0 -0
  62. {execsql2-2.8.0 → execsql2-2.10.0}/docs/dev/adding_exporters.md +0 -0
  63. {execsql2-2.8.0 → execsql2-2.10.0}/docs/dev/adding_importers.md +0 -0
  64. {execsql2-2.8.0 → execsql2-2.10.0}/docs/dev/adding_metacommands.md +0 -0
  65. {execsql2-2.8.0 → execsql2-2.10.0}/docs/dev/architecture.md +0 -0
  66. {execsql2-2.8.0 → execsql2-2.10.0}/docs/getting-started/installation.md +0 -0
  67. {execsql2-2.8.0 → execsql2-2.10.0}/docs/getting-started/requirements.md +0 -0
  68. {execsql2-2.8.0 → execsql2-2.10.0}/docs/guides/debugging.md +0 -0
  69. {execsql2-2.8.0 → execsql2-2.10.0}/docs/guides/documentation.md +0 -0
  70. {execsql2-2.8.0 → execsql2-2.10.0}/docs/guides/encoding.md +0 -0
  71. {execsql2-2.8.0 → execsql2-2.10.0}/docs/guides/examples.md +0 -0
  72. {execsql2-2.8.0 → execsql2-2.10.0}/docs/guides/formatter.md +0 -0
  73. {execsql2-2.8.0 → execsql2-2.10.0}/docs/guides/logging.md +0 -0
  74. {execsql2-2.8.0 → execsql2-2.10.0}/docs/guides/sql_syntax.md +0 -0
  75. {execsql2-2.8.0 → execsql2-2.10.0}/docs/guides/usage.md +0 -0
  76. {execsql2-2.8.0 → execsql2-2.10.0}/docs/guides/using_scripts.md +0 -0
  77. {execsql2-2.8.0 → execsql2-2.10.0}/docs/images/Compare_planets.png +0 -0
  78. {execsql2-2.8.0 → execsql2-2.10.0}/docs/images/actions.png +0 -0
  79. {execsql2-2.8.0 → execsql2-2.10.0}/docs/images/actions2.png +0 -0
  80. {execsql2-2.8.0 → execsql2-2.10.0}/docs/images/checkboxes.png +0 -0
  81. {execsql2-2.8.0 → execsql2-2.10.0}/docs/images/connect.b64 +0 -0
  82. {execsql2-2.8.0 → execsql2-2.10.0}/docs/images/connect.png +0 -0
  83. {execsql2-2.8.0 → execsql2-2.10.0}/docs/images/create_conf.png +0 -0
  84. {execsql2-2.8.0 → execsql2-2.10.0}/docs/images/data_error1_screenshot.jpg +0 -0
  85. {execsql2-2.8.0 → execsql2-2.10.0}/docs/images/entry_form.png +0 -0
  86. {execsql2-2.8.0 → execsql2-2.10.0}/docs/images/execsql_console.png +0 -0
  87. {execsql2-2.8.0 → execsql2-2.10.0}/docs/images/execsql_logo_01.png +0 -0
  88. {execsql2-2.8.0 → execsql2-2.10.0}/docs/images/fatals.png +0 -0
  89. {execsql2-2.8.0 → execsql2-2.10.0}/docs/images/logo_small.png +0 -0
  90. {execsql2-2.8.0 → execsql2-2.10.0}/docs/images/pause_terminal.png +0 -0
  91. {execsql2-2.8.0 → execsql2-2.10.0}/docs/images/pause_terminal_sm.b64 +0 -0
  92. {execsql2-2.8.0 → execsql2-2.10.0}/docs/images/pause_terminal_sm.png +0 -0
  93. {execsql2-2.8.0 → execsql2-2.10.0}/docs/images/prompt_compare.png +0 -0
  94. {execsql2-2.8.0 → execsql2-2.10.0}/docs/images/set_build_commands.jpg +0 -0
  95. {execsql2-2.8.0 → execsql2-2.10.0}/docs/images/unit_conversions.b64 +0 -0
  96. {execsql2-2.8.0 → execsql2-2.10.0}/docs/images/unit_conversions_029.png +0 -0
  97. {execsql2-2.8.0 → execsql2-2.10.0}/docs/images/unmatched.png +0 -0
  98. {execsql2-2.8.0 → execsql2-2.10.0}/docs/images/vim_execsql_highlight.png +0 -0
  99. {execsql2-2.8.0 → execsql2-2.10.0}/docs/index.md +0 -0
  100. {execsql2-2.8.0 → execsql2-2.10.0}/docs/reference/security.md +0 -0
  101. {execsql2-2.8.0 → execsql2-2.10.0}/docs/reference/substitution_vars.md +0 -0
  102. {execsql2-2.8.0 → execsql2-2.10.0}/extras/vscode-execsql/README.md +0 -0
  103. {execsql2-2.8.0 → execsql2-2.10.0}/extras/vscode-execsql/package.json +0 -0
  104. {execsql2-2.8.0 → execsql2-2.10.0}/justfile +0 -0
  105. {execsql2-2.8.0 → execsql2-2.10.0}/scripts/generate_vscode_grammar.py +0 -0
  106. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/__init__.py +0 -0
  107. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/__main__.py +0 -0
  108. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/cli/dsn.py +0 -0
  109. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/cli/help.py +0 -0
  110. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/config.py +0 -0
  111. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/constants.py +0 -0
  112. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/db/__init__.py +0 -0
  113. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/db/access.py +0 -0
  114. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/db/base.py +0 -0
  115. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/db/dsn.py +0 -0
  116. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/db/duckdb.py +0 -0
  117. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/db/factory.py +0 -0
  118. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/db/firebird.py +0 -0
  119. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/db/mysql.py +0 -0
  120. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/db/oracle.py +0 -0
  121. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/db/postgres.py +0 -0
  122. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/db/sqlite.py +0 -0
  123. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/db/sqlserver.py +0 -0
  124. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/exceptions.py +0 -0
  125. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/exporters/__init__.py +0 -0
  126. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/exporters/base.py +0 -0
  127. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/exporters/delimited.py +0 -0
  128. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/exporters/duckdb.py +0 -0
  129. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/exporters/feather.py +0 -0
  130. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/exporters/html.py +0 -0
  131. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/exporters/json.py +0 -0
  132. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/exporters/latex.py +0 -0
  133. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/exporters/markdown.py +0 -0
  134. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/exporters/ods.py +0 -0
  135. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/exporters/parquet.py +0 -0
  136. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/exporters/pretty.py +0 -0
  137. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/exporters/protocol.py +0 -0
  138. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/exporters/raw.py +0 -0
  139. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/exporters/sqlite.py +0 -0
  140. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/exporters/templates.py +0 -0
  141. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/exporters/values.py +0 -0
  142. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/exporters/xls.py +0 -0
  143. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/exporters/xlsx.py +0 -0
  144. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/exporters/xml.py +0 -0
  145. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/exporters/yaml.py +0 -0
  146. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/exporters/zip.py +0 -0
  147. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/format.py +0 -0
  148. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/gui/__init__.py +0 -0
  149. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/gui/base.py +0 -0
  150. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/gui/console.py +0 -0
  151. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/gui/desktop.py +0 -0
  152. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/gui/tui.py +0 -0
  153. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/importers/__init__.py +0 -0
  154. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/importers/base.py +0 -0
  155. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/importers/csv.py +0 -0
  156. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/importers/feather.py +0 -0
  157. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/importers/ods.py +0 -0
  158. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/importers/xls.py +0 -0
  159. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/metacommands/connect.py +0 -0
  160. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/metacommands/control.py +0 -0
  161. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/metacommands/data.py +0 -0
  162. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/metacommands/debug.py +0 -0
  163. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/metacommands/io.py +0 -0
  164. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/metacommands/io_export.py +0 -0
  165. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/metacommands/io_fileops.py +0 -0
  166. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/metacommands/io_import.py +0 -0
  167. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/metacommands/io_write.py +0 -0
  168. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/metacommands/prompt.py +0 -0
  169. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/metacommands/script_ext.py +0 -0
  170. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/metacommands/system.py +0 -0
  171. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/models.py +0 -0
  172. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/parser.py +0 -0
  173. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/py.typed +0 -0
  174. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/script/__init__.py +0 -0
  175. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/script/control.py +0 -0
  176. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/script/variables.py +0 -0
  177. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/types.py +0 -0
  178. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/utils/__init__.py +0 -0
  179. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/utils/auth.py +0 -0
  180. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/utils/crypto.py +0 -0
  181. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/utils/datetime.py +0 -0
  182. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/utils/errors.py +0 -0
  183. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/utils/fileio.py +0 -0
  184. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/utils/gui.py +0 -0
  185. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/utils/mail.py +0 -0
  186. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/utils/numeric.py +0 -0
  187. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/utils/regex.py +0 -0
  188. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/utils/strings.py +0 -0
  189. {execsql2-2.8.0 → execsql2-2.10.0}/src/execsql/utils/timer.py +0 -0
  190. {execsql2-2.8.0 → execsql2-2.10.0}/templates/README.md +0 -0
  191. {execsql2-2.8.0 → execsql2-2.10.0}/templates/config_settings.sqlite +0 -0
  192. {execsql2-2.8.0 → execsql2-2.10.0}/templates/example_config_prompt.sql +0 -0
  193. {execsql2-2.8.0 → execsql2-2.10.0}/templates/execsql.conf +0 -0
  194. {execsql2-2.8.0 → execsql2-2.10.0}/templates/make_config_db.sql +0 -0
  195. {execsql2-2.8.0 → execsql2-2.10.0}/templates/md_compare.sql +0 -0
  196. {execsql2-2.8.0 → execsql2-2.10.0}/templates/md_glossary.sql +0 -0
  197. {execsql2-2.8.0 → execsql2-2.10.0}/templates/md_upsert.sql +0 -0
  198. {execsql2-2.8.0 → execsql2-2.10.0}/templates/pg_compare.sql +0 -0
  199. {execsql2-2.8.0 → execsql2-2.10.0}/templates/pg_glossary.sql +0 -0
  200. {execsql2-2.8.0 → execsql2-2.10.0}/templates/pg_upsert.sql +0 -0
  201. {execsql2-2.8.0 → execsql2-2.10.0}/templates/script_template.sql +0 -0
  202. {execsql2-2.8.0 → execsql2-2.10.0}/templates/ss_compare.sql +0 -0
  203. {execsql2-2.8.0 → execsql2-2.10.0}/templates/ss_glossary.sql +0 -0
  204. {execsql2-2.8.0 → execsql2-2.10.0}/templates/ss_upsert.sql +0 -0
  205. {execsql2-2.8.0 → execsql2-2.10.0}/tests/__init__.py +0 -0
  206. {execsql2-2.8.0 → execsql2-2.10.0}/tests/cli/__init__.py +0 -0
  207. {execsql2-2.8.0 → execsql2-2.10.0}/tests/cli/test_cli.py +0 -0
  208. {execsql2-2.8.0 → execsql2-2.10.0}/tests/cli/test_cli_e2e.py +0 -0
  209. {execsql2-2.8.0 → execsql2-2.10.0}/tests/cli/test_cli_run.py +0 -0
  210. {execsql2-2.8.0 → execsql2-2.10.0}/tests/cli/test_profile.py +0 -0
  211. {execsql2-2.8.0 → execsql2-2.10.0}/tests/conftest.py +0 -0
  212. {execsql2-2.8.0 → execsql2-2.10.0}/tests/db/__init__.py +0 -0
  213. {execsql2-2.8.0 → execsql2-2.10.0}/tests/db/test_base.py +0 -0
  214. {execsql2-2.8.0 → execsql2-2.10.0}/tests/db/test_duckdb.py +0 -0
  215. {execsql2-2.8.0 → execsql2-2.10.0}/tests/db/test_factory.py +0 -0
  216. {execsql2-2.8.0 → execsql2-2.10.0}/tests/db/test_postgres.py +0 -0
  217. {execsql2-2.8.0 → execsql2-2.10.0}/tests/db/test_sqlite.py +0 -0
  218. {execsql2-2.8.0 → execsql2-2.10.0}/tests/db/test_sqlite_extra.py +0 -0
  219. {execsql2-2.8.0 → execsql2-2.10.0}/tests/exporters/__init__.py +0 -0
  220. {execsql2-2.8.0 → execsql2-2.10.0}/tests/exporters/test_base.py +0 -0
  221. {execsql2-2.8.0 → execsql2-2.10.0}/tests/exporters/test_db.py +0 -0
  222. {execsql2-2.8.0 → execsql2-2.10.0}/tests/exporters/test_delimited.py +0 -0
  223. {execsql2-2.8.0 → execsql2-2.10.0}/tests/exporters/test_duckdb_exporter.py +0 -0
  224. {execsql2-2.8.0 → execsql2-2.10.0}/tests/exporters/test_exporters.py +0 -0
  225. {execsql2-2.8.0 → execsql2-2.10.0}/tests/exporters/test_feather.py +0 -0
  226. {execsql2-2.8.0 → execsql2-2.10.0}/tests/exporters/test_html_latex.py +0 -0
  227. {execsql2-2.8.0 → execsql2-2.10.0}/tests/exporters/test_json.py +0 -0
  228. {execsql2-2.8.0 → execsql2-2.10.0}/tests/exporters/test_markdown.py +0 -0
  229. {execsql2-2.8.0 → execsql2-2.10.0}/tests/exporters/test_ods.py +0 -0
  230. {execsql2-2.8.0 → execsql2-2.10.0}/tests/exporters/test_parquet.py +0 -0
  231. {execsql2-2.8.0 → execsql2-2.10.0}/tests/exporters/test_sqlite_exporter.py +0 -0
  232. {execsql2-2.8.0 → execsql2-2.10.0}/tests/exporters/test_templates.py +0 -0
  233. {execsql2-2.8.0 → execsql2-2.10.0}/tests/exporters/test_xls_xlsx.py +0 -0
  234. {execsql2-2.8.0 → execsql2-2.10.0}/tests/exporters/test_xlsx.py +0 -0
  235. {execsql2-2.8.0 → execsql2-2.10.0}/tests/exporters/test_xml.py +0 -0
  236. {execsql2-2.8.0 → execsql2-2.10.0}/tests/exporters/test_yaml.py +0 -0
  237. {execsql2-2.8.0 → execsql2-2.10.0}/tests/exporters/test_zip.py +0 -0
  238. {execsql2-2.8.0 → execsql2-2.10.0}/tests/gui/__init__.py +0 -0
  239. {execsql2-2.8.0 → execsql2-2.10.0}/tests/gui/test_backends.py +0 -0
  240. {execsql2-2.8.0 → execsql2-2.10.0}/tests/importers/__init__.py +0 -0
  241. {execsql2-2.8.0 → execsql2-2.10.0}/tests/importers/test_csv_importer.py +0 -0
  242. {execsql2-2.8.0 → execsql2-2.10.0}/tests/importers/test_feather_importer.py +0 -0
  243. {execsql2-2.8.0 → execsql2-2.10.0}/tests/importers/test_ods_importer.py +0 -0
  244. {execsql2-2.8.0 → execsql2-2.10.0}/tests/importers/test_xls_importer.py +0 -0
  245. {execsql2-2.8.0 → execsql2-2.10.0}/tests/integration/__init__.py +0 -0
  246. {execsql2-2.8.0 → execsql2-2.10.0}/tests/integration/conftest.py +0 -0
  247. {execsql2-2.8.0 → execsql2-2.10.0}/tests/integration/test_dsn.py +0 -0
  248. {execsql2-2.8.0 → execsql2-2.10.0}/tests/integration/test_duckdb.py +0 -0
  249. {execsql2-2.8.0 → execsql2-2.10.0}/tests/integration/test_mysql.py +0 -0
  250. {execsql2-2.8.0 → execsql2-2.10.0}/tests/integration/test_postgres.py +0 -0
  251. {execsql2-2.8.0 → execsql2-2.10.0}/tests/integration/test_sqlite.py +0 -0
  252. {execsql2-2.8.0 → execsql2-2.10.0}/tests/metacommands/__init__.py +0 -0
  253. {execsql2-2.8.0 → execsql2-2.10.0}/tests/metacommands/test_assert.py +0 -0
  254. {execsql2-2.8.0 → execsql2-2.10.0}/tests/metacommands/test_connect.py +0 -0
  255. {execsql2-2.8.0 → execsql2-2.10.0}/tests/metacommands/test_metacommands.py +0 -0
  256. {execsql2-2.8.0 → execsql2-2.10.0}/tests/metacommands/test_metacommands_connect.py +0 -0
  257. {execsql2-2.8.0 → execsql2-2.10.0}/tests/metacommands/test_metacommands_data.py +0 -0
  258. {execsql2-2.8.0 → execsql2-2.10.0}/tests/metacommands/test_metacommands_extended.py +0 -0
  259. {execsql2-2.8.0 → execsql2-2.10.0}/tests/metacommands/test_metacommands_fileops_extra.py +0 -0
  260. {execsql2-2.8.0 → execsql2-2.10.0}/tests/metacommands/test_metacommands_io.py +0 -0
  261. {execsql2-2.8.0 → execsql2-2.10.0}/tests/metacommands/test_metacommands_io_write_extra.py +0 -0
  262. {execsql2-2.8.0 → execsql2-2.10.0}/tests/metacommands/test_metacommands_script_ext.py +0 -0
  263. {execsql2-2.8.0 → execsql2-2.10.0}/tests/metacommands/test_metacommands_system.py +0 -0
  264. {execsql2-2.8.0 → execsql2-2.10.0}/tests/metacommands/test_metacommands_system_extra.py +0 -0
  265. {execsql2-2.8.0 → execsql2-2.10.0}/tests/test_config.py +0 -0
  266. {execsql2-2.8.0 → execsql2-2.10.0}/tests/test_config_data.py +0 -0
  267. {execsql2-2.8.0 → execsql2-2.10.0}/tests/test_constants.py +0 -0
  268. {execsql2-2.8.0 → execsql2-2.10.0}/tests/test_engine.py +0 -0
  269. {execsql2-2.8.0 → execsql2-2.10.0}/tests/test_exceptions.py +0 -0
  270. {execsql2-2.8.0 → execsql2-2.10.0}/tests/test_format.py +0 -0
  271. {execsql2-2.8.0 → execsql2-2.10.0}/tests/test_mail.py +0 -0
  272. {execsql2-2.8.0 → execsql2-2.10.0}/tests/test_models.py +0 -0
  273. {execsql2-2.8.0 → execsql2-2.10.0}/tests/test_package.py +0 -0
  274. {execsql2-2.8.0 → execsql2-2.10.0}/tests/test_parser.py +0 -0
  275. {execsql2-2.8.0 → execsql2-2.10.0}/tests/test_registry.py +0 -0
  276. {execsql2-2.8.0 → execsql2-2.10.0}/tests/test_script.py +0 -0
  277. {execsql2-2.8.0 → execsql2-2.10.0}/tests/test_state.py +0 -0
  278. {execsql2-2.8.0 → execsql2-2.10.0}/tests/test_types.py +0 -0
  279. {execsql2-2.8.0 → execsql2-2.10.0}/tests/utils/__init__.py +0 -0
  280. {execsql2-2.8.0 → execsql2-2.10.0}/tests/utils/test_auth.py +0 -0
  281. {execsql2-2.8.0 → execsql2-2.10.0}/tests/utils/test_auth_extra.py +0 -0
  282. {execsql2-2.8.0 → execsql2-2.10.0}/tests/utils/test_crypto.py +0 -0
  283. {execsql2-2.8.0 → execsql2-2.10.0}/tests/utils/test_datetime.py +0 -0
  284. {execsql2-2.8.0 → execsql2-2.10.0}/tests/utils/test_errors.py +0 -0
  285. {execsql2-2.8.0 → execsql2-2.10.0}/tests/utils/test_errors_extra.py +0 -0
  286. {execsql2-2.8.0 → execsql2-2.10.0}/tests/utils/test_fileio.py +0 -0
  287. {execsql2-2.8.0 → execsql2-2.10.0}/tests/utils/test_fileio_extra.py +0 -0
  288. {execsql2-2.8.0 → execsql2-2.10.0}/tests/utils/test_numeric.py +0 -0
  289. {execsql2-2.8.0 → execsql2-2.10.0}/tests/utils/test_regex.py +0 -0
  290. {execsql2-2.8.0 → execsql2-2.10.0}/tests/utils/test_strings.py +0 -0
  291. {execsql2-2.8.0 → execsql2-2.10.0}/tests/utils/test_timer.py +0 -0
  292. {execsql2-2.8.0 → execsql2-2.10.0}/tests/utils/test_timer_extra.py +0 -0
  293. {execsql2-2.8.0 → execsql2-2.10.0}/zensical.toml +0 -0
@@ -275,28 +275,63 @@ ______________________________________________________________________
275
275
  - [x] **`ASSERT` metacommand** — `-- !x! ASSERT <condition> "message"`. Reuses IF condition engine. 13 tests.
276
276
  - [x] **`--dry-run` improvements** — expands substitution variables populated at parse time. 4 new tests.
277
277
  - [x] **Script profiling (`--profile`)** — per-statement `perf_counter()` timing with sorted summary table. 19 tests.
278
- - [ ] **Parallel execution blocks**`PARALLEL BEGIN ... PARALLEL END` for independent statements. See design notes below.
278
+ - ~~**Parallel execution blocks**~~deferred to v3.0+. See design notes and deferral rationale below.
279
279
 
280
- ### v2.9Library API & Developer Experience
280
+ ### Candidate Features (unscheduled pick and assign to milestones)
281
281
 
282
- - [ ] **Programmatic Python API** — `execsql.run(script, db=...)` for notebook/pipeline usage. Depends on `RuntimeContext` refactor.
282
+ #### Quick Wins
283
+
284
+ - [x] **`--ping`** — test database connectivity and exit with a status message. Useful for CI health checks and connection debugging. (shipped v2.8.x)
285
+ - ~~`SUB_FILE`~~ — redundant with existing `SUB_INI` metacommand which already loads variables from INI-format files.
286
+ - [ ] **`WRITE TABLE` metacommand** — pretty-print a query result to the console as a formatted table (like `psql` output). Quick debugging aid without needing a full EXPORT.
287
+ - [ ] **`--env` / `--config` flag** — load a specific config file by path instead of relying on the search hierarchy. `execsql --env prod.conf script.sql`.
288
+
289
+ #### Scripting & Reliability
290
+
291
+ - [ ] **`ON ERROR RETRY N`** — retry a failed SQL statement N times with exponential backoff. Handles transient network blips and lock timeouts in ETL scripts.
292
+ - [ ] **`DIFF` metacommand** — compare two query results non-interactively and write differences to a file or set `$DIFF_COUNT`. Unlike `PROMPT COMPARE` (which is GUI/interactive), DIFF is headless and scriptable — designed for CI pipelines, automated validation, and audit trails. Supports arbitrary queries, not just table names.
293
+ - [x] **Script linting (`--lint`)** — parse and report common issues without executing: unmatched IF/ENDIF, unmatched LOOP/END LOOP, undefined variable references, missing INCLUDE files. (shipped v2.8.x)
294
+
295
+ #### Persistent State (`~/.execsql/state.db`)
296
+
297
+ A central SQLite database at `~/.execsql/state.db` that tracks per-script
298
+ state across runs. Cross-platform via `Path.home()`. Created automatically
299
+ on first use. All features below depend on this infrastructure.
300
+
301
+ - [ ] **State DB infrastructure** — `StateDB` class, schema creation, `~/.execsql/` directory management. Foundation for everything below.
302
+ - [ ] **Run numbering (`$RUN_NUMBER`, `$LAST_RUN_TIME`, `$FIRST_RUN_TIME`)** — persistent per-script run counter and timestamps. Always-on, incremented each invocation. `--reset-run-number` CLI flag to reset. Useful for audit trails, incremental file naming (`output_!!$RUN_NUMBER!!.csv`), conditional first-run logic.
303
+ - [ ] **Resumable scripts (`--checkpoint`)** — checkpoint after each statement. Auto-resumes on next `--checkpoint` run if checkpoint exists and script hash matches. Saves position, variables, and DB alias. Deletes checkpoint on successful completion.
304
+ - [ ] **Run history (`--history`)** — log each run (start time, duration, exit status, commands run, script path) to a `run_history` table. `execsql --history script.sql` shows past runs. Useful for auditing and debugging intermittent failures.
305
+ - [ ] **Persistent variables (`PERSIST` / `RECALL`)** — save substitution variables across runs. `-- !x! PERSIST $last_watermark` saves to state DB; next run `-- !x! RECALL $last_watermark` restores it. Enables incremental ETL (remember the last-processed timestamp/ID without external bookkeeping).
306
+ - [ ] **Script scheduling metadata** — store cron-like schedule info per script. `execsql --schedule "0 2 * * *" script.sql` records intent. `execsql --list-schedules` shows all. Doesn't execute (that's cron/Task Scheduler's job) — just a registry so you can answer "what runs when?" from one place.
307
+
308
+ #### Notifications & Integrations
309
+
310
+ - [ ] **Webhook notifications** — `ON ERROR_HALT WEBHOOK "https://..."` and `ON COMPLETE WEBHOOK "https://..."`. Like the existing `ON ERROR_HALT EMAIL` but for Slack/Teams/PagerDuty. More relevant than email in 2026.
311
+ - [ ] **HTTP/REST export** — `EXPORT QUERY ... TO POST "https://api.example.com/data" AS JSON`. POST query results directly to a webhook or API endpoint.
312
+
313
+ #### Developer Experience
314
+
315
+ - [ ] **Programmatic Python API** — `execsql.run(script, db=...)` for notebook/pipeline usage. Each call gets its own isolated `RuntimeContext`.
283
316
  - [ ] **TOML configuration** — `execsql.toml` as modern alternative to legacy INI format (coexist initially).
317
+ - [ ] **Colorized SQL in console output** — syntax-highlight SQL statements in WRITE, dry-run, and error output via Rich/Pygments.
284
318
 
285
- ### v2.10 — Testing & CI Hardening
319
+ #### Testing & CI Hardening
286
320
 
287
321
  - [ ] **Property-based testing (Hypothesis)** — for parsers, type inference, substitution variables.
288
322
  - [ ] **Parser fuzzing** — `CondParser` and `NumericParser` handle arbitrary user input; fuzz for edge cases.
289
323
  - [ ] **Nightly CI against latest DB driver versions** — catch upstream breakage in psycopg2, pymysql, duckdb, etc.
290
324
  - [ ] **CI benchmarks** — track substitution variable and dispatch performance over time.
291
325
 
292
- ### v2.11 — Documentation & Community
326
+ #### Documentation & Community
293
327
 
294
- - [ ] **Cookbook / recipes page** — real-world examples: ETL workflows, HTML reports, data validation pipelines.
328
+ - [ ] **Cookbook / recipes page** — real-world examples: ETL workflows, HTML reports, data validation pipelines, CI integration.
295
329
  - [ ] **Migration guide from upstream execsql** — what changed, what's new, how to switch.
296
330
  - [ ] **Interactive tutorial** — guided walkthrough script against a bundled SQLite DB.
297
331
 
298
- ### v3.0+ — Future
332
+ #### v3.0+ — Major Features
299
333
 
334
+ - [ ] **Parallel execution blocks** — `PARALLEL BEGIN ... PARALLEL END` for independent statements. See design notes and deferral rationale below.
300
335
  - [ ] **Plugin system** — entry points for `execsql.exporters`, `execsql.importers`, `execsql.metacommands` allowing external packages to register new handlers.
301
336
  - [ ] **LSP / language server** — for the VS Code extension: autocomplete metacommands, validate substitution variables, jump-to-definition for `INCLUDE`d scripts.
302
337
 
@@ -367,6 +402,30 @@ INSERT INTO summary_c SELECT ... FROM raw_data;
367
402
  block — but that negates parallelism for most backends, so this is
368
403
  mainly useful for independent ETL loads.
369
404
 
405
+ **Deferral rationale (2026-04):** Moved from v2.8 to v3.0+. The
406
+ complexity-to-value ratio is poor for the current user base:
407
+
408
+ 1. **Connection pooling does not exist.** `DatabasePool` holds one
409
+ connection per alias. Workers need their own connections, requiring
410
+ either a pool-per-alias model or fresh connections from the DSN —
411
+ significant infrastructure work.
412
+ 2. **Global state is not thread-safe.** Even with `RuntimeContext`, the
413
+ context is shared. Workers writing `$LAST_ROWCOUNT` or reading
414
+ `_state.subvars` would race. Per-worker read-only snapshots are needed.
415
+ 3. **Error semantics are complex.** Partial failure (2 of 4 workers fail)
416
+ requires decisions about cancellation, rollback, and error aggregation.
417
+ 4. **Narrow real-world benefit.** Most execsql users target a single DBMS.
418
+ Databases like PostgreSQL already parallelize queries internally.
419
+ Running concurrent INSERTTs against the same server often doesn't help
420
+ because they compete for locks, I/O, and CPU.
421
+ 5. **Better alternatives exist for orchestration.** Users with true
422
+ parallelism needs are better served by calling `execsql.run()` (v2.9
423
+ library API) from their own threading/async code, or by tools like
424
+ Airflow/dbt.
425
+
426
+ Revisit if the library API (v2.9) reveals demand for in-script
427
+ parallelism that can't be met by external orchestration.
428
+
370
429
  ______________________________________________________________________
371
430
 
372
431
  ## Open Design Questions
@@ -13,6 +13,27 @@ ______________________________________________________________________
13
13
 
14
14
  ______________________________________________________________________
15
15
 
16
+ ## [2.10.0] - 2026-04-01
17
+
18
+ ### Added
19
+
20
+ - **`BREAKPOINT` metacommand** — pauses script execution and drops into an interactive debug REPL. The prompt accepts `continue`/`c` to resume, `abort`/`q` to halt, `vars` to list substitution variables, `$VARNAME` to print a single variable, `SELECT ...;` to run ad-hoc SQL against the current database, `next`/`n` to step one statement at a time, `stack` to inspect the command-list stack, and `help` for a command summary. Silently skipped in non-TTY environments (CI, piped input) so automated pipelines are never blocked.
21
+
22
+ - **`step_mode` on `RuntimeContext`** — internal boolean flag set by the REPL's `next` command; the script engine re-enters the debug REPL after each subsequent statement while step mode is active.
23
+
24
+ - **`ROW_COUNT_GT(table, N)`**, **`ROW_COUNT_GTE(table, N)`**, **`ROW_COUNT_EQ(table, N)`**, **`ROW_COUNT_LT(table, N)`** conditional tests — compare the row count of any table or view against an integer threshold using `IF`, `ELSEIF`, or `ASSERT`. Each issues a `SELECT count(*)` query against the current database. An error is raised if the table does not exist or the threshold is not an integer.
25
+
26
+ ______________________________________________________________________
27
+
28
+ ## [2.9.0] - 2026-04-01
29
+
30
+ ### Added
31
+
32
+ - **`--lint` flag** — parse a script and perform static analysis without connecting to a database or executing anything. Reports unmatched `IF`/`ENDIF`, `LOOP`/`END LOOP`, and `BEGIN BATCH`/`END BATCH` blocks as errors; potentially undefined `!!$VAR!!` variable references and missing `INCLUDE` file targets as warnings. Exits 0 if no errors are found (warnings alone do not affect the exit code); exits 1 if any errors are found. Works with both file scripts and inline `-c` scripts.
33
+ - **`--ping` flag** — test database connectivity without running a script. `execsql --ping --dsn <URL>` connects to the database, queries the server version, prints a one-line success summary (DBMS name, version, and location), and exits 0. On failure it prints the error and exits 1. No script file argument is required when `--ping` is used.
34
+
35
+ ______________________________________________________________________
36
+
16
37
  ## [2.8.0] - 2026-04-01
17
38
 
18
39
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: execsql2
3
- Version: 2.8.0
3
+ Version: 2.10.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
@@ -221,6 +221,7 @@ execsql script.sql # read connection from config file
221
221
  | `-w` | Skip password prompt when a username is supplied |
222
222
  | `--dsn URL` | Connection string (e.g. `postgresql://user:pass@host/db`) |
223
223
  | `--dry-run` | Parse the script and report commands without executing |
224
+ | `--lint` | Static analysis: check structure and warn on issues (no DB) |
224
225
  | `--progress` | Show a progress bar for long-running IMPORT operations |
225
226
  | `--dump-keywords` | Print metacommand keywords as JSON and exit |
226
227
  | `--gui-framework {tkinter,textual}` | GUI framework for interactive prompts |
@@ -111,6 +111,7 @@ execsql script.sql # read connection from config file
111
111
  | `-w` | Skip password prompt when a username is supplied |
112
112
  | `--dsn URL` | Connection string (e.g. `postgresql://user:pass@host/db`) |
113
113
  | `--dry-run` | Parse the script and report commands without executing |
114
+ | `--lint` | Static analysis: check structure and warn on issues (no DB) |
114
115
  | `--progress` | Show a progress bar for long-running IMPORT operations |
115
116
  | `--dump-keywords` | Print metacommand keywords as JSON and exit |
116
117
  | `--gui-framework {tkinter,textual}` | GUI framework for interactive prompts |
@@ -24,6 +24,8 @@ ______________________________________________________________________
24
24
  | `--gui-framework` | Select GUI backend: `tkinter` (default) or `textual` (terminal UI). |
25
25
  | `--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
26
  | `--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
+ | `--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. |
28
+ | `--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. |
27
29
 
28
30
  ### Export Formats
29
31
 
@@ -37,11 +39,21 @@ ______________________________________________________________________
37
39
 
38
40
  ### Metacommands
39
41
 
40
- | Metacommand | Description |
41
- | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
42
- | `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. |
43
- | `CONFIG SHOW_PROGRESS` | Enable the Rich progress bar for IMPORT operations at runtime. |
44
- | `CONFIG LOG_SQL` | Enable SQL query audit logging writes executed SQL to the log file. |
42
+ | Metacommand | Description |
43
+ | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
44
+ | `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. |
45
+ | `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. |
46
+ | `CONFIG SHOW_PROGRESS` | Enable the Rich progress bar for IMPORT operations at runtime. |
47
+ | `CONFIG LOG_SQL` | Enable SQL query audit logging — writes executed SQL to the log file. |
48
+
49
+ ### Conditional Tests
50
+
51
+ | Conditional | Description |
52
+ | ------------------------- | -------------------------------------------------------------------------------------------------------- |
53
+ | `ROW_COUNT_GT(table, N)` | True if the number of rows in *table* is strictly greater than *N* (integer). Queries `SELECT count(*)`. |
54
+ | `ROW_COUNT_GTE(table, N)` | True if the number of rows in *table* is greater than or equal to *N*. |
55
+ | `ROW_COUNT_EQ(table, N)` | True if the number of rows in *table* is exactly equal to *N*. |
56
+ | `ROW_COUNT_LT(table, N)` | True if the number of rows in *table* is strictly less than *N*. |
45
57
 
46
58
  ### Configuration Options
47
59
 
@@ -196,6 +196,34 @@ Valid encoding names can be displayed with the `-y` option. See also [Character
196
196
 
197
197
  Substitution variables that are already populated at parse time are expanded in the output: environment variables (`!!&ENV_VAR!!`), `--assign-arg` values (`!!$ARG_1!!`), and built-in start-time variables like `!!$SCRIPT_START_TIME!!` and `!!$USER!!`. Variables that are set during execution — such as `$CURRENT_TIME`, `$DB_NAME`, and `$TIMER` — remain unexpanded because no database connection is established in dry-run mode. Local `~`-prefixed script-scope variables are also left unexpanded.
198
198
 
199
+ `--lint`
200
+
201
+ : Parse the script and perform static analysis without connecting to a database or executing anything. A complement to `--dry-run` focused on structural correctness rather than command display.
202
+
203
+ Checks performed:
204
+
205
+ - **Unmatched IF / ENDIF** — open IF blocks with no closing ENDIF, or orphan ENDIF with no IF (error).
206
+ - **Unmatched LOOP / END LOOP** — open LOOP with no END LOOP, or orphan END LOOP (error).
207
+ - **Unmatched BEGIN BATCH / END BATCH** — open batch with no close, or orphan END BATCH (error).
208
+ - **Potentially undefined variables** — `!!$VAR!!` references where `$VAR` is not a built-in variable, not `$ARG_N`, and was not defined by a preceding `SUB` metacommand in the same script (warning — may be a false-positive if the variable is set in a config file or via `-a`).
209
+ - **Missing INCLUDE files** — `INCLUDE` targets that do not exist on disk relative to the script's directory (warning; `INCLUDE IF EXISTS` targets are never checked).
210
+ - **Empty script** — no commands found (warning).
211
+
212
+ Exits 0 when no errors are found (warnings alone do not affect the exit code). Exits 1 when any errors are found.
213
+
214
+ ```bash
215
+ execsql --lint script.sql
216
+ ```
217
+
218
+ `--ping`
219
+
220
+ : Test database connectivity and exit. Connects to the configured database, queries the server version if possible, and prints a one-line summary on success (exit 0). On failure, prints the error message and exits with code 1. No script file is required — `--ping` can be combined with `--dsn` or other connection flags without specifying a `.sql` file.
221
+
222
+ ```bash
223
+ execsql --ping --dsn postgresql://user:pass@host/db
224
+ execsql --ping --dsn sqlite:///mydb.sqlite
225
+ ```
226
+
199
227
  `--profile`
200
228
 
201
229
  : 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.
@@ -28,7 +28,7 @@ The section and property names that may be used in a configuration file are list
28
28
  : The database server name. This is equivalent to the second command-line argument for client-server databases.
29
29
 
30
30
  `db`
31
- : The database name. This is equivalent to the third command-line argument for client-server databases
31
+ : The database name. This is equivalent to the third command-line argument for client-server databases. The alias `database` is also accepted.
32
32
 
33
33
  `db_file`
34
34
  : The name of the database file. This is equivalent to the second command-line argument for file-based databases.
@@ -132,9 +132,9 @@ The section and property names that may be used in a configuration file are list
132
132
 
133
133
  : Controls how often row-count progress is written to the execution log during IMPORT operations. Set to a positive integer N to log a status line every N rows (e.g. `import_progress_interval = 10000`). The default is `0` (silent). When enabled, a final completion line (e.g. "IMPORT into schema.table complete: 1000000 rows imported.") is also written. Supported for all database adapters.
134
134
 
135
- `import_only_common_columns` { #import_only_common }
135
+ `import_common_columns_only` { #import_only_common }
136
136
 
137
- : Determines whether the [IMPORT](metacommands.md#import) metacommand will import data from a CSV file when the file has more data columns than the target table. The property value should be either "Yes" or "No". The default, "No", indicates that the target table must have all of the columns present in the CSV file; if the target table has fewer columns, an error will result. A property value of "Yes" will result in import of only the columns in common between the CSV file and the target table.
137
+ : Determines whether the [IMPORT](metacommands.md#import) metacommand will import data from a CSV file when the file has more data columns than the target table. The property value should be either "Yes" or "No". The default, "No", indicates that the target table must have all of the columns present in the CSV file; if the target table has fewer columns, an error will result. A property value of "Yes" will result in import of only the columns in common between the CSV file and the target table. The legacy alias `import_only_common_columns` is also accepted.
138
138
 
139
139
  `import_row_buffer`
140
140
 
@@ -171,7 +171,7 @@ The section and property names that may be used in a configuration file are list
171
171
  ## Section `output` { #config_output }
172
172
 
173
173
  `log_write_messages`
174
- : Specifies whether output of the [WRITE](metacommands.md#write) metacommand will also be written to *execsql*'s log file. The property value should be either "Yes" or "No". This configuration property can also be controlled within a script with the [CONFIG LOG_WRITE_MESSAGES](metacommands.md#logwritemessages) metacommand.
174
+ : Specifies whether output of the [WRITE](metacommands.md#write) metacommand will also be written to *execsql*'s log file. The property value should be either "Yes" or "No". The default is "No". This configuration property can also be controlled within a script with the [CONFIG LOG_WRITE_MESSAGES](metacommands.md#logwritemessages) metacommand.
175
175
 
176
176
  `make_export_dirs`
177
177
  : The output directories used in the [EXPORT](metacommands.md#export) and [WRITE](metacommands.md#write) metacommands will be automatically created if they do not exist (and the user has the necessary permission). The property value should be either "Yes" or "No". This is equivalent to the "-d" command-line option.
@@ -186,12 +186,12 @@ The section and property names that may be used in a configuration file are list
186
186
  : The number of data rows to be buffered from the database when exporting data. Larger values result in faster exports, up to a point, and at a diminishing rate of return. Larger values also require more memory. The setting value must be a positive integer greater than zero. The default value is 1000 rows. This value cannot be customized when using DuckDB.
187
187
 
188
188
  `hdf5_text_len`
189
- : The length to be assigned to columns that have the 'text' data type when data are exported in the HDF5 format.
189
+ : The length to be assigned to columns that have the 'text' data type when data are exported in the HDF5 format. The default is `1000`.
190
190
 
191
191
  `css_file`
192
192
  : The URI of a CSS file to be included in the header of an HTML file created with the [EXPORT](metacommands.md#export) metacommand. If this is specified, it will replace the CSS styles that *execsql* would otherwise use.
193
193
 
194
- `css_style`
194
+ `css_styles`
195
195
  : A set of CSS style specifications to be included in the header of an HTML file created with the [EXPORT](metacommands.md#export) metacommand. If this is specified, it will replace the CSS styles that *execsql* would otherwise use. Both css_file and css_style may be specified; if they are, they will be included in the header of the HTML file in that order.
196
196
 
197
197
  `template_processor`
@@ -240,6 +240,10 @@ The section and property names that may be used in a configuration file are list
240
240
  - 2: Also use a GUI dialog if a message is included with the [HALT](metacommands.md#halt) metacommand, and prompt for the initial database to use if no database connection parameters are specified in a configuration file or on the command line.
241
241
  - 3: Additionally, open a GUI console when *execsql* starts.
242
242
 
243
+ `gui_framework` { #gui_framework }
244
+
245
+ : The GUI framework to use when `gui_level` is greater than 0. The property value must be either `tkinter` (the default) or `textual`. `tkinter` uses native desktop dialogs via Tk; `textual` provides a terminal-based UI that works in headless/SSH environments. This can also be set via the `--gui-framework` command-line option.
246
+
243
247
  ## Section `email`
244
248
 
245
249
  `host`
@@ -305,7 +309,7 @@ The section and property names that may be used in a configuration file are list
305
309
  : The full name or path to an additional configuration file to be read. If only a path is specified, the name of the configuration file should be `execsql.conf`. The configuration file specified will be read immediately following the configuration file in which it is named. No configuration file will be read more than once. If the name or path are invalid, this setting will be silently ignored. This setting may include [substitution variables](substitution_vars.md#substitution_vars); at the time that configuration files are read, however, only environment variables and system variables related to the script name and path are defined.
306
310
 
307
311
  `dao_flush_delay_secs`
308
- : The number of seconds that *execsql* should wait between the time that a query is created in Access (which uses DAO) and the time that the next statement is executed using ODBC. This value must be greater than or equal to 5.0.
312
+ : The number of seconds that *execsql* should wait between the time that a query is created in Access (which uses DAO) and the time that the next statement is executed using ODBC. This value must be greater than or equal to 5.0. The default is `5.0`.
309
313
 
310
314
  `linux_config_file`
311
315
  : The full name or path to an additional configuration file to be read if *execsql* is running on Linux. If only a path is specified, the name of the configuration file should be `execsql.conf`. The configuration file specified will be read immediately following the configuration file in which it is named. No configuration file will be read more than once. If the name or path are invalid, this setting will be silently ignored. This setting may include [substitution variables](substitution_vars.md#substitution_vars); at the time that configuration files are read, however, only environment variables and system variables related to the script name and path are defined.
@@ -60,14 +60,18 @@ Evaluates `<condition>` using the same expression engine as [IF](#if_cmd). If th
60
60
 
61
61
  When [HALT_ON_METACOMMAND_ERROR](#config) is `ON` (the default), a failed assertion halts the script. When it is `OFF`, execution continues after the failure is logged.
62
62
 
63
- The `<condition>` supports all conditional tests available to `IF`, including:
63
+ The `<condition>` supports all [conditional tests](metacommands.md#conditional_tests) available to `IF`, including:
64
64
 
65
- - `TABLE_EXISTS <table>` / `TABLE_NOT_EXISTS <table>`
66
- - `COLUMN_EXISTS <table> <column>` / `COLUMN_NOT_EXISTS <table> <column>`
67
- - `ROWCOUNT <op> <n>` (e.g. `ROWCOUNT > 0`)
68
- - Variable comparisons: `$varname = 'value'`, `$varname != 'value'`
69
- - Numeric comparisons: `$varname > <n>`, `$varname <= <n>`
70
- - `DATABASE_TYPE <type>` / `DATABASE_TYPE_NOT <type>`
65
+ - `TABLE_EXISTS(<table>)` / `NOT TABLE_EXISTS(<table>)`
66
+ - `COLUMN_EXISTS(<table>, <column>)` / `NOT COLUMN_EXISTS(<table>, <column>)`
67
+ - `HAS_ROWS(<table>)` / `NOT HAS_ROWS(<table>)`
68
+ - `EQUAL(<val1>, <val2>)` / `NOT EQUAL(<val1>, <val2>)`
69
+ - `IS_GT(<val1>, <val2>)`, `IS_GTE(<val1>, <val2>)`, `IS_ZERO(<val>)`
70
+ - `ROW_COUNT_GT(<table>, N)`, `ROW_COUNT_GTE(<table>, N)`, `ROW_COUNT_EQ(<table>, N)`, `ROW_COUNT_LT(<table>, N)`
71
+ - `DBMS(<type>)`, `SCHEMA_EXISTS(<schema>)`, `VIEW_EXISTS(<view>)`
72
+ - Boolean combinators: `AND`, `OR`, `NOT`
73
+
74
+ Substitution variables in the condition are expanded before evaluation. Use the `!!varname!!` syntax (e.g., `!!$myvar!!`).
71
75
 
72
76
  ASSERT is silently skipped inside a `False` [IF](#if_cmd) block.
73
77
 
@@ -75,13 +79,17 @@ ASSERT is silently skipped inside a `False` [IF](#if_cmd) block.
75
79
 
76
80
  ```sql
77
81
  -- Halt with a custom message if the staging table is missing.
78
- -- !x! ASSERT TABLE_EXISTS staging "staging table must exist before running this script"
82
+ -- !x! ASSERT TABLE_EXISTS(staging) "staging table must exist before running this script"
79
83
 
80
- -- Halt with the default message if no rows were returned.
81
- -- !x! ASSERT ROWCOUNT > 0
84
+ -- Halt with the default message if the table has no rows.
85
+ -- !x! ASSERT HAS_ROWS(staging)
82
86
 
83
87
  -- Verify a substitution variable has the expected value.
84
- -- !x! ASSERT $env = 'prod' 'expected production environment'
88
+ -- !x! SUB env prod
89
+ -- !x! ASSERT EQUAL(!!env!!, prod) 'expected production environment'
90
+
91
+ -- Combine conditions with AND/OR/NOT.
92
+ -- !x! ASSERT TABLE_EXISTS(orders) AND HAS_ROWS(orders) "orders table missing or empty"
85
93
  ```
86
94
 
87
95
  ## AUTOCOMMIT
@@ -109,6 +117,43 @@ The AUTOCOMMIT metacommand is database-specific, and affects only the database i
109
117
  The [IMPORT](#import) and [COPY](#copy) metacommands do not commit data changes while AUTOCOMMIT is off (except when the NEW or REPLACEMENT clauses are used with Firebird; in those cases the 'create table' statement that *execsql* generates and runs will be committed). The SQL statements generated by the [IMPORT](#import) and [COPY](#copy) metacommands are sent to the database, however. Therefore the AUTOCOMMIT metacommand is recommended when explicit transaction control is to be applied to the [IMPORT](#import) and [COPY](#copy) metacommands.
110
118
 
111
119
 
120
+ ## BREAKPOINT { #breakpoint }
121
+
122
+ ```
123
+ BREAKPOINT
124
+ ```
125
+
126
+ Pauses script execution and drops into an interactive debug REPL (read-eval-print loop) on the console. Use this to inspect state and step through a script interactively while debugging.
127
+
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
+
130
+ **REPL commands:**
131
+
132
+ | Command | Description |
133
+ |---------|-------------|
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 |
142
+
143
+ Pressing Ctrl-D (EOF) or Ctrl-C (KeyboardInterrupt) at the `execsql debug>` prompt resumes execution, the same as typing `continue`.
144
+
145
+ **Example:**
146
+
147
+ ```sql
148
+ INSERT INTO staging SELECT * FROM raw_data;
149
+ -- !x! BREAKPOINT
150
+ -- Script pauses here. Inspect variables, run queries, then continue.
151
+ SELECT count(*) FROM staging;
152
+ ```
153
+
154
+ BREAKPOINT is silently skipped inside a `False` [IF](#if_cmd) block.
155
+
156
+
112
157
  ## BEGIN BATCH and END BATCH { #batch }
113
158
 
114
159
  ```
@@ -1428,6 +1473,62 @@ IS_GTE(<value1>, <value2>)
1428
1473
  Evaluates whether or not the first of the specified values is greater than or equal to the second value. If the values are not numeric, an error will occur, and script processing will halt.
1429
1474
 
1430
1475
 
1476
+ ### *ROW_COUNT_GT* { #row_count_gt }
1477
+
1478
+ ```
1479
+ ROW_COUNT_GT(<table_name>, <N>)
1480
+ ```
1481
+
1482
+ Evaluates whether the number of rows in the specified table or view is strictly greater than the integer *N*. Issues a `SELECT count(*)` query against the current database. An error will occur if the table does not exist or is not accessible.
1483
+
1484
+
1485
+ ### *ROW_COUNT_GTE* { #row_count_gte }
1486
+
1487
+ ```
1488
+ ROW_COUNT_GTE(<table_name>, <N>)
1489
+ ```
1490
+
1491
+ Evaluates whether the number of rows in the specified table or view is greater than or equal to the integer *N*.
1492
+
1493
+
1494
+ ### *ROW_COUNT_EQ* { #row_count_eq }
1495
+
1496
+ ```
1497
+ ROW_COUNT_EQ(<table_name>, <N>)
1498
+ ```
1499
+
1500
+ Evaluates whether the number of rows in the specified table or view is exactly equal to the integer *N*.
1501
+
1502
+
1503
+ ### *ROW_COUNT_LT* { #row_count_lt }
1504
+
1505
+ ```
1506
+ ROW_COUNT_LT(<table_name>, <N>)
1507
+ ```
1508
+
1509
+ Evaluates whether the number of rows in the specified table or view is strictly less than the integer *N*.
1510
+
1511
+ **Examples:**
1512
+
1513
+ ```sql
1514
+ -- Halt if the orders table is empty.
1515
+ -- !x! ASSERT ROW_COUNT_GT(orders, 0) "orders table must not be empty"
1516
+
1517
+ -- Only process if staging has at least 1000 rows.
1518
+ -- !x! IF (ROW_COUNT_GTE(staging, 1000))
1519
+ -- !x! WRITE "staging is ready"
1520
+ -- !x! ENDIF
1521
+
1522
+ -- Verify exactly 12 monthly records were loaded.
1523
+ -- !x! ASSERT ROW_COUNT_EQ(monthly_totals, 12) "expected 12 monthly rows"
1524
+
1525
+ -- Skip cleanup if the temp table already has fewer than 100 rows.
1526
+ -- !x! IF (ROW_COUNT_LT(temp_work, 100))
1527
+ -- !x! WRITE "temp_work is small, skipping truncate"
1528
+ -- !x! ENDIF
1529
+ ```
1530
+
1531
+
1431
1532
  ### *IS_NULL*
1432
1533
 
1433
1534
  ```
@@ -93,7 +93,7 @@
93
93
  },
94
94
  "action-keywords": {
95
95
  "comment": "sub, write, execute script, export, etc.",
96
- "match": "(?i)\\b(reset\\s+dialog_canceled|write\\s+create_table|export_metadata|sub_querystring|execute\\s+script|append\\s+script|extend\\s+script|reset\\s+counter|export\\s+query|sub_tempfile|write\\s+script|import_file|set\\s+counter|sub_decrypt|sub_encrypt|autocommit|copy\\s+query|disconnect|select_sub|sub_append|system_cmd|pg_vacuum|sub_empty|sub_local|connect|include|max_int|rm_file|subdata|sub_add|sub_ini|assert|export|import|rm_sub|debug|email|serve|write|copy|log|run|sub|use|zip|cd)\\b",
96
+ "match": "(?i)\\b(reset\\s+dialog_canceled|write\\s+create_table|export_metadata|sub_querystring|execute\\s+script|append\\s+script|extend\\s+script|reset\\s+counter|export\\s+query|sub_tempfile|write\\s+script|import_file|set\\s+counter|sub_decrypt|sub_encrypt|autocommit|breakpoint|copy\\s+query|disconnect|select_sub|sub_append|system_cmd|pg_vacuum|sub_empty|sub_local|connect|include|max_int|rm_file|subdata|sub_add|sub_ini|assert|export|import|rm_sub|debug|email|serve|write|copy|log|run|sub|use|zip|cd)\\b",
97
97
  "name": "keyword.other.execsql"
98
98
  },
99
99
  "config-event-keywords": {
@@ -108,7 +108,7 @@
108
108
  },
109
109
  "builtin-functions": {
110
110
  "comment": "Conditional test functions used in if/elseif",
111
- "match": "(?i)\\b(metacommand_error|directory_exists|dialog_canceled|alias_defined|column_exists|database_name|schema_exists|script_exists|table_exists|file_exists|role_exists|starts_with|sub_defined|view_exists|console_on|newer_date|newer_file|ends_with|identical|sql_error|sub_empty|contains|is_false|hasrows|is_null|is_true|is_zero|is_gte|equal|is_gt|dbms|not|or)\\b",
111
+ "match": "(?i)\\b(metacommand_error|directory_exists|dialog_canceled|alias_defined|column_exists|database_name|row_count_gte|schema_exists|script_exists|row_count_eq|row_count_gt|row_count_lt|table_exists|file_exists|role_exists|starts_with|sub_defined|view_exists|console_on|newer_date|newer_file|ends_with|identical|sql_error|sub_empty|contains|is_false|hasrows|is_null|is_true|is_zero|is_gte|equal|is_gt|dbms|not|or)\\b",
112
112
  "name": "support.function.execsql"
113
113
  },
114
114
  "config-option-names": {
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "execsql2"
7
- version = "2.8.0"
7
+ version = "2.10.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.8.0"
161
+ current_version = "2.10.0"
162
162
  commit = true
163
163
  commit_args = "--no-verify"
164
164
  tag = true
@@ -223,6 +223,25 @@ def main(
223
223
  "--dry-run",
224
224
  help=("Parse the script and print the command list without connecting to a database or executing anything."),
225
225
  ),
226
+ lint: bool = typer.Option(
227
+ False,
228
+ "--lint",
229
+ help=(
230
+ "Parse the script and perform static analysis without connecting to a database or executing anything. "
231
+ "Reports unmatched IF/ENDIF/LOOP/BATCH blocks (errors), potentially undefined variables, "
232
+ "and missing INCLUDE files (warnings). Exits 0 if no errors, 1 if errors found."
233
+ ),
234
+ ),
235
+ ping: bool = typer.Option(
236
+ False,
237
+ "--ping",
238
+ help=(
239
+ "Test database connectivity and exit. "
240
+ "Prints connection details and the server version on success (exit 0), "
241
+ "or the error message on failure (exit 1). "
242
+ "No script file is required."
243
+ ),
244
+ ),
226
245
  dsn: str | None = typer.Option(
227
246
  None,
228
247
  "--dsn",
@@ -350,6 +369,10 @@ def main(
350
369
  positional = args or []
351
370
  if command is not None:
352
371
  script_name = None # inline mode — no script file
372
+ elif ping:
373
+ # --ping does not require a script file; positional args are still
374
+ # available for server/db arguments if --dsn is not used.
375
+ script_name = None
353
376
  else:
354
377
  if not positional:
355
378
  _err_console.print(
@@ -421,6 +444,8 @@ def main(
421
444
  output_dir=output_dir,
422
445
  progress=progress,
423
446
  profile=profile,
447
+ ping=ping,
448
+ lint=lint,
424
449
  )
425
450
 
426
451