execsql2 2.7.1__tar.gz → 2.9.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 (290) hide show
  1. {execsql2-2.7.1 → execsql2-2.9.0}/.claude/project_context.md +69 -10
  2. {execsql2-2.7.1 → execsql2-2.9.0}/CHANGELOG.md +22 -0
  3. {execsql2-2.7.1 → execsql2-2.9.0}/PKG-INFO +3 -1
  4. {execsql2-2.7.1 → execsql2-2.9.0}/README.md +2 -0
  5. {execsql2-2.7.1 → execsql2-2.9.0}/docs/about/divergence.md +18 -14
  6. {execsql2-2.7.1 → execsql2-2.9.0}/docs/getting-started/syntax.md +40 -0
  7. {execsql2-2.7.1 → execsql2-2.9.0}/docs/reference/configuration.md +11 -7
  8. {execsql2-2.7.1 → execsql2-2.9.0}/docs/reference/metacommands.md +43 -0
  9. {execsql2-2.7.1 → execsql2-2.9.0}/extras/vscode-execsql/syntaxes/execsql.tmLanguage.json +1 -1
  10. {execsql2-2.7.1 → execsql2-2.9.0}/pyproject.toml +2 -2
  11. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/cli/__init__.py +31 -0
  12. execsql2-2.9.0/src/execsql/cli/lint.py +459 -0
  13. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/cli/run.py +195 -15
  14. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/metacommands/__init__.py +2 -0
  15. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/metacommands/control.py +33 -0
  16. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/metacommands/dispatch.py +31 -0
  17. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/script/engine.py +16 -0
  18. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/state.py +10 -0
  19. {execsql2-2.7.1 → execsql2-2.9.0}/tests/cli/test_cli.py +55 -0
  20. execsql2-2.9.0/tests/cli/test_lint.py +473 -0
  21. execsql2-2.9.0/tests/cli/test_ping.py +315 -0
  22. execsql2-2.9.0/tests/cli/test_profile.py +282 -0
  23. execsql2-2.9.0/tests/metacommands/test_assert.py +197 -0
  24. {execsql2-2.7.1 → execsql2-2.9.0}/uv.lock +1 -1
  25. {execsql2-2.7.1 → execsql2-2.9.0}/.claude/agents/dba.md +0 -0
  26. {execsql2-2.7.1 → execsql2-2.9.0}/.claude/agents/herald.md +0 -0
  27. {execsql2-2.7.1 → execsql2-2.9.0}/.claude/agents/inspector.md +0 -0
  28. {execsql2-2.7.1 → execsql2-2.9.0}/.claude/agents/oracle.md +0 -0
  29. {execsql2-2.7.1 → execsql2-2.9.0}/.claude/agents/patcher.md +0 -0
  30. {execsql2-2.7.1 → execsql2-2.9.0}/.claude/agents/qa.md +0 -0
  31. {execsql2-2.7.1 → execsql2-2.9.0}/.claude/agents/scribe.md +0 -0
  32. {execsql2-2.7.1 → execsql2-2.9.0}/.claude/commands/code-oracle.md +0 -0
  33. {execsql2-2.7.1 → execsql2-2.9.0}/.claude/commands/migrate.md +0 -0
  34. {execsql2-2.7.1 → execsql2-2.9.0}/.claude/commands/review-changes.md +0 -0
  35. {execsql2-2.7.1 → execsql2-2.9.0}/.claude/commands/test-module.md +0 -0
  36. {execsql2-2.7.1 → execsql2-2.9.0}/.claude/commands/update-changelog.md +0 -0
  37. {execsql2-2.7.1 → execsql2-2.9.0}/.claude/commands/where-is.md +0 -0
  38. {execsql2-2.7.1 → execsql2-2.9.0}/.claude/state/status.md +0 -0
  39. {execsql2-2.7.1 → execsql2-2.9.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  40. {execsql2-2.7.1 → execsql2-2.9.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  41. {execsql2-2.7.1 → execsql2-2.9.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  42. {execsql2-2.7.1 → execsql2-2.9.0}/.github/workflows/ci-cd.yml +0 -0
  43. {execsql2-2.7.1 → execsql2-2.9.0}/.gitignore +0 -0
  44. {execsql2-2.7.1 → execsql2-2.9.0}/.pre-commit-config.yaml +0 -0
  45. {execsql2-2.7.1 → execsql2-2.9.0}/.pre-commit-hooks.yaml +0 -0
  46. {execsql2-2.7.1 → execsql2-2.9.0}/.python-version +0 -0
  47. {execsql2-2.7.1 → execsql2-2.9.0}/.readthedocs.yaml +0 -0
  48. {execsql2-2.7.1 → execsql2-2.9.0}/CLAUDE.md +0 -0
  49. {execsql2-2.7.1 → execsql2-2.9.0}/CONTRIBUTING.md +0 -0
  50. {execsql2-2.7.1 → execsql2-2.9.0}/LICENSE.txt +0 -0
  51. {execsql2-2.7.1 → execsql2-2.9.0}/NOTICE +0 -0
  52. {execsql2-2.7.1 → execsql2-2.9.0}/SECURITY.md +0 -0
  53. {execsql2-2.7.1 → execsql2-2.9.0}/docs/about/contributors.md +0 -0
  54. {execsql2-2.7.1 → execsql2-2.9.0}/docs/about/copyright.md +0 -0
  55. {execsql2-2.7.1 → execsql2-2.9.0}/docs/api/cli.md +0 -0
  56. {execsql2-2.7.1 → execsql2-2.9.0}/docs/api/db.md +0 -0
  57. {execsql2-2.7.1 → execsql2-2.9.0}/docs/api/exporters.md +0 -0
  58. {execsql2-2.7.1 → execsql2-2.9.0}/docs/api/importers.md +0 -0
  59. {execsql2-2.7.1 → execsql2-2.9.0}/docs/api/index.md +0 -0
  60. {execsql2-2.7.1 → execsql2-2.9.0}/docs/api/metacommands.md +0 -0
  61. {execsql2-2.7.1 → execsql2-2.9.0}/docs/dev/adding_db_adapters.md +0 -0
  62. {execsql2-2.7.1 → execsql2-2.9.0}/docs/dev/adding_exporters.md +0 -0
  63. {execsql2-2.7.1 → execsql2-2.9.0}/docs/dev/adding_importers.md +0 -0
  64. {execsql2-2.7.1 → execsql2-2.9.0}/docs/dev/adding_metacommands.md +0 -0
  65. {execsql2-2.7.1 → execsql2-2.9.0}/docs/dev/architecture.md +0 -0
  66. {execsql2-2.7.1 → execsql2-2.9.0}/docs/getting-started/installation.md +0 -0
  67. {execsql2-2.7.1 → execsql2-2.9.0}/docs/getting-started/requirements.md +0 -0
  68. {execsql2-2.7.1 → execsql2-2.9.0}/docs/guides/debugging.md +0 -0
  69. {execsql2-2.7.1 → execsql2-2.9.0}/docs/guides/documentation.md +0 -0
  70. {execsql2-2.7.1 → execsql2-2.9.0}/docs/guides/encoding.md +0 -0
  71. {execsql2-2.7.1 → execsql2-2.9.0}/docs/guides/examples.md +0 -0
  72. {execsql2-2.7.1 → execsql2-2.9.0}/docs/guides/formatter.md +0 -0
  73. {execsql2-2.7.1 → execsql2-2.9.0}/docs/guides/logging.md +0 -0
  74. {execsql2-2.7.1 → execsql2-2.9.0}/docs/guides/sql_syntax.md +0 -0
  75. {execsql2-2.7.1 → execsql2-2.9.0}/docs/guides/usage.md +0 -0
  76. {execsql2-2.7.1 → execsql2-2.9.0}/docs/guides/using_scripts.md +0 -0
  77. {execsql2-2.7.1 → execsql2-2.9.0}/docs/images/Compare_planets.png +0 -0
  78. {execsql2-2.7.1 → execsql2-2.9.0}/docs/images/actions.png +0 -0
  79. {execsql2-2.7.1 → execsql2-2.9.0}/docs/images/actions2.png +0 -0
  80. {execsql2-2.7.1 → execsql2-2.9.0}/docs/images/checkboxes.png +0 -0
  81. {execsql2-2.7.1 → execsql2-2.9.0}/docs/images/connect.b64 +0 -0
  82. {execsql2-2.7.1 → execsql2-2.9.0}/docs/images/connect.png +0 -0
  83. {execsql2-2.7.1 → execsql2-2.9.0}/docs/images/create_conf.png +0 -0
  84. {execsql2-2.7.1 → execsql2-2.9.0}/docs/images/data_error1_screenshot.jpg +0 -0
  85. {execsql2-2.7.1 → execsql2-2.9.0}/docs/images/entry_form.png +0 -0
  86. {execsql2-2.7.1 → execsql2-2.9.0}/docs/images/execsql_console.png +0 -0
  87. {execsql2-2.7.1 → execsql2-2.9.0}/docs/images/execsql_logo_01.png +0 -0
  88. {execsql2-2.7.1 → execsql2-2.9.0}/docs/images/fatals.png +0 -0
  89. {execsql2-2.7.1 → execsql2-2.9.0}/docs/images/logo_small.png +0 -0
  90. {execsql2-2.7.1 → execsql2-2.9.0}/docs/images/pause_terminal.png +0 -0
  91. {execsql2-2.7.1 → execsql2-2.9.0}/docs/images/pause_terminal_sm.b64 +0 -0
  92. {execsql2-2.7.1 → execsql2-2.9.0}/docs/images/pause_terminal_sm.png +0 -0
  93. {execsql2-2.7.1 → execsql2-2.9.0}/docs/images/prompt_compare.png +0 -0
  94. {execsql2-2.7.1 → execsql2-2.9.0}/docs/images/set_build_commands.jpg +0 -0
  95. {execsql2-2.7.1 → execsql2-2.9.0}/docs/images/unit_conversions.b64 +0 -0
  96. {execsql2-2.7.1 → execsql2-2.9.0}/docs/images/unit_conversions_029.png +0 -0
  97. {execsql2-2.7.1 → execsql2-2.9.0}/docs/images/unmatched.png +0 -0
  98. {execsql2-2.7.1 → execsql2-2.9.0}/docs/images/vim_execsql_highlight.png +0 -0
  99. {execsql2-2.7.1 → execsql2-2.9.0}/docs/index.md +0 -0
  100. {execsql2-2.7.1 → execsql2-2.9.0}/docs/reference/security.md +0 -0
  101. {execsql2-2.7.1 → execsql2-2.9.0}/docs/reference/substitution_vars.md +0 -0
  102. {execsql2-2.7.1 → execsql2-2.9.0}/extras/vscode-execsql/README.md +0 -0
  103. {execsql2-2.7.1 → execsql2-2.9.0}/extras/vscode-execsql/package.json +0 -0
  104. {execsql2-2.7.1 → execsql2-2.9.0}/justfile +0 -0
  105. {execsql2-2.7.1 → execsql2-2.9.0}/scripts/generate_vscode_grammar.py +0 -0
  106. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/__init__.py +0 -0
  107. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/__main__.py +0 -0
  108. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/cli/dsn.py +0 -0
  109. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/cli/help.py +0 -0
  110. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/config.py +0 -0
  111. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/constants.py +0 -0
  112. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/db/__init__.py +0 -0
  113. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/db/access.py +0 -0
  114. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/db/base.py +0 -0
  115. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/db/dsn.py +0 -0
  116. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/db/duckdb.py +0 -0
  117. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/db/factory.py +0 -0
  118. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/db/firebird.py +0 -0
  119. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/db/mysql.py +0 -0
  120. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/db/oracle.py +0 -0
  121. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/db/postgres.py +0 -0
  122. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/db/sqlite.py +0 -0
  123. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/db/sqlserver.py +0 -0
  124. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/exceptions.py +0 -0
  125. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/exporters/__init__.py +0 -0
  126. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/exporters/base.py +0 -0
  127. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/exporters/delimited.py +0 -0
  128. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/exporters/duckdb.py +0 -0
  129. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/exporters/feather.py +0 -0
  130. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/exporters/html.py +0 -0
  131. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/exporters/json.py +0 -0
  132. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/exporters/latex.py +0 -0
  133. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/exporters/markdown.py +0 -0
  134. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/exporters/ods.py +0 -0
  135. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/exporters/parquet.py +0 -0
  136. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/exporters/pretty.py +0 -0
  137. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/exporters/protocol.py +0 -0
  138. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/exporters/raw.py +0 -0
  139. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/exporters/sqlite.py +0 -0
  140. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/exporters/templates.py +0 -0
  141. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/exporters/values.py +0 -0
  142. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/exporters/xls.py +0 -0
  143. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/exporters/xlsx.py +0 -0
  144. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/exporters/xml.py +0 -0
  145. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/exporters/yaml.py +0 -0
  146. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/exporters/zip.py +0 -0
  147. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/format.py +0 -0
  148. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/gui/__init__.py +0 -0
  149. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/gui/base.py +0 -0
  150. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/gui/console.py +0 -0
  151. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/gui/desktop.py +0 -0
  152. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/gui/tui.py +0 -0
  153. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/importers/__init__.py +0 -0
  154. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/importers/base.py +0 -0
  155. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/importers/csv.py +0 -0
  156. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/importers/feather.py +0 -0
  157. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/importers/ods.py +0 -0
  158. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/importers/xls.py +0 -0
  159. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/metacommands/conditions.py +0 -0
  160. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/metacommands/connect.py +0 -0
  161. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/metacommands/data.py +0 -0
  162. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/metacommands/debug.py +0 -0
  163. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/metacommands/io.py +0 -0
  164. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/metacommands/io_export.py +0 -0
  165. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/metacommands/io_fileops.py +0 -0
  166. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/metacommands/io_import.py +0 -0
  167. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/metacommands/io_write.py +0 -0
  168. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/metacommands/prompt.py +0 -0
  169. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/metacommands/script_ext.py +0 -0
  170. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/metacommands/system.py +0 -0
  171. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/models.py +0 -0
  172. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/parser.py +0 -0
  173. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/py.typed +0 -0
  174. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/script/__init__.py +0 -0
  175. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/script/control.py +0 -0
  176. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/script/variables.py +0 -0
  177. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/types.py +0 -0
  178. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/utils/__init__.py +0 -0
  179. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/utils/auth.py +0 -0
  180. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/utils/crypto.py +0 -0
  181. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/utils/datetime.py +0 -0
  182. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/utils/errors.py +0 -0
  183. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/utils/fileio.py +0 -0
  184. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/utils/gui.py +0 -0
  185. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/utils/mail.py +0 -0
  186. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/utils/numeric.py +0 -0
  187. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/utils/regex.py +0 -0
  188. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/utils/strings.py +0 -0
  189. {execsql2-2.7.1 → execsql2-2.9.0}/src/execsql/utils/timer.py +0 -0
  190. {execsql2-2.7.1 → execsql2-2.9.0}/templates/README.md +0 -0
  191. {execsql2-2.7.1 → execsql2-2.9.0}/templates/config_settings.sqlite +0 -0
  192. {execsql2-2.7.1 → execsql2-2.9.0}/templates/example_config_prompt.sql +0 -0
  193. {execsql2-2.7.1 → execsql2-2.9.0}/templates/execsql.conf +0 -0
  194. {execsql2-2.7.1 → execsql2-2.9.0}/templates/make_config_db.sql +0 -0
  195. {execsql2-2.7.1 → execsql2-2.9.0}/templates/md_compare.sql +0 -0
  196. {execsql2-2.7.1 → execsql2-2.9.0}/templates/md_glossary.sql +0 -0
  197. {execsql2-2.7.1 → execsql2-2.9.0}/templates/md_upsert.sql +0 -0
  198. {execsql2-2.7.1 → execsql2-2.9.0}/templates/pg_compare.sql +0 -0
  199. {execsql2-2.7.1 → execsql2-2.9.0}/templates/pg_glossary.sql +0 -0
  200. {execsql2-2.7.1 → execsql2-2.9.0}/templates/pg_upsert.sql +0 -0
  201. {execsql2-2.7.1 → execsql2-2.9.0}/templates/script_template.sql +0 -0
  202. {execsql2-2.7.1 → execsql2-2.9.0}/templates/ss_compare.sql +0 -0
  203. {execsql2-2.7.1 → execsql2-2.9.0}/templates/ss_glossary.sql +0 -0
  204. {execsql2-2.7.1 → execsql2-2.9.0}/templates/ss_upsert.sql +0 -0
  205. {execsql2-2.7.1 → execsql2-2.9.0}/tests/__init__.py +0 -0
  206. {execsql2-2.7.1 → execsql2-2.9.0}/tests/cli/__init__.py +0 -0
  207. {execsql2-2.7.1 → execsql2-2.9.0}/tests/cli/test_cli_e2e.py +0 -0
  208. {execsql2-2.7.1 → execsql2-2.9.0}/tests/cli/test_cli_run.py +0 -0
  209. {execsql2-2.7.1 → execsql2-2.9.0}/tests/conftest.py +0 -0
  210. {execsql2-2.7.1 → execsql2-2.9.0}/tests/db/__init__.py +0 -0
  211. {execsql2-2.7.1 → execsql2-2.9.0}/tests/db/test_base.py +0 -0
  212. {execsql2-2.7.1 → execsql2-2.9.0}/tests/db/test_duckdb.py +0 -0
  213. {execsql2-2.7.1 → execsql2-2.9.0}/tests/db/test_factory.py +0 -0
  214. {execsql2-2.7.1 → execsql2-2.9.0}/tests/db/test_postgres.py +0 -0
  215. {execsql2-2.7.1 → execsql2-2.9.0}/tests/db/test_sqlite.py +0 -0
  216. {execsql2-2.7.1 → execsql2-2.9.0}/tests/db/test_sqlite_extra.py +0 -0
  217. {execsql2-2.7.1 → execsql2-2.9.0}/tests/exporters/__init__.py +0 -0
  218. {execsql2-2.7.1 → execsql2-2.9.0}/tests/exporters/test_base.py +0 -0
  219. {execsql2-2.7.1 → execsql2-2.9.0}/tests/exporters/test_db.py +0 -0
  220. {execsql2-2.7.1 → execsql2-2.9.0}/tests/exporters/test_delimited.py +0 -0
  221. {execsql2-2.7.1 → execsql2-2.9.0}/tests/exporters/test_duckdb_exporter.py +0 -0
  222. {execsql2-2.7.1 → execsql2-2.9.0}/tests/exporters/test_exporters.py +0 -0
  223. {execsql2-2.7.1 → execsql2-2.9.0}/tests/exporters/test_feather.py +0 -0
  224. {execsql2-2.7.1 → execsql2-2.9.0}/tests/exporters/test_html_latex.py +0 -0
  225. {execsql2-2.7.1 → execsql2-2.9.0}/tests/exporters/test_json.py +0 -0
  226. {execsql2-2.7.1 → execsql2-2.9.0}/tests/exporters/test_markdown.py +0 -0
  227. {execsql2-2.7.1 → execsql2-2.9.0}/tests/exporters/test_ods.py +0 -0
  228. {execsql2-2.7.1 → execsql2-2.9.0}/tests/exporters/test_parquet.py +0 -0
  229. {execsql2-2.7.1 → execsql2-2.9.0}/tests/exporters/test_sqlite_exporter.py +0 -0
  230. {execsql2-2.7.1 → execsql2-2.9.0}/tests/exporters/test_templates.py +0 -0
  231. {execsql2-2.7.1 → execsql2-2.9.0}/tests/exporters/test_xls_xlsx.py +0 -0
  232. {execsql2-2.7.1 → execsql2-2.9.0}/tests/exporters/test_xlsx.py +0 -0
  233. {execsql2-2.7.1 → execsql2-2.9.0}/tests/exporters/test_xml.py +0 -0
  234. {execsql2-2.7.1 → execsql2-2.9.0}/tests/exporters/test_yaml.py +0 -0
  235. {execsql2-2.7.1 → execsql2-2.9.0}/tests/exporters/test_zip.py +0 -0
  236. {execsql2-2.7.1 → execsql2-2.9.0}/tests/gui/__init__.py +0 -0
  237. {execsql2-2.7.1 → execsql2-2.9.0}/tests/gui/test_backends.py +0 -0
  238. {execsql2-2.7.1 → execsql2-2.9.0}/tests/importers/__init__.py +0 -0
  239. {execsql2-2.7.1 → execsql2-2.9.0}/tests/importers/test_csv_importer.py +0 -0
  240. {execsql2-2.7.1 → execsql2-2.9.0}/tests/importers/test_feather_importer.py +0 -0
  241. {execsql2-2.7.1 → execsql2-2.9.0}/tests/importers/test_ods_importer.py +0 -0
  242. {execsql2-2.7.1 → execsql2-2.9.0}/tests/importers/test_xls_importer.py +0 -0
  243. {execsql2-2.7.1 → execsql2-2.9.0}/tests/integration/__init__.py +0 -0
  244. {execsql2-2.7.1 → execsql2-2.9.0}/tests/integration/conftest.py +0 -0
  245. {execsql2-2.7.1 → execsql2-2.9.0}/tests/integration/test_dsn.py +0 -0
  246. {execsql2-2.7.1 → execsql2-2.9.0}/tests/integration/test_duckdb.py +0 -0
  247. {execsql2-2.7.1 → execsql2-2.9.0}/tests/integration/test_mysql.py +0 -0
  248. {execsql2-2.7.1 → execsql2-2.9.0}/tests/integration/test_postgres.py +0 -0
  249. {execsql2-2.7.1 → execsql2-2.9.0}/tests/integration/test_sqlite.py +0 -0
  250. {execsql2-2.7.1 → execsql2-2.9.0}/tests/metacommands/__init__.py +0 -0
  251. {execsql2-2.7.1 → execsql2-2.9.0}/tests/metacommands/test_connect.py +0 -0
  252. {execsql2-2.7.1 → execsql2-2.9.0}/tests/metacommands/test_metacommands.py +0 -0
  253. {execsql2-2.7.1 → execsql2-2.9.0}/tests/metacommands/test_metacommands_connect.py +0 -0
  254. {execsql2-2.7.1 → execsql2-2.9.0}/tests/metacommands/test_metacommands_data.py +0 -0
  255. {execsql2-2.7.1 → execsql2-2.9.0}/tests/metacommands/test_metacommands_extended.py +0 -0
  256. {execsql2-2.7.1 → execsql2-2.9.0}/tests/metacommands/test_metacommands_fileops_extra.py +0 -0
  257. {execsql2-2.7.1 → execsql2-2.9.0}/tests/metacommands/test_metacommands_io.py +0 -0
  258. {execsql2-2.7.1 → execsql2-2.9.0}/tests/metacommands/test_metacommands_io_write_extra.py +0 -0
  259. {execsql2-2.7.1 → execsql2-2.9.0}/tests/metacommands/test_metacommands_script_ext.py +0 -0
  260. {execsql2-2.7.1 → execsql2-2.9.0}/tests/metacommands/test_metacommands_system.py +0 -0
  261. {execsql2-2.7.1 → execsql2-2.9.0}/tests/metacommands/test_metacommands_system_extra.py +0 -0
  262. {execsql2-2.7.1 → execsql2-2.9.0}/tests/test_config.py +0 -0
  263. {execsql2-2.7.1 → execsql2-2.9.0}/tests/test_config_data.py +0 -0
  264. {execsql2-2.7.1 → execsql2-2.9.0}/tests/test_constants.py +0 -0
  265. {execsql2-2.7.1 → execsql2-2.9.0}/tests/test_engine.py +0 -0
  266. {execsql2-2.7.1 → execsql2-2.9.0}/tests/test_exceptions.py +0 -0
  267. {execsql2-2.7.1 → execsql2-2.9.0}/tests/test_format.py +0 -0
  268. {execsql2-2.7.1 → execsql2-2.9.0}/tests/test_mail.py +0 -0
  269. {execsql2-2.7.1 → execsql2-2.9.0}/tests/test_models.py +0 -0
  270. {execsql2-2.7.1 → execsql2-2.9.0}/tests/test_package.py +0 -0
  271. {execsql2-2.7.1 → execsql2-2.9.0}/tests/test_parser.py +0 -0
  272. {execsql2-2.7.1 → execsql2-2.9.0}/tests/test_registry.py +0 -0
  273. {execsql2-2.7.1 → execsql2-2.9.0}/tests/test_script.py +0 -0
  274. {execsql2-2.7.1 → execsql2-2.9.0}/tests/test_state.py +0 -0
  275. {execsql2-2.7.1 → execsql2-2.9.0}/tests/test_types.py +0 -0
  276. {execsql2-2.7.1 → execsql2-2.9.0}/tests/utils/__init__.py +0 -0
  277. {execsql2-2.7.1 → execsql2-2.9.0}/tests/utils/test_auth.py +0 -0
  278. {execsql2-2.7.1 → execsql2-2.9.0}/tests/utils/test_auth_extra.py +0 -0
  279. {execsql2-2.7.1 → execsql2-2.9.0}/tests/utils/test_crypto.py +0 -0
  280. {execsql2-2.7.1 → execsql2-2.9.0}/tests/utils/test_datetime.py +0 -0
  281. {execsql2-2.7.1 → execsql2-2.9.0}/tests/utils/test_errors.py +0 -0
  282. {execsql2-2.7.1 → execsql2-2.9.0}/tests/utils/test_errors_extra.py +0 -0
  283. {execsql2-2.7.1 → execsql2-2.9.0}/tests/utils/test_fileio.py +0 -0
  284. {execsql2-2.7.1 → execsql2-2.9.0}/tests/utils/test_fileio_extra.py +0 -0
  285. {execsql2-2.7.1 → execsql2-2.9.0}/tests/utils/test_numeric.py +0 -0
  286. {execsql2-2.7.1 → execsql2-2.9.0}/tests/utils/test_regex.py +0 -0
  287. {execsql2-2.7.1 → execsql2-2.9.0}/tests/utils/test_strings.py +0 -0
  288. {execsql2-2.7.1 → execsql2-2.9.0}/tests/utils/test_timer.py +0 -0
  289. {execsql2-2.7.1 → execsql2-2.9.0}/tests/utils/test_timer_extra.py +0 -0
  290. {execsql2-2.7.1 → execsql2-2.9.0}/zensical.toml +0 -0
@@ -272,31 +272,66 @@ ______________________________________________________________________
272
272
 
273
273
  ### v2.8 — Scripting Power Features
274
274
 
275
- - [ ] **`ASSERT` metacommand** — `-- !x! ASSERT <condition> "message"`. Data validation for CI pipelines and sanity checks.
276
- - [ ] **`--dry-run` improvements** — show SQL with substitution variables expanded, not just raw metacommands.
277
- - [ ] **Script profiling (`--profile`)** — per-statement execution times, summary report at end. Leverages existing `Timer` infrastructure.
278
- - [ ] **Parallel execution blocks**`PARALLEL BEGIN ... PARALLEL END` for independent statements. See design notes below.
275
+ - [x] **`ASSERT` metacommand** — `-- !x! ASSERT <condition> "message"`. Reuses IF condition engine. 13 tests.
276
+ - [x] **`--dry-run` improvements** — expands substitution variables populated at parse time. 4 new tests.
277
+ - [x] **Script profiling (`--profile`)** — per-statement `perf_counter()` timing with sorted summary table. 19 tests.
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,28 @@ ______________________________________________________________________
13
13
 
14
14
  ______________________________________________________________________
15
15
 
16
+ ## [2.9.0] - 2026-04-01
17
+
18
+ ### Added
19
+
20
+ - **`--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.
21
+ - **`--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.
22
+
23
+ ______________________________________________________________________
24
+
25
+ ## [2.8.0] - 2026-04-01
26
+
27
+ ### Added
28
+
29
+ - **`--profile` flag** — records wall-clock time for each SQL and metacommand statement and prints a formatted timing summary after the script completes. The summary lists statements sorted by elapsed time descending (top 20 shown), with per-statement percentage of total time, source location, command type, and a preview of the command text.
30
+ - **`ASSERT` metacommand** — evaluates any IF-compatible condition and raises an error (halting the script when `HALT_ON_METACOMMAND_ERROR` is `ON`) if the condition is false. Supports an optional quoted failure message; omitting the message produces `Assertion failed: <condition>`. A passing assertion is logged. ASSERT is silently skipped inside a false IF block.
31
+
32
+ ### Changed
33
+
34
+ - **`--dry-run` expands substitution variables** — the command list printed by `--dry-run` now shows resolved `!!$VAR!!` / `!!&ENV!!` tokens for variables that are already populated at parse time (environment variables, `--assign-arg` values, config-sourced variables, and built-in start-time variables like `$SCRIPT_START_TIME`). Variables that are set during execution (e.g. `$CURRENT_TIME`, `$DB_NAME`, `$TIMER`) remain unexpanded because the database connection has not yet been established. Local `~`-prefixed script-scope variables are also left unexpanded. If expansion fails (e.g. a cycle is detected), the raw token is displayed instead.
35
+
36
+ ______________________________________________________________________
37
+
16
38
  ## [2.7.1] - 2026-04-01
17
39
 
18
40
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: execsql2
3
- Version: 2.7.1
3
+ Version: 2.9.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 |
@@ -233,6 +234,7 @@ Run `execsql --help` for the full option list, or `execsql -m` to list all metac
233
234
  - Export query results in 20+ formats including CSV, TSV, JSON, YAML, XML, HTML, Markdown, LaTeX, XLSX, OpenDocument, Feather, Parquet, HDF5, DuckDB, SQLite, plain text, and Jinja2 templates.
234
235
  - Copy data between databases, including across different DBMS types.
235
236
  - Conditionally execute SQL and metacommands using `IF`/`ELSE`/`ENDIF` based on data values, DBMS type, or user input.
237
+ - Validate data with `ASSERT` — halt the script with a clear error message if a condition is false (ideal for CI pipelines).
236
238
  - Loop over blocks of SQL and metacommands using `LOOP`/`ENDLOOP`.
237
239
  - Use substitution variables (`SUB`, `$ARG_x`, built-in variables like `$date_tag`) to parameterize scripts.
238
240
  - Include or chain scripts with `INCLUDE` and `SCRIPT`.
@@ -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 |
@@ -123,6 +124,7 @@ Run `execsql --help` for the full option list, or `execsql -m` to list all metac
123
124
  - Export query results in 20+ formats including CSV, TSV, JSON, YAML, XML, HTML, Markdown, LaTeX, XLSX, OpenDocument, Feather, Parquet, HDF5, DuckDB, SQLite, plain text, and Jinja2 templates.
124
125
  - Copy data between databases, including across different DBMS types.
125
126
  - Conditionally execute SQL and metacommands using `IF`/`ELSE`/`ENDIF` based on data values, DBMS type, or user input.
127
+ - Validate data with `ASSERT` — halt the script with a clear error message if a condition is false (ideal for CI pipelines).
126
128
  - Loop over blocks of SQL and metacommands using `LOOP`/`ENDLOOP`.
127
129
  - Use substitution variables (`SUB`, `$ARG_x`, built-in variables like `$date_tag`) to parameterize scripts.
128
130
  - Include or chain scripts with `INCLUDE` and `SCRIPT`.
@@ -13,16 +13,19 @@ ______________________________________________________________________
13
13
 
14
14
  ### CLI Options
15
15
 
16
- | Flag | Description |
17
- | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
18
- | `--version` | Print version and exit (Rich-formatted). |
19
- | `-c` / `--command` | Execute an inline SQL or metacommand string instead of a script file. |
20
- | `--dsn` / `--connection-string` | Accept a standard database URL (e.g. `postgresql://user:pass@host/db`). Supports `postgresql`, `mysql`, `mssql`, `oracle`, `firebird`, `sqlite`, and `duckdb` schemes. |
21
- | `--output-dir` | Set a default base directory for export output files. |
22
- | `--progress` | Show a Rich progress bar during long-running IMPORT operations. |
23
- | `--dump-keywords` | Emit all metacommand keywords, conditionals, config options, and export formats as structured JSON. |
24
- | `--gui-framework` | Select GUI backend: `tkinter` (default) or `textual` (terminal UI). |
25
- | `--dry-run` | Parse the script and print the full command list without connecting to a database or executing anything. |
16
+ | Flag | Description |
17
+ | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
18
+ | `--version` | Print version and exit (Rich-formatted). |
19
+ | `-c` / `--command` | Execute an inline SQL or metacommand string instead of a script file. |
20
+ | `--dsn` / `--connection-string` | Accept a standard database URL (e.g. `postgresql://user:pass@host/db`). Supports `postgresql`, `mysql`, `mssql`, `oracle`, `firebird`, `sqlite`, and `duckdb` schemes. |
21
+ | `--output-dir` | Set a default base directory for export output files. |
22
+ | `--progress` | Show a Rich progress bar during long-running IMPORT operations. |
23
+ | `--dump-keywords` | Emit all metacommand keywords, conditionals, config options, and export formats as structured JSON. |
24
+ | `--gui-framework` | Select GUI backend: `tkinter` (default) or `textual` (terminal UI). |
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
+ | `--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. |
26
29
 
27
30
  ### Export Formats
28
31
 
@@ -36,10 +39,11 @@ ______________________________________________________________________
36
39
 
37
40
  ### Metacommands
38
41
 
39
- | Metacommand | Description |
40
- | ---------------------- | --------------------------------------------------------------------- |
41
- | `CONFIG SHOW_PROGRESS` | Enable the Rich progress bar for IMPORT operations at runtime. |
42
- | `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
+ | `CONFIG SHOW_PROGRESS` | Enable the Rich progress bar for IMPORT operations at runtime. |
46
+ | `CONFIG LOG_SQL` | Enable SQL query audit logging — writes executed SQL to the log file. |
43
47
 
44
48
  ### Configuration Options
45
49
 
@@ -175,21 +175,61 @@ Valid encoding names can be displayed with the `-y` option. See also [Character
175
175
  ### Informational options
176
176
 
177
177
  `-m`, `--metacommands`
178
+
178
179
  : List all metacommands and exit.
179
180
 
180
181
  `-o`, `--online-help`
182
+
181
183
  : Open the online documentation in the default browser.
182
184
 
183
185
  `-y`, `--encodings`
186
+
184
187
  : List all valid character encoding names and exit.
185
188
 
186
189
  `--dump-keywords`
190
+
187
191
  : Dump all metacommand keywords, conditional functions, config options, and export formats as JSON and exit. Useful for tooling that consumes execsql's keyword registry (e.g., the VS Code grammar generator).
188
192
 
189
193
  `--dry-run`
194
+
190
195
  : Parse the script (or inline `-c` command) and print the full command list — SQL statements and metacommands with source locations — without connecting to a database or executing anything. Useful for validating scripts.
191
196
 
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
+
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
+
227
+ `--profile`
228
+
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.
230
+
192
231
  `--version`
232
+
193
233
  : Show the version number and exit.
194
234
 
195
235
  ## Configuration File Defaults { #config_defaults }
@@ -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.
@@ -48,6 +48,49 @@ Double quotes (as shown above), apostrophes, or square brackets can be used to d
48
48
 
49
49
  See the [PROMPT ASK](#prompt_ask) metacommand for a version of this command that uses a GUI window and that can display a data table with the prompt.
50
50
 
51
+ ## ASSERT { #assert }
52
+
53
+ ```
54
+ ASSERT <condition>
55
+ ASSERT <condition> "<failure message>"
56
+ ASSERT <condition> '<failure message>'
57
+ ```
58
+
59
+ Evaluates `<condition>` using the same expression engine as [IF](#if_cmd). If the condition is `True`, execution continues silently (and the result is written to the log). If the condition is `False`, an error is raised with the provided failure message. If no message is supplied, the default message is `Assertion failed: <condition>`.
60
+
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
+
63
+ The `<condition>` supports all [conditional tests](metacommands.md#conditional_tests) available to `IF`, including:
64
+
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
+ - `DBMS(<type>)`, `SCHEMA_EXISTS(<schema>)`, `VIEW_EXISTS(<view>)`
71
+ - Boolean combinators: `AND`, `OR`, `NOT`
72
+
73
+ Substitution variables in the condition are expanded before evaluation. Use the `!!varname!!` syntax (e.g., `!!$myvar!!`).
74
+
75
+ ASSERT is silently skipped inside a `False` [IF](#if_cmd) block.
76
+
77
+ **Examples:**
78
+
79
+ ```sql
80
+ -- Halt with a custom message if the staging table is missing.
81
+ -- !x! ASSERT TABLE_EXISTS(staging) "staging table must exist before running this script"
82
+
83
+ -- Halt with the default message if the table has no rows.
84
+ -- !x! ASSERT HAS_ROWS(staging)
85
+
86
+ -- Verify a substitution variable has the expected value.
87
+ -- !x! SUB env prod
88
+ -- !x! ASSERT EQUAL(!!env!!, prod) 'expected production environment'
89
+
90
+ -- Combine conditions with AND/OR/NOT.
91
+ -- !x! ASSERT TABLE_EXISTS(orders) AND HAS_ROWS(orders) "orders table missing or empty"
92
+ ```
93
+
51
94
  ## AUTOCOMMIT
52
95
 
53
96
  ```
@@ -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|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|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": {
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "execsql2"
7
- version = "2.7.1"
7
+ version = "2.9.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.7.1"
161
+ current_version = "2.9.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",
@@ -254,6 +273,11 @@ def main(
254
273
  "--dump-keywords",
255
274
  help="Dump all metacommand keywords as JSON and exit.",
256
275
  ),
276
+ profile: bool = typer.Option(
277
+ False,
278
+ "--profile",
279
+ help="Record per-statement execution times and print a timing summary after the script completes.",
280
+ ),
257
281
  version: bool | None = typer.Option(
258
282
  None,
259
283
  "--version",
@@ -345,6 +369,10 @@ def main(
345
369
  positional = args or []
346
370
  if command is not None:
347
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
348
376
  else:
349
377
  if not positional:
350
378
  _err_console.print(
@@ -415,6 +443,9 @@ def main(
415
443
  dsn=dsn,
416
444
  output_dir=output_dir,
417
445
  progress=progress,
446
+ profile=profile,
447
+ ping=ping,
448
+ lint=lint,
418
449
  )
419
450
 
420
451