execsql2 2.16.13__tar.gz → 2.16.15__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 (321) hide show
  1. {execsql2-2.16.13 → execsql2-2.16.15}/CHANGELOG.md +18 -0
  2. {execsql2-2.16.13 → execsql2-2.16.15}/PKG-INFO +1 -1
  3. {execsql2-2.16.13 → execsql2-2.16.15}/docs/about/divergence.md +15 -14
  4. {execsql2-2.16.13 → execsql2-2.16.15}/docs/reference/metacommands.md +25 -22
  5. {execsql2-2.16.13 → execsql2-2.16.15}/extras/vscode-execsql/syntaxes/execsql.tmLanguage.json +1 -1
  6. {execsql2-2.16.13 → execsql2-2.16.15}/pyproject.toml +2 -2
  7. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/db/base.py +2 -0
  8. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/format.py +0 -1
  9. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/metacommands/debug.py +45 -41
  10. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/metacommands/dispatch.py +2 -9
  11. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/metacommands/io_export.py +14 -10
  12. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/metacommands/io_fileops.py +4 -6
  13. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/script/ast.py +3 -0
  14. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/script/executor.py +7 -3
  15. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/script/parser.py +20 -14
  16. {execsql2-2.16.13 → execsql2-2.16.15}/tests/metacommands/test_show_scripts.py +19 -9
  17. {execsql2-2.16.13 → execsql2-2.16.15}/tests/test_ast_parser.py +56 -0
  18. {execsql2-2.16.13 → execsql2-2.16.15}/tests/test_executor.py +91 -16
  19. {execsql2-2.16.13 → execsql2-2.16.15}/uv.lock +1 -1
  20. {execsql2-2.16.13 → execsql2-2.16.15}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  21. {execsql2-2.16.13 → execsql2-2.16.15}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  22. {execsql2-2.16.13 → execsql2-2.16.15}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  23. {execsql2-2.16.13 → execsql2-2.16.15}/.github/workflows/ci-cd.yml +0 -0
  24. {execsql2-2.16.13 → execsql2-2.16.15}/.gitignore +0 -0
  25. {execsql2-2.16.13 → execsql2-2.16.15}/.pre-commit-config.yaml +0 -0
  26. {execsql2-2.16.13 → execsql2-2.16.15}/.pre-commit-hooks.yaml +0 -0
  27. {execsql2-2.16.13 → execsql2-2.16.15}/.python-version +0 -0
  28. {execsql2-2.16.13 → execsql2-2.16.15}/.readthedocs.yaml +0 -0
  29. {execsql2-2.16.13 → execsql2-2.16.15}/CONTRIBUTING.md +0 -0
  30. {execsql2-2.16.13 → execsql2-2.16.15}/LICENSE.txt +0 -0
  31. {execsql2-2.16.13 → execsql2-2.16.15}/NOTICE +0 -0
  32. {execsql2-2.16.13 → execsql2-2.16.15}/README.md +0 -0
  33. {execsql2-2.16.13 → execsql2-2.16.15}/SECURITY.md +0 -0
  34. {execsql2-2.16.13 → execsql2-2.16.15}/docs/about/contributors.md +0 -0
  35. {execsql2-2.16.13 → execsql2-2.16.15}/docs/about/copyright.md +0 -0
  36. {execsql2-2.16.13 → execsql2-2.16.15}/docs/api/cli.md +0 -0
  37. {execsql2-2.16.13 → execsql2-2.16.15}/docs/api/db.md +0 -0
  38. {execsql2-2.16.13 → execsql2-2.16.15}/docs/api/exporters.md +0 -0
  39. {execsql2-2.16.13 → execsql2-2.16.15}/docs/api/importers.md +0 -0
  40. {execsql2-2.16.13 → execsql2-2.16.15}/docs/api/index.md +0 -0
  41. {execsql2-2.16.13 → execsql2-2.16.15}/docs/api/metacommands.md +0 -0
  42. {execsql2-2.16.13 → execsql2-2.16.15}/docs/dev/adding_db_adapters.md +0 -0
  43. {execsql2-2.16.13 → execsql2-2.16.15}/docs/dev/adding_exporters.md +0 -0
  44. {execsql2-2.16.13 → execsql2-2.16.15}/docs/dev/adding_importers.md +0 -0
  45. {execsql2-2.16.13 → execsql2-2.16.15}/docs/dev/adding_metacommands.md +0 -0
  46. {execsql2-2.16.13 → execsql2-2.16.15}/docs/dev/architecture.md +0 -0
  47. {execsql2-2.16.13 → execsql2-2.16.15}/docs/getting-started/installation.md +0 -0
  48. {execsql2-2.16.13 → execsql2-2.16.15}/docs/getting-started/requirements.md +0 -0
  49. {execsql2-2.16.13 → execsql2-2.16.15}/docs/getting-started/syntax.md +0 -0
  50. {execsql2-2.16.13 → execsql2-2.16.15}/docs/guides/debugging.md +0 -0
  51. {execsql2-2.16.13 → execsql2-2.16.15}/docs/guides/documentation.md +0 -0
  52. {execsql2-2.16.13 → execsql2-2.16.15}/docs/guides/encoding.md +0 -0
  53. {execsql2-2.16.13 → execsql2-2.16.15}/docs/guides/examples.md +0 -0
  54. {execsql2-2.16.13 → execsql2-2.16.15}/docs/guides/formatter.md +0 -0
  55. {execsql2-2.16.13 → execsql2-2.16.15}/docs/guides/logging.md +0 -0
  56. {execsql2-2.16.13 → execsql2-2.16.15}/docs/guides/sql_syntax.md +0 -0
  57. {execsql2-2.16.13 → execsql2-2.16.15}/docs/guides/usage.md +0 -0
  58. {execsql2-2.16.13 → execsql2-2.16.15}/docs/guides/using_scripts.md +0 -0
  59. {execsql2-2.16.13 → execsql2-2.16.15}/docs/images/Compare_planets.png +0 -0
  60. {execsql2-2.16.13 → execsql2-2.16.15}/docs/images/actions.png +0 -0
  61. {execsql2-2.16.13 → execsql2-2.16.15}/docs/images/actions2.png +0 -0
  62. {execsql2-2.16.13 → execsql2-2.16.15}/docs/images/checkboxes.png +0 -0
  63. {execsql2-2.16.13 → execsql2-2.16.15}/docs/images/connect.b64 +0 -0
  64. {execsql2-2.16.13 → execsql2-2.16.15}/docs/images/connect.png +0 -0
  65. {execsql2-2.16.13 → execsql2-2.16.15}/docs/images/create_conf.png +0 -0
  66. {execsql2-2.16.13 → execsql2-2.16.15}/docs/images/data_error1_screenshot.jpg +0 -0
  67. {execsql2-2.16.13 → execsql2-2.16.15}/docs/images/entry_form.png +0 -0
  68. {execsql2-2.16.13 → execsql2-2.16.15}/docs/images/execsql_console.png +0 -0
  69. {execsql2-2.16.13 → execsql2-2.16.15}/docs/images/execsql_logo_01.png +0 -0
  70. {execsql2-2.16.13 → execsql2-2.16.15}/docs/images/fatals.png +0 -0
  71. {execsql2-2.16.13 → execsql2-2.16.15}/docs/images/logo_small.png +0 -0
  72. {execsql2-2.16.13 → execsql2-2.16.15}/docs/images/pause_terminal.png +0 -0
  73. {execsql2-2.16.13 → execsql2-2.16.15}/docs/images/pause_terminal_sm.b64 +0 -0
  74. {execsql2-2.16.13 → execsql2-2.16.15}/docs/images/pause_terminal_sm.png +0 -0
  75. {execsql2-2.16.13 → execsql2-2.16.15}/docs/images/prompt_compare.png +0 -0
  76. {execsql2-2.16.13 → execsql2-2.16.15}/docs/images/set_build_commands.jpg +0 -0
  77. {execsql2-2.16.13 → execsql2-2.16.15}/docs/images/unit_conversions.b64 +0 -0
  78. {execsql2-2.16.13 → execsql2-2.16.15}/docs/images/unit_conversions_029.png +0 -0
  79. {execsql2-2.16.13 → execsql2-2.16.15}/docs/images/unmatched.png +0 -0
  80. {execsql2-2.16.13 → execsql2-2.16.15}/docs/images/vim_execsql_highlight.png +0 -0
  81. {execsql2-2.16.13 → execsql2-2.16.15}/docs/index.md +0 -0
  82. {execsql2-2.16.13 → execsql2-2.16.15}/docs/reference/configuration.md +0 -0
  83. {execsql2-2.16.13 → execsql2-2.16.15}/docs/reference/security.md +0 -0
  84. {execsql2-2.16.13 → execsql2-2.16.15}/docs/reference/substitution_vars.md +0 -0
  85. {execsql2-2.16.13 → execsql2-2.16.15}/extras/plugin-template/README.md +0 -0
  86. {execsql2-2.16.13 → execsql2-2.16.15}/extras/plugin-template/pyproject.toml +0 -0
  87. {execsql2-2.16.13 → execsql2-2.16.15}/extras/plugin-template/src/execsql_plugin_YOURNAME/__init__.py +0 -0
  88. {execsql2-2.16.13 → execsql2-2.16.15}/extras/plugin-template/tests/test_plugin.py.example +0 -0
  89. {execsql2-2.16.13 → execsql2-2.16.15}/extras/vscode-execsql/README.md +0 -0
  90. {execsql2-2.16.13 → execsql2-2.16.15}/extras/vscode-execsql/package.json +0 -0
  91. {execsql2-2.16.13 → execsql2-2.16.15}/justfile +0 -0
  92. {execsql2-2.16.13 → execsql2-2.16.15}/scripts/generate_vscode_grammar.py +0 -0
  93. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/__init__.py +0 -0
  94. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/__main__.py +0 -0
  95. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/api.py +0 -0
  96. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/cli/__init__.py +0 -0
  97. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/cli/dsn.py +0 -0
  98. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/cli/help.py +0 -0
  99. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/cli/lint.py +0 -0
  100. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/cli/lint_ast.py +0 -0
  101. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/cli/run.py +0 -0
  102. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/config.py +0 -0
  103. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/db/__init__.py +0 -0
  104. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/db/access.py +0 -0
  105. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/db/dsn.py +0 -0
  106. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/db/duckdb.py +0 -0
  107. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/db/factory.py +0 -0
  108. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/db/firebird.py +0 -0
  109. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/db/mysql.py +0 -0
  110. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/db/oracle.py +0 -0
  111. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/db/postgres.py +0 -0
  112. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/db/sqlite.py +0 -0
  113. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/db/sqlserver.py +0 -0
  114. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/debug/__init__.py +0 -0
  115. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/debug/repl.py +0 -0
  116. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/exceptions.py +0 -0
  117. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/exporters/__init__.py +0 -0
  118. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/exporters/base.py +0 -0
  119. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/exporters/delimited.py +0 -0
  120. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/exporters/duckdb.py +0 -0
  121. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/exporters/feather.py +0 -0
  122. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/exporters/html.py +0 -0
  123. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/exporters/json.py +0 -0
  124. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/exporters/latex.py +0 -0
  125. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/exporters/markdown.py +0 -0
  126. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/exporters/ods.py +0 -0
  127. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/exporters/parquet.py +0 -0
  128. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/exporters/pretty.py +0 -0
  129. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/exporters/protocol.py +0 -0
  130. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/exporters/raw.py +0 -0
  131. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/exporters/sqlite.py +0 -0
  132. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/exporters/templates.py +0 -0
  133. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/exporters/values.py +0 -0
  134. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/exporters/xls.py +0 -0
  135. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/exporters/xlsx.py +0 -0
  136. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/exporters/xml.py +0 -0
  137. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/exporters/yaml.py +0 -0
  138. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/exporters/zip.py +0 -0
  139. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/gui/__init__.py +0 -0
  140. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/gui/base.py +0 -0
  141. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/gui/console.py +0 -0
  142. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/gui/desktop.py +0 -0
  143. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/gui/tui.py +0 -0
  144. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/importers/__init__.py +0 -0
  145. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/importers/base.py +0 -0
  146. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/importers/csv.py +0 -0
  147. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/importers/feather.py +0 -0
  148. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/importers/json.py +0 -0
  149. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/importers/ods.py +0 -0
  150. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/importers/xls.py +0 -0
  151. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/metacommands/__init__.py +0 -0
  152. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/metacommands/conditions.py +0 -0
  153. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/metacommands/connect.py +0 -0
  154. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/metacommands/control.py +0 -0
  155. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/metacommands/data.py +0 -0
  156. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/metacommands/io.py +0 -0
  157. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/metacommands/io_import.py +0 -0
  158. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/metacommands/io_write.py +0 -0
  159. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/metacommands/prompt.py +0 -0
  160. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/metacommands/script_ext.py +0 -0
  161. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/metacommands/system.py +0 -0
  162. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/metacommands/upsert.py +0 -0
  163. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/models.py +0 -0
  164. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/parser.py +0 -0
  165. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/plugins.py +0 -0
  166. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/py.typed +0 -0
  167. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/script/__init__.py +0 -0
  168. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/script/control.py +0 -0
  169. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/script/engine.py +0 -0
  170. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/script/variables.py +0 -0
  171. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/state.py +0 -0
  172. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/types.py +0 -0
  173. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/utils/__init__.py +0 -0
  174. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/utils/auth.py +0 -0
  175. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/utils/crypto.py +0 -0
  176. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/utils/datetime.py +0 -0
  177. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/utils/errors.py +0 -0
  178. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/utils/fileio.py +0 -0
  179. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/utils/gui.py +0 -0
  180. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/utils/mail.py +0 -0
  181. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/utils/numeric.py +0 -0
  182. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/utils/regex.py +0 -0
  183. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/utils/strings.py +0 -0
  184. {execsql2-2.16.13 → execsql2-2.16.15}/src/execsql/utils/timer.py +0 -0
  185. {execsql2-2.16.13 → execsql2-2.16.15}/templates/README.md +0 -0
  186. {execsql2-2.16.13 → execsql2-2.16.15}/templates/config_settings.sqlite +0 -0
  187. {execsql2-2.16.13 → execsql2-2.16.15}/templates/example_config_prompt.sql +0 -0
  188. {execsql2-2.16.13 → execsql2-2.16.15}/templates/execsql.conf +0 -0
  189. {execsql2-2.16.13 → execsql2-2.16.15}/templates/make_config_db.sql +0 -0
  190. {execsql2-2.16.13 → execsql2-2.16.15}/templates/md_compare.sql +0 -0
  191. {execsql2-2.16.13 → execsql2-2.16.15}/templates/md_glossary.sql +0 -0
  192. {execsql2-2.16.13 → execsql2-2.16.15}/templates/md_upsert.sql +0 -0
  193. {execsql2-2.16.13 → execsql2-2.16.15}/templates/pg_compare.sql +0 -0
  194. {execsql2-2.16.13 → execsql2-2.16.15}/templates/pg_glossary.sql +0 -0
  195. {execsql2-2.16.13 → execsql2-2.16.15}/templates/pg_upsert.sql +0 -0
  196. {execsql2-2.16.13 → execsql2-2.16.15}/templates/script_template.sql +0 -0
  197. {execsql2-2.16.13 → execsql2-2.16.15}/templates/ss_compare.sql +0 -0
  198. {execsql2-2.16.13 → execsql2-2.16.15}/templates/ss_glossary.sql +0 -0
  199. {execsql2-2.16.13 → execsql2-2.16.15}/templates/ss_upsert.sql +0 -0
  200. {execsql2-2.16.13 → execsql2-2.16.15}/tests/__init__.py +0 -0
  201. {execsql2-2.16.13 → execsql2-2.16.15}/tests/cli/__init__.py +0 -0
  202. {execsql2-2.16.13 → execsql2-2.16.15}/tests/cli/test_cli.py +0 -0
  203. {execsql2-2.16.13 → execsql2-2.16.15}/tests/cli/test_cli_e2e.py +0 -0
  204. {execsql2-2.16.13 → execsql2-2.16.15}/tests/cli/test_cli_run.py +0 -0
  205. {execsql2-2.16.13 → execsql2-2.16.15}/tests/cli/test_lint.py +0 -0
  206. {execsql2-2.16.13 → execsql2-2.16.15}/tests/cli/test_ping.py +0 -0
  207. {execsql2-2.16.13 → execsql2-2.16.15}/tests/cli/test_profile.py +0 -0
  208. {execsql2-2.16.13 → execsql2-2.16.15}/tests/conftest.py +0 -0
  209. {execsql2-2.16.13 → execsql2-2.16.15}/tests/db/__init__.py +0 -0
  210. {execsql2-2.16.13 → execsql2-2.16.15}/tests/db/test_base.py +0 -0
  211. {execsql2-2.16.13 → execsql2-2.16.15}/tests/db/test_db_adapters_mocked.py +0 -0
  212. {execsql2-2.16.13 → execsql2-2.16.15}/tests/db/test_dsn.py +0 -0
  213. {execsql2-2.16.13 → execsql2-2.16.15}/tests/db/test_duckdb.py +0 -0
  214. {execsql2-2.16.13 → execsql2-2.16.15}/tests/db/test_factory.py +0 -0
  215. {execsql2-2.16.13 → execsql2-2.16.15}/tests/db/test_postgres.py +0 -0
  216. {execsql2-2.16.13 → execsql2-2.16.15}/tests/db/test_sqlite.py +0 -0
  217. {execsql2-2.16.13 → execsql2-2.16.15}/tests/db/test_sqlite_extra.py +0 -0
  218. {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/__init__.py +0 -0
  219. {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_base.py +0 -0
  220. {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_db.py +0 -0
  221. {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_delimited.py +0 -0
  222. {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_duckdb_exporter.py +0 -0
  223. {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_exporters.py +0 -0
  224. {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_feather.py +0 -0
  225. {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_html_extended.py +0 -0
  226. {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_html_latex.py +0 -0
  227. {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_json.py +0 -0
  228. {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_json_extended.py +0 -0
  229. {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_latex_extended.py +0 -0
  230. {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_markdown.py +0 -0
  231. {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_ods.py +0 -0
  232. {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_parquet.py +0 -0
  233. {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_pretty_extended.py +0 -0
  234. {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_raw_extended.py +0 -0
  235. {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_sqlite_exporter.py +0 -0
  236. {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_templates.py +0 -0
  237. {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_templates_extended.py +0 -0
  238. {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_values_extended.py +0 -0
  239. {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_xls_xlsx.py +0 -0
  240. {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_xlsx.py +0 -0
  241. {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_xml.py +0 -0
  242. {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_yaml.py +0 -0
  243. {execsql2-2.16.13 → execsql2-2.16.15}/tests/exporters/test_zip.py +0 -0
  244. {execsql2-2.16.13 → execsql2-2.16.15}/tests/gui/__init__.py +0 -0
  245. {execsql2-2.16.13 → execsql2-2.16.15}/tests/gui/test_backends.py +0 -0
  246. {execsql2-2.16.13 → execsql2-2.16.15}/tests/gui/test_compare_stats.py +0 -0
  247. {execsql2-2.16.13 → execsql2-2.16.15}/tests/gui/test_compute_row_diffs.py +0 -0
  248. {execsql2-2.16.13 → execsql2-2.16.15}/tests/importers/__init__.py +0 -0
  249. {execsql2-2.16.13 → execsql2-2.16.15}/tests/importers/test_base_extended.py +0 -0
  250. {execsql2-2.16.13 → execsql2-2.16.15}/tests/importers/test_csv_edge_cases.py +0 -0
  251. {execsql2-2.16.13 → execsql2-2.16.15}/tests/importers/test_csv_importer.py +0 -0
  252. {execsql2-2.16.13 → execsql2-2.16.15}/tests/importers/test_feather_importer.py +0 -0
  253. {execsql2-2.16.13 → execsql2-2.16.15}/tests/importers/test_json_importer.py +0 -0
  254. {execsql2-2.16.13 → execsql2-2.16.15}/tests/importers/test_ods_importer.py +0 -0
  255. {execsql2-2.16.13 → execsql2-2.16.15}/tests/importers/test_xls_importer.py +0 -0
  256. {execsql2-2.16.13 → execsql2-2.16.15}/tests/integration/__init__.py +0 -0
  257. {execsql2-2.16.13 → execsql2-2.16.15}/tests/integration/conftest.py +0 -0
  258. {execsql2-2.16.13 → execsql2-2.16.15}/tests/integration/test_dsn.py +0 -0
  259. {execsql2-2.16.13 → execsql2-2.16.15}/tests/integration/test_duckdb.py +0 -0
  260. {execsql2-2.16.13 → execsql2-2.16.15}/tests/integration/test_mysql.py +0 -0
  261. {execsql2-2.16.13 → execsql2-2.16.15}/tests/integration/test_postgres.py +0 -0
  262. {execsql2-2.16.13 → execsql2-2.16.15}/tests/integration/test_sqlite.py +0 -0
  263. {execsql2-2.16.13 → execsql2-2.16.15}/tests/metacommands/__init__.py +0 -0
  264. {execsql2-2.16.13 → execsql2-2.16.15}/tests/metacommands/test_assert.py +0 -0
  265. {execsql2-2.16.13 → execsql2-2.16.15}/tests/metacommands/test_breakpoint.py +0 -0
  266. {execsql2-2.16.13 → execsql2-2.16.15}/tests/metacommands/test_connect.py +0 -0
  267. {execsql2-2.16.13 → execsql2-2.16.15}/tests/metacommands/test_io_export.py +0 -0
  268. {execsql2-2.16.13 → execsql2-2.16.15}/tests/metacommands/test_io_import.py +0 -0
  269. {execsql2-2.16.13 → execsql2-2.16.15}/tests/metacommands/test_metacommands.py +0 -0
  270. {execsql2-2.16.13 → execsql2-2.16.15}/tests/metacommands/test_metacommands_connect.py +0 -0
  271. {execsql2-2.16.13 → execsql2-2.16.15}/tests/metacommands/test_metacommands_data.py +0 -0
  272. {execsql2-2.16.13 → execsql2-2.16.15}/tests/metacommands/test_metacommands_extended.py +0 -0
  273. {execsql2-2.16.13 → execsql2-2.16.15}/tests/metacommands/test_metacommands_fileops_extra.py +0 -0
  274. {execsql2-2.16.13 → execsql2-2.16.15}/tests/metacommands/test_metacommands_io.py +0 -0
  275. {execsql2-2.16.13 → execsql2-2.16.15}/tests/metacommands/test_metacommands_io_write_extra.py +0 -0
  276. {execsql2-2.16.13 → execsql2-2.16.15}/tests/metacommands/test_metacommands_script_ext.py +0 -0
  277. {execsql2-2.16.13 → execsql2-2.16.15}/tests/metacommands/test_metacommands_system.py +0 -0
  278. {execsql2-2.16.13 → execsql2-2.16.15}/tests/metacommands/test_metacommands_system_extra.py +0 -0
  279. {execsql2-2.16.13 → execsql2-2.16.15}/tests/metacommands/test_pg_upsert.py +0 -0
  280. {execsql2-2.16.13 → execsql2-2.16.15}/tests/metacommands/test_row_count.py +0 -0
  281. {execsql2-2.16.13 → execsql2-2.16.15}/tests/scripts/__init__.py +0 -0
  282. {execsql2-2.16.13 → execsql2-2.16.15}/tests/scripts/fixtures/control_flow.sql +0 -0
  283. {execsql2-2.16.13 → execsql2-2.16.15}/tests/scripts/fixtures/io_roundtrip.sql +0 -0
  284. {execsql2-2.16.13 → execsql2-2.16.15}/tests/scripts/fixtures/parse_only/parse_tree.sql +0 -0
  285. {execsql2-2.16.13 → execsql2-2.16.15}/tests/scripts/fixtures/smoke.sql +0 -0
  286. {execsql2-2.16.13 → execsql2-2.16.15}/tests/scripts/test_sql_scripts.py +0 -0
  287. {execsql2-2.16.13 → execsql2-2.16.15}/tests/test_api.py +0 -0
  288. {execsql2-2.16.13 → execsql2-2.16.15}/tests/test_ast.py +0 -0
  289. {execsql2-2.16.13 → execsql2-2.16.15}/tests/test_config.py +0 -0
  290. {execsql2-2.16.13 → execsql2-2.16.15}/tests/test_config_data.py +0 -0
  291. {execsql2-2.16.13 → execsql2-2.16.15}/tests/test_config_extended.py +0 -0
  292. {execsql2-2.16.13 → execsql2-2.16.15}/tests/test_debug_repl.py +0 -0
  293. {execsql2-2.16.13 → execsql2-2.16.15}/tests/test_engine.py +0 -0
  294. {execsql2-2.16.13 → execsql2-2.16.15}/tests/test_error_messages.py +0 -0
  295. {execsql2-2.16.13 → execsql2-2.16.15}/tests/test_exceptions.py +0 -0
  296. {execsql2-2.16.13 → execsql2-2.16.15}/tests/test_format.py +0 -0
  297. {execsql2-2.16.13 → execsql2-2.16.15}/tests/test_mail.py +0 -0
  298. {execsql2-2.16.13 → execsql2-2.16.15}/tests/test_models.py +0 -0
  299. {execsql2-2.16.13 → execsql2-2.16.15}/tests/test_package.py +0 -0
  300. {execsql2-2.16.13 → execsql2-2.16.15}/tests/test_parser.py +0 -0
  301. {execsql2-2.16.13 → execsql2-2.16.15}/tests/test_parser_params.py +0 -0
  302. {execsql2-2.16.13 → execsql2-2.16.15}/tests/test_plugins.py +0 -0
  303. {execsql2-2.16.13 → execsql2-2.16.15}/tests/test_registry.py +0 -0
  304. {execsql2-2.16.13 → execsql2-2.16.15}/tests/test_script.py +0 -0
  305. {execsql2-2.16.13 → execsql2-2.16.15}/tests/test_state.py +0 -0
  306. {execsql2-2.16.13 → execsql2-2.16.15}/tests/test_types.py +0 -0
  307. {execsql2-2.16.13 → execsql2-2.16.15}/tests/utils/__init__.py +0 -0
  308. {execsql2-2.16.13 → execsql2-2.16.15}/tests/utils/test_auth.py +0 -0
  309. {execsql2-2.16.13 → execsql2-2.16.15}/tests/utils/test_auth_extra.py +0 -0
  310. {execsql2-2.16.13 → execsql2-2.16.15}/tests/utils/test_crypto.py +0 -0
  311. {execsql2-2.16.13 → execsql2-2.16.15}/tests/utils/test_datetime.py +0 -0
  312. {execsql2-2.16.13 → execsql2-2.16.15}/tests/utils/test_errors.py +0 -0
  313. {execsql2-2.16.13 → execsql2-2.16.15}/tests/utils/test_errors_extra.py +0 -0
  314. {execsql2-2.16.13 → execsql2-2.16.15}/tests/utils/test_fileio.py +0 -0
  315. {execsql2-2.16.13 → execsql2-2.16.15}/tests/utils/test_fileio_extra.py +0 -0
  316. {execsql2-2.16.13 → execsql2-2.16.15}/tests/utils/test_numeric.py +0 -0
  317. {execsql2-2.16.13 → execsql2-2.16.15}/tests/utils/test_regex.py +0 -0
  318. {execsql2-2.16.13 → execsql2-2.16.15}/tests/utils/test_strings.py +0 -0
  319. {execsql2-2.16.13 → execsql2-2.16.15}/tests/utils/test_timer.py +0 -0
  320. {execsql2-2.16.13 → execsql2-2.16.15}/tests/utils/test_timer_extra.py +0 -0
  321. {execsql2-2.16.13 → execsql2-2.16.15}/zensical.toml +0 -0
@@ -13,6 +13,24 @@ ______________________________________________________________________
13
13
 
14
14
  ______________________________________________________________________
15
15
 
16
+ ## [2.16.15] - 2026-05-02
17
+
18
+ ### Changed
19
+
20
+ - Merged `SHOW SCRIPTS` and `SHOW SCRIPT <name>` into a single `SHOW SCRIPTS [<name>]` metacommand. Without a name, lists all registered scripts; with a name, shows detail for that script.
21
+
22
+ ______________________________________________________________________
23
+
24
+ ## [2.16.14] - 2026-05-01
25
+
26
+ ### Fixed
27
+
28
+ - ELSEIF conditions now support ANDIF/ORIF modifiers. Previously, ANDIF/ORIF after an ELSEIF were silently attached to the parent IF condition instead of the ELSEIF clause, meaning the compound condition was never evaluated correctly. ELSEIF + ANDIF/ORIF now works the same way as IF + ANDIF/ORIF.
29
+ - Unknown AST node types now raise an error instead of being silently ignored during execution.
30
+ - Cursor leak in `select_rowsource()` and `select_rowdict()`: cursor is now closed on query execution failure. High-traffic callers (EXPORT, COPY) now explicitly close the row generator on error instead of relying on garbage collection.
31
+
32
+ ______________________________________________________________________
33
+
16
34
  ## [2.16.13] - 2026-05-01
17
35
 
18
36
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: execsql2
3
- Version: 2.16.13
3
+ Version: 2.16.15
4
4
  Summary: Runs a SQL script against a PostgreSQL, SQLite, MariaDB/MySQL, DuckDB, Firebird, MS-Access, MS-SQL-Server, or Oracle database, or an ODBC DSN. Provides metacommands to import and export data, copy data between databases, conditionally execute SQL and metacommands, and dynamically alter SQL and metacommands with substitution variables.
5
5
  Project-URL: Homepage, https://execsql2.readthedocs.io
6
6
  Project-URL: Repository, https://github.com/geocoug/execsql
@@ -44,23 +44,22 @@ ______________________________________________________________________
44
44
 
45
45
  ### Metacommands
46
46
 
47
- | Metacommand | Description |
48
- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
49
- | `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. |
50
- | `BREAKPOINT` | Pause script execution and drop into an interactive debug REPL. See [Debugging](#debugging) below for full details. |
51
- | `CONFIG SHOW_PROGRESS` | Enable the Rich progress bar for IMPORT operations at runtime. |
52
- | `CONFIG LOG_SQL` | Enable SQL query audit logging — writes executed SQL to the log file. |
53
- | `PG_UPSERT` | QA-checked, FK-dependency-ordered upserts from staging to base schema on PostgreSQL. Integrates [pg-upsert](https://pg-upsert.readthedocs.io/) as an optional dependency. Three modes: full pipeline, QA-only, and schema check. Supports `EXPORT_FAILURES`, `EXPORT_FORMAT`, `EXPORT_MAX_ROWS`, and `STRICT_COLUMNS` keywords. `STRICT_COLUMNS` forces all missing columns to be errors (requires `pg-upsert>=1.22.0`). |
54
- | `IMPORT … FROM JSON` | Import a JSON file (array of objects or NDJSON) into a database table. Nested objects are flattened with dot-separated column names; arrays are stored as JSON strings. |
55
- | `SHOW SCRIPTS` | List all registered SCRIPT definitions with parameter signatures and source locations. Includes scripts from INCLUDEEd files. |
56
- | `SHOW SCRIPT <name>` | Show detail for a single registered SCRIPT: name, parameters (with defaults), source file/line range, and docstring. |
47
+ | Metacommand | Description |
48
+ | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
49
+ | `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. |
50
+ | `BREAKPOINT` | Pause script execution and drop into an interactive debug REPL. See [Debugging](#debugging) below for full details. |
51
+ | `CONFIG SHOW_PROGRESS` | Enable the Rich progress bar for IMPORT operations at runtime. |
52
+ | `CONFIG LOG_SQL` | Enable SQL query audit logging — writes executed SQL to the log file. |
53
+ | `PG_UPSERT` | QA-checked, FK-dependency-ordered upserts from staging to base schema on PostgreSQL. Integrates [pg-upsert](https://pg-upsert.readthedocs.io/) as an optional dependency. Three modes: full pipeline, QA-only, and schema check. Supports `EXPORT_FAILURES`, `EXPORT_FORMAT`, `EXPORT_MAX_ROWS`, and `STRICT_COLUMNS` keywords. `STRICT_COLUMNS` forces all missing columns to be errors (requires `pg-upsert>=1.22.0`). |
54
+ | `IMPORT … FROM JSON` | Import a JSON file (array of objects or NDJSON) into a database table. Nested objects are flattened with dot-separated column names; arrays are stored as JSON strings. |
55
+ | `SHOW SCRIPTS [<name>]` | Without a name, lists all registered SCRIPT definitions with parameter signatures and source locations. With a name, shows detail including parameters (with defaults), source file/line range, and docstring. |
57
56
 
58
57
  ### SCRIPT Enhancements
59
58
 
60
- | Feature | Description |
61
- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
62
- | Default parameters | `BEGIN SCRIPT load(schema, table, batch=1000)` — parameters with defaults can be omitted at call site. Required parameters must precede optional parameters. |
63
- | Docstrings | Comments (`--` or `/* */`) immediately following `BEGIN SCRIPT` are captured as documentation. A blank line terminates the docstring. Displayed by `SHOW SCRIPT`, `SHOW SCRIPTS`, and `.scripts` REPL command. |
59
+ | Feature | Description |
60
+ | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
61
+ | Default parameters | `BEGIN SCRIPT load(schema, table, batch=1000)` — parameters with defaults can be omitted at call site. Required parameters must precede optional parameters. |
62
+ | Docstrings | Comments (`--` or `/* */`) immediately following `BEGIN SCRIPT` are captured as documentation. A blank line terminates the docstring. Displayed by `SHOW SCRIPTS <name>` and `.scripts <name>` REPL command. |
64
63
 
65
64
  ### Bug Fixes
66
65
 
@@ -310,6 +309,8 @@ These are behavioral changes driven by security or correctness issues in the ups
310
309
  | Empty script name in error msg | `_execute_script_direct()` and `_execute_script_textual_console()` no longer append "in script , line 0" to uncaught-exception messages when `current_script_line()` returns an empty string. |
311
310
  | `PROMPT COMPARE` diff comparison | Diff engine uses native Python equality instead of string comparison — numeric types, Decimals, and booleans compare correctly. `None` is distinguished from empty string. Columns are matched by name (not position), key columns are excluded from comparison, and duplicate PKs keep the first row. |
312
311
  | `win_config_file` broken | Checked `os.name == "windows"` which Python never returns (correct value is `"nt"`). Fixed to `os.name == "nt"`. Inherited from upstream. |
312
+ | ELSEIF + ANDIF/ORIF | ANDIF/ORIF after an ELSEIF were silently attached to the parent IF condition instead of the ELSEIF clause. The compound condition was never evaluated for the ELSEIF branch. Fixed in the AST parser and executor. |
313
+ | Cursor leak in `select_rowsource()` | Cursor was not closed on query execution failure. Row generators in EXPORT and COPY paths were not explicitly closed on error, relying on garbage collection for cleanup. |
313
314
  | `NumericParser` right-associative | Arithmetic operators were parsed right-to-left. `10 - 3 - 2` evaluated as `9` instead of `5`. Fixed to left-associative parsing. Inherited from upstream. |
314
315
  | Empty-column check precedence | `DataTable` and `Database.populate_table()` had an operator precedence bug in the extra-column emptiness check — a redundant `and conf.del_empty_cols` caused incorrect short-circuit evaluation. Inherited from upstream. |
315
316
  | SQLite import string processing | `SQLiteDatabase.populate_table()` applied `trim_strings`, `replace_newlines`, and `empty_strings` after copying row data, so processing never reached the INSERT. Fixed to process before extraction. Inherited from upstream. |
@@ -1372,7 +1372,7 @@ ENDIF
1372
1372
 
1373
1373
  Multiple ELSEIF clauses can be used within a single multi-line IF metacommand. An ELSE clause can be used in combination with ELSEIF clauses, but this is not recommended because the results are not likely to be what you expect---the ELSE keyword only inverts the current truth state, it does not provide an alternative to all preceding ELSEIF clauses. To achieve the effect of a case or switch statement, use only ELSEIF clauses without a final ELSE clause.
1374
1374
 
1375
- The ANDIF metacommand allows you to test for the conjunction of two conditional expressions using two separate metacommands instead of one. This may be beneficial for clarity. The simplest form of usage of the ANDIF clause is:
1375
+ The ANDIF metacommand allows you to test for the conjunction of two conditional expressions using two separate metacommands instead of one. This may be beneficial for clarity. ANDIF and ORIF can follow either an IF or an ELSEIF metacommand. The simplest form of usage of the ANDIF clause is:
1376
1376
 
1377
1377
  ```
1378
1378
  IF(<conditional expression>)
@@ -1381,7 +1381,7 @@ ANDIF(<conditional expression>)
1381
1381
  ENDIF
1382
1382
  ```
1383
1383
 
1384
- The ANDIF metacommand does not have to immediately follow the IF metacommand. It could instead follow an ELSE statement, or appear anywhere at all within a multi-line IF metacommand. Usage patterns other than that illustrated above may be difficult to interpret, however, and nested IF metacommands may be preferable to complex uses of the ANDIF clause.
1384
+ The ANDIF metacommand does not have to immediately follow the IF metacommand. It could instead follow an ELSEIF statement. Usage patterns other than those illustrated here may be difficult to interpret, however, and nested IF metacommands may be preferable to complex uses of the ANDIF clause.
1385
1385
 
1386
1386
  The ORIF metacommand is similar to the ANDIF clause, but allows you to test for the disjunction of two conditional expressions using two different metacommands. The simplest form of usage of the ORIF clause is:
1387
1387
 
@@ -1392,6 +1392,17 @@ ORIF(<conditional expression>)
1392
1392
  ENDIF
1393
1393
  ```
1394
1394
 
1395
+ ANDIF and ORIF can also compound an ELSEIF condition:
1396
+
1397
+ ```
1398
+ IF(<conditional expression>)
1399
+ <SQL statements and metacommands>
1400
+ ELSEIF(<conditional expression>)
1401
+ ANDIF(<conditional expression>)
1402
+ <SQL statements and metacommands>
1403
+ ENDIF
1404
+ ```
1405
+
1395
1406
  The IF metacommands can be used not only to control a single stream of script commands, but also to loop over sets of SQL statements and metacommands, as shown in [Example 6](../guides/examples.md#example6).
1396
1407
 
1397
1408
  The IF metacommands cannot be used within a SQL statement (nor can any other metacommands). This restriction prohibits constructions such as:
@@ -2756,12 +2767,18 @@ The numeric expression may consist of the simple algebraic operations of additio
2756
2767
  ## SHOW SCRIPTS { #show_scripts }
2757
2768
 
2758
2769
  ```
2759
- SHOW SCRIPTS
2770
+ SHOW SCRIPTS [<name>]
2760
2771
  ```
2761
2772
 
2762
- Lists all registered SCRIPT definitions with their parameter signatures and source locations. This is useful for discovering what scripts are available at runtime, especially when scripts are loaded from INCLUDEEd files whose paths are determined dynamically.
2773
+ Without a name, lists all registered SCRIPT definitions with their parameter signatures and source locations. With a name, shows detail for that script including parameters, source file/line range, and docstring.
2774
+
2775
+ This is useful for discovering what scripts are available at runtime, especially when scripts are loaded from INCLUDEEd files whose paths are determined dynamically.
2763
2776
 
2764
- **Example output:**
2777
+ **List all scripts:**
2778
+
2779
+ ```sql
2780
+ -- !x! SHOW SCRIPTS
2781
+ ```
2765
2782
 
2766
2783
  ```
2767
2784
  Registered scripts (3):
@@ -2771,26 +2788,12 @@ Registered scripts (3):
2771
2788
  validate(schema, table) pipeline.sql:62-80
2772
2789
  ```
2773
2790
 
2774
- Default parameter values are shown in the signature. Use `SHOW SCRIPT <name>` for full detail including docstrings.
2775
-
2776
- If no scripts are registered, prints `No scripts registered.`
2777
-
2778
- ## SHOW SCRIPT { #show_script }
2779
-
2780
- ```
2781
- SHOW SCRIPT <name>
2782
- ```
2783
-
2784
- Shows detail for a single registered SCRIPT definition, including its parameter list (with required/optional status and defaults), source file/line range, and docstring.
2785
-
2786
- **Example:**
2791
+ **Show detail for one script:**
2787
2792
 
2788
2793
  ```sql
2789
- -- !x! SHOW SCRIPT load_data
2794
+ -- !x! SHOW SCRIPTS load_data
2790
2795
  ```
2791
2796
 
2792
- **Example output:**
2793
-
2794
2797
  ```
2795
2798
  Script: load_data(schema, table, batch_size=1000)
2796
2799
  Source: pipeline.sql:15-42
@@ -2802,7 +2805,7 @@ Parameters:
2802
2805
  Load data from staging into the target table.
2803
2806
  ```
2804
2807
 
2805
- If the script is not found, prints `No script named '<name>' is registered.`
2808
+ If no scripts are registered, prints `No scripts registered.` If the named script is not found, prints `No script named '<name>' is registered.`
2806
2809
 
2807
2810
  !!! tip
2808
2811
  In the debug REPL, use `.scripts` to list all scripts or `.scripts <name>` to show detail for one script.
@@ -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|pg_upsert\\s+check|sub_querystring|execute\\s+script|append\\s+script|extend\\s+script|reset\\s+counter|export\\s+query|pg_upsert\\s+qa|show\\s+scripts|sub_tempfile|write\\s+script|import_file|set\\s+counter|show\\s+script|sub_decrypt|sub_encrypt|autocommit|breakpoint|copy\\s+query|disconnect|select_sub|sub_append|system_cmd|pg_upsert|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|pg_upsert\\s+check|sub_querystring|execute\\s+script|append\\s+script|extend\\s+script|reset\\s+counter|export\\s+query|pg_upsert\\s+qa|show\\s+scripts|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_upsert|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.16.13"
7
+ version = "2.16.15"
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" }
@@ -165,7 +165,7 @@ skip-magic-trailing-comma = false
165
165
  line-ending = "auto"
166
166
 
167
167
  [tool.bumpversion]
168
- current_version = "2.16.13"
168
+ current_version = "2.16.15"
169
169
  commit = true
170
170
  tag = true
171
171
  tag_name = "v{new_version}"
@@ -226,6 +226,7 @@ class Database(ABC):
226
226
  try:
227
227
  curs.execute(sql)
228
228
  except Exception:
229
+ curs.close()
229
230
  self.rollback()
230
231
  raise
231
232
  try:
@@ -264,6 +265,7 @@ class Database(ABC):
264
265
  try:
265
266
  curs.execute(sql)
266
267
  except Exception:
268
+ curs.close()
267
269
  self.rollback()
268
270
  raise
269
271
  try:
@@ -74,7 +74,6 @@ MULTIWORD_KEYWORDS = [
74
74
  "WITH TEMPLATE",
75
75
  "IN ZIPFILE",
76
76
  "SHOW SCRIPTS",
77
- "SHOW SCRIPT",
78
77
  ]
79
78
 
80
79
  # Depth-tracking sets
@@ -7,8 +7,8 @@ Provides ``x_debug_write_metacommands``, which implements the
7
7
  ``WRITE METACOMMANDS`` debug metacommand that prints the full registered
8
8
  metacommand list to the log/console for troubleshooting.
9
9
 
10
- Also provides ``x_show_scripts`` and ``x_show_script`` for runtime
11
- introspection of registered SCRIPT blocks.
10
+ Also provides ``x_show_scripts`` for runtime introspection of registered
11
+ SCRIPT blocks.
12
12
  """
13
13
 
14
14
  from pathlib import Path
@@ -214,50 +214,54 @@ def _format_script_source(span: Any) -> str:
214
214
 
215
215
 
216
216
  # ---------------------------------------------------------------------------
217
- # SHOW SCRIPTS / SHOW SCRIPT metacommand handlers
217
+ # SHOW SCRIPTS metacommand handler
218
218
  # ---------------------------------------------------------------------------
219
219
 
220
220
 
221
221
  def x_show_scripts(**kwargs: Any) -> None:
222
- """List all registered SCRIPT definitions with parameters and source location."""
223
- scripts = _state.ast_scripts
224
- if not scripts:
225
- _state.output.write("No scripts registered.\n")
226
- return
227
- _state.output.write(f"Registered scripts ({len(scripts)}):\n\n")
228
- # Compute column width for alignment
229
- sigs = {name: _format_script_signature(name, block.param_defs) for name, block in scripts.items()}
230
- max_sig = max(len(s) for s in sigs.values())
231
- for name, block in scripts.items():
232
- sig = sigs[name]
233
- src = _format_script_source(block.span)
234
- _state.output.write(f" {sig:<{max_sig}} {src}\n")
235
- _state.output.write("\n")
222
+ """List all registered scripts, or show detail for one script.
236
223
 
237
-
238
- def x_show_script(**kwargs: Any) -> None:
239
- """Show detail for a single registered SCRIPT definition."""
240
- script_name = kwargs.get("script_id", "").lower()
224
+ Without a name argument, lists all registered SCRIPT definitions with
225
+ their parameter signatures and source locations. With a name, shows
226
+ detail for that script including parameters, source, and docstring.
227
+ """
228
+ script_name = (kwargs.get("script_id") or "").strip().lower()
241
229
  scripts = _state.ast_scripts
242
- if script_name not in scripts:
243
- _state.output.write(f"No script named '{script_name}' is registered.\n")
244
- return
245
- block = scripts[script_name]
246
- sig = _format_script_signature(block.name, block.param_defs)
247
- src = _format_script_source(block.span)
248
- _state.output.write(f"Script: {sig}\n")
249
- _state.output.write(f"Source: {src}\n")
250
- if block.param_defs:
251
- _state.output.write("Parameters:\n")
252
- max_name = max(len(p.name) for p in block.param_defs)
253
- for p in block.param_defs:
254
- if p.default is not None:
255
- _state.output.write(f" {p.name:<{max_name}} (optional, default: {p.default})\n")
256
- else:
257
- _state.output.write(f" {p.name:<{max_name}} (required)\n")
230
+
231
+ if script_name:
232
+ # ---------- detail for one script ----------
233
+ if script_name not in scripts:
234
+ _state.output.write(f"No script named '{script_name}' is registered.\n")
235
+ return
236
+ block = scripts[script_name]
237
+ sig = _format_script_signature(block.name, block.param_defs)
238
+ src = _format_script_source(block.span)
239
+ _state.output.write(f"Script: {sig}\n")
240
+ _state.output.write(f"Source: {src}\n")
241
+ if block.param_defs:
242
+ _state.output.write("Parameters:\n")
243
+ max_name = max(len(p.name) for p in block.param_defs)
244
+ for p in block.param_defs:
245
+ if p.default is not None:
246
+ _state.output.write(f" {p.name:<{max_name}} (optional, default: {p.default})\n")
247
+ else:
248
+ _state.output.write(f" {p.name:<{max_name}} (required)\n")
249
+ else:
250
+ _state.output.write("Parameters: (none)\n")
251
+ if block.doc:
252
+ _state.output.write("\n")
253
+ for doc_line in block.doc.split("\n"):
254
+ _state.output.write(f" {doc_line}\n")
258
255
  else:
259
- _state.output.write("Parameters: (none)\n")
260
- if block.doc:
256
+ # ---------- list all scripts ----------
257
+ if not scripts:
258
+ _state.output.write("No scripts registered.\n")
259
+ return
260
+ _state.output.write(f"Registered scripts ({len(scripts)}):\n\n")
261
+ sigs = {name: _format_script_signature(name, block.param_defs) for name, block in scripts.items()}
262
+ max_sig = max(len(s) for s in sigs.values())
263
+ for name, block in scripts.items():
264
+ sig = sigs[name]
265
+ src = _format_script_source(block.span)
266
+ _state.output.write(f" {sig:<{max_sig}} {src}\n")
261
267
  _state.output.write("\n")
262
- for doc_line in block.doc.split("\n"):
263
- _state.output.write(f" {doc_line}\n")
@@ -99,7 +99,6 @@ from execsql.metacommands.debug import (
99
99
  x_debug_write_metacommands,
100
100
  x_debug_write_odbc_drivers,
101
101
  x_debug_write_subvars,
102
- x_show_script,
103
102
  x_show_scripts,
104
103
  )
105
104
  from execsql.debug.repl import x_breakpoint
@@ -1750,20 +1749,14 @@ def build_dispatch_table() -> MetaCommandList:
1750
1749
  )
1751
1750
 
1752
1751
  # ------------------------------------------------------------------
1753
- # SHOW SCRIPTS / SHOW SCRIPT
1752
+ # SHOW SCRIPTS [<name>]
1754
1753
  # ------------------------------------------------------------------
1755
1754
  mcl.add(
1756
- r"^\s*SHOW\s+SCRIPTS\s*$",
1755
+ r"^\s*SHOW\s+SCRIPTS\s*(?P<script_id>\w+)?\s*$",
1757
1756
  x_show_scripts,
1758
1757
  description="SHOW SCRIPTS",
1759
1758
  category="action",
1760
1759
  )
1761
- mcl.add(
1762
- r"^\s*SHOW\s+SCRIPT\s+(?P<script_id>\w+)\s*$",
1763
- x_show_script,
1764
- description="SHOW SCRIPT",
1765
- category="action",
1766
- )
1767
1760
 
1768
1761
  # ------------------------------------------------------------------
1769
1762
  # IF / ORIF / ANDIF / ELSEIF / ELSE / ENDIF
@@ -136,16 +136,20 @@ def _dispatch_format(
136
136
  raise
137
137
  except Exception as e:
138
138
  raise ErrInfo("db", select_stmt, exception_msg=exception_desc()) from e
139
- if filefmt == "raw":
140
- write_query_raw(outfile, rows, db.encoding, append, zipfile=zipfilename)
141
- elif filefmt == "b64":
142
- write_query_b64(outfile, rows, append, zipfile=zipfilename)
143
- elif filefmt == "feather":
144
- write_query_to_feather(outfile, hdrs, rows)
145
- elif filefmt == "parquet":
146
- write_query_to_parquet(outfile, hdrs, rows)
147
- else:
148
- write_delimited_file(outfile, filefmt, hdrs, rows, _state.conf.output_encoding, append, zipfilename)
139
+ try:
140
+ if filefmt == "raw":
141
+ write_query_raw(outfile, rows, db.encoding, append, zipfile=zipfilename)
142
+ elif filefmt == "b64":
143
+ write_query_b64(outfile, rows, append, zipfile=zipfilename)
144
+ elif filefmt == "feather":
145
+ write_query_to_feather(outfile, hdrs, rows)
146
+ elif filefmt == "parquet":
147
+ write_query_to_parquet(outfile, hdrs, rows)
148
+ else:
149
+ write_delimited_file(outfile, filefmt, hdrs, rows, _state.conf.output_encoding, append, zipfilename)
150
+ except BaseException:
151
+ rows.close()
152
+ raise
149
153
 
150
154
 
151
155
  # ---------------------------------------------------------------------------
@@ -106,10 +106,9 @@ def x_copy(**kwargs: Any) -> None:
106
106
  try:
107
107
  db2.populate_table(schema2, table2, rows, hdrs, get_ts)
108
108
  db2.commit()
109
- except ErrInfo:
109
+ except BaseException:
110
+ rows.close()
110
111
  raise
111
- except Exception as e:
112
- raise ErrInfo("db", select_stmt, exception_msg=exception_desc()) from e
113
112
 
114
113
 
115
114
  def x_copy_query(**kwargs: Any) -> None:
@@ -181,10 +180,9 @@ def x_copy_query(**kwargs: Any) -> None:
181
180
  try:
182
181
  db2.populate_table(schema2, table2, rows, hdrs, get_ts)
183
182
  db2.commit()
184
- except ErrInfo:
183
+ except BaseException:
184
+ rows.close()
185
185
  raise
186
- except Exception as e:
187
- raise ErrInfo("db", select_stmt, exception_msg=exception_desc()) from e
188
186
 
189
187
 
190
188
  def x_zip(**kwargs: Any) -> None:
@@ -209,11 +209,14 @@ class ElseIfClause:
209
209
  Attributes:
210
210
  condition: The condition expression text (e.g. ``"HAS_ROWS"``).
211
211
  span: Source location of the ELSEIF line itself.
212
+ condition_modifiers: ANDIF/ORIF modifiers that compound the ELSEIF
213
+ condition, evaluated left-to-right at runtime.
212
214
  body: Nodes executed when this condition is true.
213
215
  """
214
216
 
215
217
  condition: str
216
218
  span: SourceSpan
219
+ condition_modifiers: list[ConditionModifier] = field(default_factory=list)
217
220
  body: list[Node] = field(default_factory=list)
218
221
 
219
222
 
@@ -384,6 +384,12 @@ def _execute_node(
384
384
  ctx.last_command = _FakeScriptCmd(node)
385
385
  _execute_include(ctx, node, localvars)
386
386
 
387
+ else:
388
+ raise ErrInfo(
389
+ type="error",
390
+ other_msg=f"Unhandled AST node type: {type(node).__name__} at {node.span}",
391
+ )
392
+
387
393
 
388
394
  # ---------------------------------------------------------------------------
389
395
  # Block executors
@@ -404,9 +410,7 @@ def _execute_if(
404
410
 
405
411
  # Try ELSEIF clauses
406
412
  for clause in node.elseif_clauses:
407
- effective_locals = _stack_localvars(ctx)
408
- expanded = substitute_vars(clause.condition, effective_locals, ctx=ctx)
409
- if xcmd_test(expanded):
413
+ if _eval_condition(ctx, clause.condition, clause.condition_modifiers):
410
414
  _execute_nodes(ctx, clause.body, node.span.file, localvars, in_loop=in_loop)
411
415
  return
412
416
 
@@ -580,14 +580,17 @@ def _parse_lines(lines: Iterable[str], source_name: str) -> Script:
580
580
  command_text=line,
581
581
  other_msg=f"ANDIF without matching IF on line {file_lineno} of {source_name}.",
582
582
  )
583
- if_node = block_stack[-1].node
584
- if_node.condition_modifiers.append( # type: ignore[union-attr]
585
- ConditionModifier(
586
- kind="AND",
587
- condition=m.group("cond").strip(),
588
- span=SourceSpan(source_name, file_lineno),
589
- ),
583
+ modifier = ConditionModifier(
584
+ kind="AND",
585
+ condition=m.group("cond").strip(),
586
+ span=SourceSpan(source_name, file_lineno),
590
587
  )
588
+ frame = block_stack[-1]
589
+ if_node = frame.node
590
+ if frame._in_elseif and if_node.elseif_clauses: # type: ignore[union-attr]
591
+ if_node.elseif_clauses[-1].condition_modifiers.append(modifier) # type: ignore[union-attr]
592
+ else:
593
+ if_node.condition_modifiers.append(modifier) # type: ignore[union-attr]
591
594
  continue
592
595
 
593
596
  # -- ORIF --
@@ -599,14 +602,17 @@ def _parse_lines(lines: Iterable[str], source_name: str) -> Script:
599
602
  command_text=line,
600
603
  other_msg=f"ORIF without matching IF on line {file_lineno} of {source_name}.",
601
604
  )
602
- if_node = block_stack[-1].node
603
- if_node.condition_modifiers.append( # type: ignore[union-attr]
604
- ConditionModifier(
605
- kind="OR",
606
- condition=m.group("cond").strip(),
607
- span=SourceSpan(source_name, file_lineno),
608
- ),
605
+ modifier = ConditionModifier(
606
+ kind="OR",
607
+ condition=m.group("cond").strip(),
608
+ span=SourceSpan(source_name, file_lineno),
609
609
  )
610
+ frame = block_stack[-1]
611
+ if_node = frame.node
612
+ if frame._in_elseif and if_node.elseif_clauses: # type: ignore[union-attr]
613
+ if_node.elseif_clauses[-1].condition_modifiers.append(modifier) # type: ignore[union-attr]
614
+ else:
615
+ if_node.condition_modifiers.append(modifier) # type: ignore[union-attr]
610
616
  continue
611
617
 
612
618
  # -- ELSE --
@@ -1,4 +1,4 @@
1
- """Unit tests for SHOW SCRIPTS / SHOW SCRIPT metacommand handlers and helpers."""
1
+ """Unit tests for SHOW SCRIPTS metacommand handler and helpers."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
@@ -10,7 +10,6 @@ import pytest
10
10
  from execsql.metacommands.debug import (
11
11
  _format_script_signature,
12
12
  _format_script_source,
13
- x_show_script,
14
13
  x_show_scripts,
15
14
  )
16
15
  from execsql.script.ast import ParamDef, ScriptBlock, SourceSpan
@@ -93,7 +92,9 @@ def mock_state(monkeypatch):
93
92
  return state
94
93
 
95
94
 
96
- class TestShowScriptsHandler:
95
+ class TestShowScriptsListAll:
96
+ """SHOW SCRIPTS with no name argument lists all scripts."""
97
+
97
98
  def test_empty(self, mock_state):
98
99
  x_show_scripts(metacommandline="SHOW SCRIPTS")
99
100
  assert "No scripts registered" in mock_state.output.getvalue()
@@ -117,14 +118,16 @@ class TestShowScriptsHandler:
117
118
  assert "load(schema, batch=1000)" in mock_state.output.getvalue()
118
119
 
119
120
 
120
- class TestShowScriptHandler:
121
+ class TestShowScriptsDetail:
122
+ """SHOW SCRIPTS <name> shows detail for one script."""
123
+
121
124
  def test_not_found(self, mock_state):
122
- x_show_script(script_id="nonexistent", metacommandline="SHOW SCRIPT nonexistent")
125
+ x_show_scripts(script_id="nonexistent", metacommandline="SHOW SCRIPTS nonexistent")
123
126
  assert "No script named" in mock_state.output.getvalue()
124
127
 
125
128
  def test_detail_no_params(self, mock_state):
126
129
  mock_state.ast_scripts = {"proc": _make_script("proc")}
127
- x_show_script(script_id="proc", metacommandline="SHOW SCRIPT proc")
130
+ x_show_scripts(script_id="proc", metacommandline="SHOW SCRIPTS proc")
128
131
  output = mock_state.output.getvalue()
129
132
  assert "proc()" in output
130
133
  assert "Parameters: (none)" in output
@@ -133,7 +136,7 @@ class TestShowScriptHandler:
133
136
  mock_state.ast_scripts = {
134
137
  "load": _make_script("load", [ParamDef("schema"), ParamDef("batch", "1000")]),
135
138
  }
136
- x_show_script(script_id="load", metacommandline="SHOW SCRIPT load")
139
+ x_show_scripts(script_id="load", metacommandline="SHOW SCRIPTS load")
137
140
  output = mock_state.output.getvalue()
138
141
  assert "load(schema, batch=1000)" in output
139
142
  assert "(required)" in output
@@ -143,12 +146,19 @@ class TestShowScriptHandler:
143
146
  mock_state.ast_scripts = {
144
147
  "proc": _make_script("proc", doc="This is the docstring.\nSecond line."),
145
148
  }
146
- x_show_script(script_id="proc", metacommandline="SHOW SCRIPT proc")
149
+ x_show_scripts(script_id="proc", metacommandline="SHOW SCRIPTS proc")
147
150
  output = mock_state.output.getvalue()
148
151
  assert "This is the docstring." in output
149
152
  assert "Second line." in output
150
153
 
151
154
  def test_case_insensitive_lookup(self, mock_state):
152
155
  mock_state.ast_scripts = {"myproc": _make_script("myproc")}
153
- x_show_script(script_id="MYPROC", metacommandline="SHOW SCRIPT MYPROC")
156
+ x_show_scripts(script_id="MYPROC", metacommandline="SHOW SCRIPTS MYPROC")
154
157
  assert "myproc()" in mock_state.output.getvalue()
158
+
159
+ def test_no_script_id_kwarg_lists_all(self, mock_state):
160
+ """When script_id is not provided at all, list all scripts."""
161
+ mock_state.ast_scripts = {"proc": _make_script("proc")}
162
+ x_show_scripts(metacommandline="SHOW SCRIPTS")
163
+ output = mock_state.output.getvalue()
164
+ assert "Registered scripts (1)" in output