execsql2 2.32.0__tar.gz → 2.33.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 (507) hide show
  1. {execsql2-2.32.0 → execsql2-2.33.0}/CHANGELOG.md +14 -0
  2. {execsql2-2.32.0 → execsql2-2.33.0}/PKG-INFO +2 -2
  3. {execsql2-2.32.0 → execsql2-2.33.0}/README.md +1 -1
  4. {execsql2-2.32.0 → execsql2-2.33.0}/docs/about/divergence.md +10 -10
  5. {execsql2-2.32.0 → execsql2-2.33.0}/docs/guides/formatter.md +1 -1
  6. {execsql2-2.32.0 → execsql2-2.33.0}/docs/reference/lint.md +1 -1
  7. {execsql2-2.32.0 → execsql2-2.33.0}/docs/reference/metacommands.md +6 -4
  8. {execsql2-2.32.0 → execsql2-2.33.0}/extras/vscode-execsql/package-lock.json +2 -2
  9. {execsql2-2.32.0 → execsql2-2.33.0}/extras/vscode-execsql/package.json +1 -1
  10. {execsql2-2.32.0 → execsql2-2.33.0}/pyproject.toml +2 -2
  11. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/metacommands/reference.py +10 -0
  12. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/metacommands/upsert.py +13 -0
  13. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/script/engine.py +5 -7
  14. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/script/executor.py +3 -1
  15. {execsql2-2.32.0 → execsql2-2.33.0}/tests/integration/test_postgres.py +49 -0
  16. {execsql2-2.32.0 → execsql2-2.33.0}/tests/metacommands/test_pg_upsert.py +34 -0
  17. {execsql2-2.32.0 → execsql2-2.33.0}/tests/test_engine.py +22 -17
  18. execsql2-2.33.0/tests/test_metacommand_errors.py +98 -0
  19. {execsql2-2.32.0 → execsql2-2.33.0}/tests/test_script.py +3 -3
  20. {execsql2-2.32.0 → execsql2-2.33.0}/uv.lock +1 -1
  21. {execsql2-2.32.0 → execsql2-2.33.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  22. {execsql2-2.32.0 → execsql2-2.33.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  23. {execsql2-2.32.0 → execsql2-2.33.0}/.github/dependabot.yml +0 -0
  24. {execsql2-2.32.0 → execsql2-2.33.0}/.github/workflows/ci-cd.yml +0 -0
  25. {execsql2-2.32.0 → execsql2-2.33.0}/.gitignore +0 -0
  26. {execsql2-2.32.0 → execsql2-2.33.0}/.pre-commit-config.yaml +0 -0
  27. {execsql2-2.32.0 → execsql2-2.33.0}/.pre-commit-hooks.yaml +0 -0
  28. {execsql2-2.32.0 → execsql2-2.33.0}/.python-version +0 -0
  29. {execsql2-2.32.0 → execsql2-2.33.0}/.readthedocs.yaml +0 -0
  30. {execsql2-2.32.0 → execsql2-2.33.0}/CONTRIBUTING.md +0 -0
  31. {execsql2-2.32.0 → execsql2-2.33.0}/LICENSE.txt +0 -0
  32. {execsql2-2.32.0 → execsql2-2.33.0}/NOTICE +0 -0
  33. {execsql2-2.32.0 → execsql2-2.33.0}/SECURITY.md +0 -0
  34. {execsql2-2.32.0 → execsql2-2.33.0}/docs/about/contributors.md +0 -0
  35. {execsql2-2.32.0 → execsql2-2.33.0}/docs/about/copyright.md +0 -0
  36. {execsql2-2.32.0 → execsql2-2.33.0}/docs/api/cli.md +0 -0
  37. {execsql2-2.32.0 → execsql2-2.33.0}/docs/api/db.md +0 -0
  38. {execsql2-2.32.0 → execsql2-2.33.0}/docs/api/exporters.md +0 -0
  39. {execsql2-2.32.0 → execsql2-2.33.0}/docs/api/importers.md +0 -0
  40. {execsql2-2.32.0 → execsql2-2.33.0}/docs/api/index.md +0 -0
  41. {execsql2-2.32.0 → execsql2-2.33.0}/docs/api/metacommands.md +0 -0
  42. {execsql2-2.32.0 → execsql2-2.33.0}/docs/dev/adding_db_adapters.md +0 -0
  43. {execsql2-2.32.0 → execsql2-2.33.0}/docs/dev/adding_exporters.md +0 -0
  44. {execsql2-2.32.0 → execsql2-2.33.0}/docs/dev/adding_importers.md +0 -0
  45. {execsql2-2.32.0 → execsql2-2.33.0}/docs/dev/adding_metacommands.md +0 -0
  46. {execsql2-2.32.0 → execsql2-2.33.0}/docs/dev/architecture.md +0 -0
  47. {execsql2-2.32.0 → execsql2-2.33.0}/docs/dev/releasing.md +0 -0
  48. {execsql2-2.32.0 → execsql2-2.33.0}/docs/getting-started/installation.md +0 -0
  49. {execsql2-2.32.0 → execsql2-2.33.0}/docs/getting-started/requirements.md +0 -0
  50. {execsql2-2.32.0 → execsql2-2.33.0}/docs/getting-started/syntax.md +0 -0
  51. {execsql2-2.32.0 → execsql2-2.33.0}/docs/guides/debugging.md +0 -0
  52. {execsql2-2.32.0 → execsql2-2.33.0}/docs/guides/documentation.md +0 -0
  53. {execsql2-2.32.0 → execsql2-2.33.0}/docs/guides/editors.md +0 -0
  54. {execsql2-2.32.0 → execsql2-2.33.0}/docs/guides/encoding.md +0 -0
  55. {execsql2-2.32.0 → execsql2-2.33.0}/docs/guides/examples.md +0 -0
  56. {execsql2-2.32.0 → execsql2-2.33.0}/docs/guides/logging.md +0 -0
  57. {execsql2-2.32.0 → execsql2-2.33.0}/docs/guides/sql_syntax.md +0 -0
  58. {execsql2-2.32.0 → execsql2-2.33.0}/docs/guides/usage.md +0 -0
  59. {execsql2-2.32.0 → execsql2-2.33.0}/docs/guides/using_scripts.md +0 -0
  60. {execsql2-2.32.0 → execsql2-2.33.0}/docs/images/Compare_planets.png +0 -0
  61. {execsql2-2.32.0 → execsql2-2.33.0}/docs/images/actions.png +0 -0
  62. {execsql2-2.32.0 → execsql2-2.33.0}/docs/images/actions2.png +0 -0
  63. {execsql2-2.32.0 → execsql2-2.33.0}/docs/images/checkboxes.png +0 -0
  64. {execsql2-2.32.0 → execsql2-2.33.0}/docs/images/connect.png +0 -0
  65. {execsql2-2.32.0 → execsql2-2.33.0}/docs/images/data_error1_screenshot.jpg +0 -0
  66. {execsql2-2.32.0 → execsql2-2.33.0}/docs/images/execsql_logo_01.png +0 -0
  67. {execsql2-2.32.0 → execsql2-2.33.0}/docs/images/fatals.png +0 -0
  68. {execsql2-2.32.0 → execsql2-2.33.0}/docs/images/pause_terminal_sm.png +0 -0
  69. {execsql2-2.32.0 → execsql2-2.33.0}/docs/images/set_build_commands.jpg +0 -0
  70. {execsql2-2.32.0 → execsql2-2.33.0}/docs/images/unit_conversions_029.png +0 -0
  71. {execsql2-2.32.0 → execsql2-2.33.0}/docs/images/unmatched.png +0 -0
  72. {execsql2-2.32.0 → execsql2-2.33.0}/docs/images/vim_execsql_highlight.png +0 -0
  73. {execsql2-2.32.0 → execsql2-2.33.0}/docs/index.md +0 -0
  74. {execsql2-2.32.0 → execsql2-2.33.0}/docs/reference/configuration.md +0 -0
  75. {execsql2-2.32.0 → execsql2-2.33.0}/docs/reference/security.md +0 -0
  76. {execsql2-2.32.0 → execsql2-2.33.0}/docs/reference/substitution_vars.md +0 -0
  77. {execsql2-2.32.0 → execsql2-2.33.0}/docs/templates/compare.md +0 -0
  78. {execsql2-2.32.0 → execsql2-2.33.0}/docs/templates/glossary.md +0 -0
  79. {execsql2-2.32.0 → execsql2-2.33.0}/docs/templates/index.md +0 -0
  80. {execsql2-2.32.0 → execsql2-2.33.0}/docs/templates/upsert.md +0 -0
  81. {execsql2-2.32.0 → execsql2-2.33.0}/extras/plugin-template/README.md +0 -0
  82. {execsql2-2.32.0 → execsql2-2.33.0}/extras/plugin-template/pyproject.toml +0 -0
  83. {execsql2-2.32.0 → execsql2-2.33.0}/extras/plugin-template/src/execsql_plugin_YOURNAME/__init__.py +0 -0
  84. {execsql2-2.32.0 → execsql2-2.33.0}/extras/plugin-template/tests/test_plugin.py.example +0 -0
  85. {execsql2-2.32.0 → execsql2-2.33.0}/extras/vscode-execsql/.vscodeignore +0 -0
  86. {execsql2-2.32.0 → execsql2-2.33.0}/extras/vscode-execsql/LICENSE.txt +0 -0
  87. {execsql2-2.32.0 → execsql2-2.33.0}/extras/vscode-execsql/README.md +0 -0
  88. {execsql2-2.32.0 → execsql2-2.33.0}/extras/vscode-execsql/extension.js +0 -0
  89. {execsql2-2.32.0 → execsql2-2.33.0}/extras/vscode-execsql/icon.png +0 -0
  90. {execsql2-2.32.0 → execsql2-2.33.0}/extras/vscode-execsql/syntaxes/execsql.tmLanguage.json +0 -0
  91. {execsql2-2.32.0 → execsql2-2.33.0}/justfile +0 -0
  92. {execsql2-2.32.0 → execsql2-2.33.0}/scripts/benchmarks/README.md +0 -0
  93. {execsql2-2.32.0 → execsql2-2.33.0}/scripts/benchmarks/results/.gitignore +0 -0
  94. {execsql2-2.32.0 → execsql2-2.33.0}/scripts/benchmarks/run_benchmarks.py +0 -0
  95. {execsql2-2.32.0 → execsql2-2.33.0}/scripts/generate_vscode_grammar.py +0 -0
  96. {execsql2-2.32.0 → execsql2-2.33.0}/scripts/release_notes.py +0 -0
  97. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/__init__.py +0 -0
  98. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/__main__.py +0 -0
  99. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/api.py +0 -0
  100. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/cli/__init__.py +0 -0
  101. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/cli/application.py +0 -0
  102. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/cli/commands/__init__.py +0 -0
  103. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/cli/commands/config.py +0 -0
  104. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/cli/commands/format.py +0 -0
  105. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/cli/commands/init.py +0 -0
  106. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/cli/commands/lint.py +0 -0
  107. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/cli/commands/listing.py +0 -0
  108. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/cli/commands/lsp.py +0 -0
  109. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/cli/commands/run.py +0 -0
  110. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/cli/dispatch.py +0 -0
  111. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/cli/dsn.py +0 -0
  112. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/cli/help.py +0 -0
  113. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/cli/lint.py +0 -0
  114. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/cli/options.py +0 -0
  115. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/cli/run.py +0 -0
  116. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/config.py +0 -0
  117. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/config_validation.py +0 -0
  118. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/data/__init__.py +0 -0
  119. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/data/execsql.conf.template +0 -0
  120. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/data/script_header.sql +0 -0
  121. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/db/__init__.py +0 -0
  122. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/db/access.py +0 -0
  123. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/db/base.py +0 -0
  124. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/db/dsn.py +0 -0
  125. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/db/duckdb.py +0 -0
  126. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/db/factory.py +0 -0
  127. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/db/firebird.py +0 -0
  128. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/db/mysql.py +0 -0
  129. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/db/odbc.py +0 -0
  130. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/db/oracle.py +0 -0
  131. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/db/postgres.py +0 -0
  132. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/db/sqlite.py +0 -0
  133. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/db/sqlserver.py +0 -0
  134. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/db/tiers.py +0 -0
  135. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/debug/__init__.py +0 -0
  136. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/debug/repl.py +0 -0
  137. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/exceptions.py +0 -0
  138. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/exporters/__init__.py +0 -0
  139. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/exporters/base.py +0 -0
  140. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/exporters/delimited.py +0 -0
  141. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/exporters/duckdb.py +0 -0
  142. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/exporters/feather.py +0 -0
  143. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/exporters/html.py +0 -0
  144. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/exporters/json.py +0 -0
  145. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/exporters/latex.py +0 -0
  146. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/exporters/markdown.py +0 -0
  147. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/exporters/ods.py +0 -0
  148. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/exporters/parquet.py +0 -0
  149. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/exporters/pretty.py +0 -0
  150. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/exporters/protocol.py +0 -0
  151. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/exporters/raw.py +0 -0
  152. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/exporters/sqlite.py +0 -0
  153. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/exporters/templates.py +0 -0
  154. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/exporters/values.py +0 -0
  155. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/exporters/xls.py +0 -0
  156. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/exporters/xlsx.py +0 -0
  157. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/exporters/xml.py +0 -0
  158. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/exporters/yaml.py +0 -0
  159. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/exporters/zip.py +0 -0
  160. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/format.py +0 -0
  161. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/gui/__init__.py +0 -0
  162. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/gui/base.py +0 -0
  163. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/gui/console.py +0 -0
  164. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/gui/desktop.py +0 -0
  165. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/gui/tui.py +0 -0
  166. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/importers/__init__.py +0 -0
  167. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/importers/base.py +0 -0
  168. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/importers/csv.py +0 -0
  169. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/importers/feather.py +0 -0
  170. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/importers/json.py +0 -0
  171. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/importers/ods.py +0 -0
  172. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/importers/xls.py +0 -0
  173. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/interactive.py +0 -0
  174. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/lsp/__init__.py +0 -0
  175. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/lsp/_util.py +0 -0
  176. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/lsp/cache.py +0 -0
  177. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/lsp/code_actions.py +0 -0
  178. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/lsp/completion.py +0 -0
  179. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/lsp/diagnostics.py +0 -0
  180. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/lsp/document.py +0 -0
  181. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/lsp/folding.py +0 -0
  182. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/lsp/formatting.py +0 -0
  183. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/lsp/hover.py +0 -0
  184. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/lsp/navigation.py +0 -0
  185. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/lsp/rename.py +0 -0
  186. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/lsp/server.py +0 -0
  187. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/manifest.py +0 -0
  188. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/metacommands/__init__.py +0 -0
  189. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/metacommands/conditions.py +0 -0
  190. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/metacommands/connect.py +0 -0
  191. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/metacommands/control.py +0 -0
  192. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/metacommands/data.py +0 -0
  193. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/metacommands/debug.py +0 -0
  194. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/metacommands/dispatch.py +0 -0
  195. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/metacommands/effects.py +0 -0
  196. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/metacommands/io.py +0 -0
  197. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/metacommands/io_export.py +0 -0
  198. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/metacommands/io_fileops.py +0 -0
  199. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/metacommands/io_import.py +0 -0
  200. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/metacommands/io_write.py +0 -0
  201. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/metacommands/prompt.py +0 -0
  202. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/metacommands/script_ext.py +0 -0
  203. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/metacommands/system.py +0 -0
  204. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/models.py +0 -0
  205. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/parser.py +0 -0
  206. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/plugins.py +0 -0
  207. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/py.typed +0 -0
  208. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/script/__init__.py +0 -0
  209. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/script/ast.py +0 -0
  210. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/script/control.py +0 -0
  211. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/script/parser.py +0 -0
  212. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/script/variables.py +0 -0
  213. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/state.py +0 -0
  214. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/state.pyi +0 -0
  215. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/types.py +0 -0
  216. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/utils/__init__.py +0 -0
  217. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/utils/auth.py +0 -0
  218. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/utils/color.py +0 -0
  219. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/utils/crypto.py +0 -0
  220. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/utils/datetime.py +0 -0
  221. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/utils/errors.py +0 -0
  222. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/utils/fileio.py +0 -0
  223. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/utils/gui.py +0 -0
  224. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/utils/mail.py +0 -0
  225. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/utils/numeric.py +0 -0
  226. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/utils/paths.py +0 -0
  227. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/utils/regex.py +0 -0
  228. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/utils/strings.py +0 -0
  229. {execsql2-2.32.0 → execsql2-2.33.0}/src/execsql/utils/timer.py +0 -0
  230. {execsql2-2.32.0 → execsql2-2.33.0}/templates/README.md +0 -0
  231. {execsql2-2.32.0 → execsql2-2.33.0}/templates/config_settings.sqlite +0 -0
  232. {execsql2-2.32.0 → execsql2-2.33.0}/templates/example_config_prompt.sql +0 -0
  233. {execsql2-2.32.0 → execsql2-2.33.0}/templates/make_config_db.sql +0 -0
  234. {execsql2-2.32.0 → execsql2-2.33.0}/templates/md_compare.sql +0 -0
  235. {execsql2-2.32.0 → execsql2-2.33.0}/templates/md_glossary.sql +0 -0
  236. {execsql2-2.32.0 → execsql2-2.33.0}/templates/md_upsert.sql +0 -0
  237. {execsql2-2.32.0 → execsql2-2.33.0}/templates/pg_compare.sql +0 -0
  238. {execsql2-2.32.0 → execsql2-2.33.0}/templates/pg_glossary.sql +0 -0
  239. {execsql2-2.32.0 → execsql2-2.33.0}/templates/script_template.sql +0 -0
  240. {execsql2-2.32.0 → execsql2-2.33.0}/templates/ss_compare.sql +0 -0
  241. {execsql2-2.32.0 → execsql2-2.33.0}/templates/ss_glossary.sql +0 -0
  242. {execsql2-2.32.0 → execsql2-2.33.0}/templates/ss_upsert.sql +0 -0
  243. {execsql2-2.32.0 → execsql2-2.33.0}/tests/__init__.py +0 -0
  244. {execsql2-2.32.0 → execsql2-2.33.0}/tests/cli/__init__.py +0 -0
  245. {execsql2-2.32.0 → execsql2-2.33.0}/tests/cli/test_cli.py +0 -0
  246. {execsql2-2.32.0 → execsql2-2.33.0}/tests/cli/test_cli_e2e.py +0 -0
  247. {execsql2-2.32.0 → execsql2-2.33.0}/tests/cli/test_cli_run.py +0 -0
  248. {execsql2-2.32.0 → execsql2-2.33.0}/tests/cli/test_cli_surface.py +0 -0
  249. {execsql2-2.32.0 → execsql2-2.33.0}/tests/cli/test_commands.py +0 -0
  250. {execsql2-2.32.0 → execsql2-2.33.0}/tests/cli/test_config_validate.py +0 -0
  251. {execsql2-2.32.0 → execsql2-2.33.0}/tests/cli/test_dispatch.py +0 -0
  252. {execsql2-2.32.0 → execsql2-2.33.0}/tests/cli/test_error_location.py +0 -0
  253. {execsql2-2.32.0 → execsql2-2.33.0}/tests/cli/test_init.py +0 -0
  254. {execsql2-2.32.0 → execsql2-2.33.0}/tests/cli/test_lint.py +0 -0
  255. {execsql2-2.32.0 → execsql2-2.33.0}/tests/cli/test_lint_fixtures.py +0 -0
  256. {execsql2-2.32.0 → execsql2-2.33.0}/tests/cli/test_log_exit_record.py +0 -0
  257. {execsql2-2.32.0 → execsql2-2.33.0}/tests/cli/test_manifest.py +0 -0
  258. {execsql2-2.32.0 → execsql2-2.33.0}/tests/cli/test_ping.py +0 -0
  259. {execsql2-2.32.0 → execsql2-2.33.0}/tests/cli/test_profile.py +0 -0
  260. {execsql2-2.32.0 → execsql2-2.33.0}/tests/cli/test_sigterm.py +0 -0
  261. {execsql2-2.32.0 → execsql2-2.33.0}/tests/cli/test_undeclared_imports.py +0 -0
  262. {execsql2-2.32.0 → execsql2-2.33.0}/tests/conftest.py +0 -0
  263. {execsql2-2.32.0 → execsql2-2.33.0}/tests/data/formatter_shapes/comment_anchoring.sql +0 -0
  264. {execsql2-2.32.0 → execsql2-2.33.0}/tests/data/formatter_shapes/escapes_and_literals.sql +0 -0
  265. {execsql2-2.32.0 → execsql2-2.33.0}/tests/data/formatter_shapes/substitution_and_metacommands.sql +0 -0
  266. {execsql2-2.32.0 → execsql2-2.33.0}/tests/data/formatter_shapes/unparsable_sql.sql +0 -0
  267. {execsql2-2.32.0 → execsql2-2.33.0}/tests/data/lint/conditions_and_blocks.sql +0 -0
  268. {execsql2-2.32.0 → execsql2-2.33.0}/tests/data/lint/does_not_parse.sql +0 -0
  269. {execsql2-2.32.0 → execsql2-2.33.0}/tests/data/lint/every_metacommand.sql +0 -0
  270. {execsql2-2.32.0 → execsql2-2.33.0}/tests/data/lint/invalid.sql +0 -0
  271. {execsql2-2.32.0 → execsql2-2.33.0}/tests/data/ods/libreoffice.fods +0 -0
  272. {execsql2-2.32.0 → execsql2-2.33.0}/tests/data/ods/libreoffice.ods +0 -0
  273. {execsql2-2.32.0 → execsql2-2.33.0}/tests/db/__init__.py +0 -0
  274. {execsql2-2.32.0 → execsql2-2.33.0}/tests/db/conftest.py +0 -0
  275. {execsql2-2.32.0 → execsql2-2.33.0}/tests/db/test_access_windows.py +0 -0
  276. {execsql2-2.32.0 → execsql2-2.33.0}/tests/db/test_base.py +0 -0
  277. {execsql2-2.32.0 → execsql2-2.33.0}/tests/db/test_bulk_inserts.py +0 -0
  278. {execsql2-2.32.0 → execsql2-2.33.0}/tests/db/test_copy_new.py +0 -0
  279. {execsql2-2.32.0 → execsql2-2.33.0}/tests/db/test_db_adapters_mocked.py +0 -0
  280. {execsql2-2.32.0 → execsql2-2.33.0}/tests/db/test_db_adapters_mocked_extra.py +0 -0
  281. {execsql2-2.32.0 → execsql2-2.33.0}/tests/db/test_dsn.py +0 -0
  282. {execsql2-2.32.0 → execsql2-2.33.0}/tests/db/test_duckdb.py +0 -0
  283. {execsql2-2.32.0 → execsql2-2.33.0}/tests/db/test_factory.py +0 -0
  284. {execsql2-2.32.0 → execsql2-2.33.0}/tests/db/test_factory_postgres_encoding.py +0 -0
  285. {execsql2-2.32.0 → execsql2-2.33.0}/tests/db/test_import_atomic.py +0 -0
  286. {execsql2-2.32.0 → execsql2-2.33.0}/tests/db/test_import_replacement_quoted.py +0 -0
  287. {execsql2-2.32.0 → execsql2-2.33.0}/tests/db/test_import_short_rows.py +0 -0
  288. {execsql2-2.32.0 → execsql2-2.33.0}/tests/db/test_import_timezones.py +0 -0
  289. {execsql2-2.32.0 → execsql2-2.33.0}/tests/db/test_mysql_case_folding.py +0 -0
  290. {execsql2-2.32.0 → execsql2-2.33.0}/tests/db/test_mysql_inprocess.py +0 -0
  291. {execsql2-2.32.0 → execsql2-2.33.0}/tests/db/test_mysql_trailing_blank_lines.py +0 -0
  292. {execsql2-2.32.0 → execsql2-2.33.0}/tests/db/test_new_table_checks.py +0 -0
  293. {execsql2-2.32.0 → execsql2-2.33.0}/tests/db/test_odbc_fresh_sessions.py +0 -0
  294. {execsql2-2.32.0 → execsql2-2.33.0}/tests/db/test_oracle_driver.py +0 -0
  295. {execsql2-2.32.0 → execsql2-2.33.0}/tests/db/test_postgres.py +0 -0
  296. {execsql2-2.32.0 → execsql2-2.33.0}/tests/db/test_postgres_inprocess.py +0 -0
  297. {execsql2-2.32.0 → execsql2-2.33.0}/tests/db/test_quoted_substitution_roundtrip.py +0 -0
  298. {execsql2-2.32.0 → execsql2-2.33.0}/tests/db/test_replacement_missing_table.py +0 -0
  299. {execsql2-2.32.0 → execsql2-2.33.0}/tests/db/test_roundtrip_adapters.py +0 -0
  300. {execsql2-2.32.0 → execsql2-2.33.0}/tests/db/test_sqlite.py +0 -0
  301. {execsql2-2.32.0 → execsql2-2.33.0}/tests/db/test_sqlite_extra.py +0 -0
  302. {execsql2-2.32.0 → execsql2-2.33.0}/tests/db/test_sqlserver_driver18.py +0 -0
  303. {execsql2-2.32.0 → execsql2-2.33.0}/tests/db/test_sqlserver_inprocess.py +0 -0
  304. {execsql2-2.32.0 → execsql2-2.33.0}/tests/db/test_tiers.py +0 -0
  305. {execsql2-2.32.0 → execsql2-2.33.0}/tests/db/test_transactions.py +0 -0
  306. {execsql2-2.32.0 → execsql2-2.33.0}/tests/exporters/__init__.py +0 -0
  307. {execsql2-2.32.0 → execsql2-2.33.0}/tests/exporters/conftest.py +0 -0
  308. {execsql2-2.32.0 → execsql2-2.33.0}/tests/exporters/test_base.py +0 -0
  309. {execsql2-2.32.0 → execsql2-2.33.0}/tests/exporters/test_db.py +0 -0
  310. {execsql2-2.32.0 → execsql2-2.33.0}/tests/exporters/test_delimited.py +0 -0
  311. {execsql2-2.32.0 → execsql2-2.33.0}/tests/exporters/test_duckdb_exporter.py +0 -0
  312. {execsql2-2.32.0 → execsql2-2.33.0}/tests/exporters/test_exporters.py +0 -0
  313. {execsql2-2.32.0 → execsql2-2.33.0}/tests/exporters/test_feather.py +0 -0
  314. {execsql2-2.32.0 → execsql2-2.33.0}/tests/exporters/test_html_extended.py +0 -0
  315. {execsql2-2.32.0 → execsql2-2.33.0}/tests/exporters/test_html_latex.py +0 -0
  316. {execsql2-2.32.0 → execsql2-2.33.0}/tests/exporters/test_json.py +0 -0
  317. {execsql2-2.32.0 → execsql2-2.33.0}/tests/exporters/test_json_extended.py +0 -0
  318. {execsql2-2.32.0 → execsql2-2.33.0}/tests/exporters/test_latex_extended.py +0 -0
  319. {execsql2-2.32.0 → execsql2-2.33.0}/tests/exporters/test_markdown.py +0 -0
  320. {execsql2-2.32.0 → execsql2-2.33.0}/tests/exporters/test_ods.py +0 -0
  321. {execsql2-2.32.0 → execsql2-2.33.0}/tests/exporters/test_ods_export.py +0 -0
  322. {execsql2-2.32.0 → execsql2-2.33.0}/tests/exporters/test_parquet.py +0 -0
  323. {execsql2-2.32.0 → execsql2-2.33.0}/tests/exporters/test_pretty_extended.py +0 -0
  324. {execsql2-2.32.0 → execsql2-2.33.0}/tests/exporters/test_raw_extended.py +0 -0
  325. {execsql2-2.32.0 → execsql2-2.33.0}/tests/exporters/test_roundtrip_columnar.py +0 -0
  326. {execsql2-2.32.0 → execsql2-2.33.0}/tests/exporters/test_roundtrip_dbfile.py +0 -0
  327. {execsql2-2.32.0 → execsql2-2.33.0}/tests/exporters/test_roundtrip_delimited.py +0 -0
  328. {execsql2-2.32.0 → execsql2-2.33.0}/tests/exporters/test_roundtrip_json.py +0 -0
  329. {execsql2-2.32.0 → execsql2-2.33.0}/tests/exporters/test_roundtrip_latex_xml.py +0 -0
  330. {execsql2-2.32.0 → execsql2-2.33.0}/tests/exporters/test_roundtrip_markup.py +0 -0
  331. {execsql2-2.32.0 → execsql2-2.33.0}/tests/exporters/test_roundtrip_ods.py +0 -0
  332. {execsql2-2.32.0 → execsql2-2.33.0}/tests/exporters/test_roundtrip_pretty_raw.py +0 -0
  333. {execsql2-2.32.0 → execsql2-2.33.0}/tests/exporters/test_roundtrip_template.py +0 -0
  334. {execsql2-2.32.0 → execsql2-2.33.0}/tests/exporters/test_roundtrip_values.py +0 -0
  335. {execsql2-2.32.0 → execsql2-2.33.0}/tests/exporters/test_roundtrip_xlsx.py +0 -0
  336. {execsql2-2.32.0 → execsql2-2.33.0}/tests/exporters/test_sqlite_exporter.py +0 -0
  337. {execsql2-2.32.0 → execsql2-2.33.0}/tests/exporters/test_templates.py +0 -0
  338. {execsql2-2.32.0 → execsql2-2.33.0}/tests/exporters/test_templates_extended.py +0 -0
  339. {execsql2-2.32.0 → execsql2-2.33.0}/tests/exporters/test_values_extended.py +0 -0
  340. {execsql2-2.32.0 → execsql2-2.33.0}/tests/exporters/test_values_roundtrip.py +0 -0
  341. {execsql2-2.32.0 → execsql2-2.33.0}/tests/exporters/test_xls_xlsx.py +0 -0
  342. {execsql2-2.32.0 → execsql2-2.33.0}/tests/exporters/test_xlsx.py +0 -0
  343. {execsql2-2.32.0 → execsql2-2.33.0}/tests/exporters/test_xml.py +0 -0
  344. {execsql2-2.32.0 → execsql2-2.33.0}/tests/exporters/test_yaml.py +0 -0
  345. {execsql2-2.32.0 → execsql2-2.33.0}/tests/exporters/test_zip.py +0 -0
  346. {execsql2-2.32.0 → execsql2-2.33.0}/tests/gui/__init__.py +0 -0
  347. {execsql2-2.32.0 → execsql2-2.33.0}/tests/gui/test_backends.py +0 -0
  348. {execsql2-2.32.0 → execsql2-2.33.0}/tests/gui/test_backends_extended.py +0 -0
  349. {execsql2-2.32.0 → execsql2-2.33.0}/tests/gui/test_compare_stats.py +0 -0
  350. {execsql2-2.32.0 → execsql2-2.33.0}/tests/gui/test_compute_row_diffs.py +0 -0
  351. {execsql2-2.32.0 → execsql2-2.33.0}/tests/gui/test_desktop_dialogs.py +0 -0
  352. {execsql2-2.32.0 → execsql2-2.33.0}/tests/gui/test_format_table.py +0 -0
  353. {execsql2-2.32.0 → execsql2-2.33.0}/tests/gui/test_tui_pilot.py +0 -0
  354. {execsql2-2.32.0 → execsql2-2.33.0}/tests/gui/test_tui_pilot_complex.py +0 -0
  355. {execsql2-2.32.0 → execsql2-2.33.0}/tests/gui/test_utils_gui_extended.py +0 -0
  356. {execsql2-2.32.0 → execsql2-2.33.0}/tests/importers/__init__.py +0 -0
  357. {execsql2-2.32.0 → execsql2-2.33.0}/tests/importers/conftest.py +0 -0
  358. {execsql2-2.32.0 → execsql2-2.33.0}/tests/importers/test_base_extended.py +0 -0
  359. {execsql2-2.32.0 → execsql2-2.33.0}/tests/importers/test_csv_blank_lines.py +0 -0
  360. {execsql2-2.32.0 → execsql2-2.33.0}/tests/importers/test_csv_edge_cases.py +0 -0
  361. {execsql2-2.32.0 → execsql2-2.33.0}/tests/importers/test_csv_importer.py +0 -0
  362. {execsql2-2.32.0 → execsql2-2.33.0}/tests/importers/test_feather_importer.py +0 -0
  363. {execsql2-2.32.0 → execsql2-2.33.0}/tests/importers/test_json_importer.py +0 -0
  364. {execsql2-2.32.0 → execsql2-2.33.0}/tests/importers/test_ods_importer.py +0 -0
  365. {execsql2-2.32.0 → execsql2-2.33.0}/tests/importers/test_ods_values.py +0 -0
  366. {execsql2-2.32.0 → execsql2-2.33.0}/tests/importers/test_roundtrip_csv.py +0 -0
  367. {execsql2-2.32.0 → execsql2-2.33.0}/tests/importers/test_xls_importer.py +0 -0
  368. {execsql2-2.32.0 → execsql2-2.33.0}/tests/integration/__init__.py +0 -0
  369. {execsql2-2.32.0 → execsql2-2.33.0}/tests/integration/conftest.py +0 -0
  370. {execsql2-2.32.0 → execsql2-2.33.0}/tests/integration/test_dsn.py +0 -0
  371. {execsql2-2.32.0 → execsql2-2.33.0}/tests/integration/test_duckdb.py +0 -0
  372. {execsql2-2.32.0 → execsql2-2.33.0}/tests/integration/test_mysql.py +0 -0
  373. {execsql2-2.32.0 → execsql2-2.33.0}/tests/integration/test_sqlite.py +0 -0
  374. {execsql2-2.32.0 → execsql2-2.33.0}/tests/live_db.py +0 -0
  375. {execsql2-2.32.0 → execsql2-2.33.0}/tests/lsp/__init__.py +0 -0
  376. {execsql2-2.32.0 → execsql2-2.33.0}/tests/lsp/test_cache.py +0 -0
  377. {execsql2-2.32.0 → execsql2-2.33.0}/tests/lsp/test_code_actions.py +0 -0
  378. {execsql2-2.32.0 → execsql2-2.33.0}/tests/lsp/test_completion.py +0 -0
  379. {execsql2-2.32.0 → execsql2-2.33.0}/tests/lsp/test_diagnostics.py +0 -0
  380. {execsql2-2.32.0 → execsql2-2.33.0}/tests/lsp/test_document.py +0 -0
  381. {execsql2-2.32.0 → execsql2-2.33.0}/tests/lsp/test_folding.py +0 -0
  382. {execsql2-2.32.0 → execsql2-2.33.0}/tests/lsp/test_formatting.py +0 -0
  383. {execsql2-2.32.0 → execsql2-2.33.0}/tests/lsp/test_hover.py +0 -0
  384. {execsql2-2.32.0 → execsql2-2.33.0}/tests/lsp/test_lsp_command.py +0 -0
  385. {execsql2-2.32.0 → execsql2-2.33.0}/tests/lsp/test_navigation.py +0 -0
  386. {execsql2-2.32.0 → execsql2-2.33.0}/tests/lsp/test_rename.py +0 -0
  387. {execsql2-2.32.0 → execsql2-2.33.0}/tests/lsp/test_server.py +0 -0
  388. {execsql2-2.32.0 → execsql2-2.33.0}/tests/metacommands/__init__.py +0 -0
  389. {execsql2-2.32.0 → execsql2-2.33.0}/tests/metacommands/conftest.py +0 -0
  390. {execsql2-2.32.0 → execsql2-2.33.0}/tests/metacommands/test_assert.py +0 -0
  391. {execsql2-2.32.0 → execsql2-2.33.0}/tests/metacommands/test_breakpoint.py +0 -0
  392. {execsql2-2.32.0 → execsql2-2.33.0}/tests/metacommands/test_conditions_extra.py +0 -0
  393. {execsql2-2.32.0 → execsql2-2.33.0}/tests/metacommands/test_connect.py +0 -0
  394. {execsql2-2.32.0 → execsql2-2.33.0}/tests/metacommands/test_io_export.py +0 -0
  395. {execsql2-2.32.0 → execsql2-2.33.0}/tests/metacommands/test_io_import.py +0 -0
  396. {execsql2-2.32.0 → execsql2-2.33.0}/tests/metacommands/test_metacommands.py +0 -0
  397. {execsql2-2.32.0 → execsql2-2.33.0}/tests/metacommands/test_metacommands_connect.py +0 -0
  398. {execsql2-2.32.0 → execsql2-2.33.0}/tests/metacommands/test_metacommands_data.py +0 -0
  399. {execsql2-2.32.0 → execsql2-2.33.0}/tests/metacommands/test_metacommands_extended.py +0 -0
  400. {execsql2-2.32.0 → execsql2-2.33.0}/tests/metacommands/test_metacommands_fileops_extra.py +0 -0
  401. {execsql2-2.32.0 → execsql2-2.33.0}/tests/metacommands/test_metacommands_io.py +0 -0
  402. {execsql2-2.32.0 → execsql2-2.33.0}/tests/metacommands/test_metacommands_io_write_extra.py +0 -0
  403. {execsql2-2.32.0 → execsql2-2.33.0}/tests/metacommands/test_metacommands_script_ext.py +0 -0
  404. {execsql2-2.32.0 → execsql2-2.33.0}/tests/metacommands/test_metacommands_system.py +0 -0
  405. {execsql2-2.32.0 → execsql2-2.33.0}/tests/metacommands/test_metacommands_system_extra.py +0 -0
  406. {execsql2-2.32.0 → execsql2-2.33.0}/tests/metacommands/test_prompt.py +0 -0
  407. {execsql2-2.32.0 → execsql2-2.33.0}/tests/metacommands/test_row_count.py +0 -0
  408. {execsql2-2.32.0 → execsql2-2.33.0}/tests/metacommands/test_serve_output.py +0 -0
  409. {execsql2-2.32.0 → execsql2-2.33.0}/tests/metacommands/test_show_scripts.py +0 -0
  410. {execsql2-2.32.0 → execsql2-2.33.0}/tests/scripts/__init__.py +0 -0
  411. {execsql2-2.32.0 → execsql2-2.33.0}/tests/scripts/fixtures/audit_lint_bad.sql +0 -0
  412. {execsql2-2.32.0 → execsql2-2.33.0}/tests/scripts/fixtures/audit_smoke/sample.json +0 -0
  413. {execsql2-2.32.0 → execsql2-2.33.0}/tests/scripts/fixtures/audit_smoke/sample.jsonl +0 -0
  414. {execsql2-2.32.0 → execsql2-2.33.0}/tests/scripts/fixtures/audit_smoke.sql +0 -0
  415. {execsql2-2.32.0 → execsql2-2.33.0}/tests/scripts/fixtures/config_runtime.sql +0 -0
  416. {execsql2-2.32.0 → execsql2-2.33.0}/tests/scripts/fixtures/control_flow.sql +0 -0
  417. {execsql2-2.32.0 → execsql2-2.33.0}/tests/scripts/fixtures/counters_and_locals.sql +0 -0
  418. {execsql2-2.32.0 → execsql2-2.33.0}/tests/scripts/fixtures/error_handling.sql +0 -0
  419. {execsql2-2.32.0 → execsql2-2.33.0}/tests/scripts/fixtures/import_options/has_comments.csv +0 -0
  420. {execsql2-2.32.0 → execsql2-2.33.0}/tests/scripts/fixtures/import_options.sql +0 -0
  421. {execsql2-2.32.0 → execsql2-2.33.0}/tests/scripts/fixtures/includes/helper.sql +0 -0
  422. {execsql2-2.32.0 → execsql2-2.33.0}/tests/scripts/fixtures/includes.sql +0 -0
  423. {execsql2-2.32.0 → execsql2-2.33.0}/tests/scripts/fixtures/io_formats.sql +0 -0
  424. {execsql2-2.32.0 → execsql2-2.33.0}/tests/scripts/fixtures/io_roundtrip.sql +0 -0
  425. {execsql2-2.32.0 → execsql2-2.33.0}/tests/scripts/fixtures/multi_db.sql +0 -0
  426. {execsql2-2.32.0 → execsql2-2.33.0}/tests/scripts/fixtures/parquet_feather_roundtrip.sql +0 -0
  427. {execsql2-2.32.0 → execsql2-2.33.0}/tests/scripts/fixtures/parse_only/parse_tree.sql +0 -0
  428. {execsql2-2.32.0 → execsql2-2.33.0}/tests/scripts/fixtures/predicates.sql +0 -0
  429. {execsql2-2.32.0 → execsql2-2.33.0}/tests/scripts/fixtures/scripts.sql +0 -0
  430. {execsql2-2.32.0 → execsql2-2.33.0}/tests/scripts/fixtures/smoke.sql +0 -0
  431. {execsql2-2.32.0 → execsql2-2.33.0}/tests/scripts/fixtures/subroutine_loops.sql +0 -0
  432. {execsql2-2.32.0 → execsql2-2.33.0}/tests/scripts/fixtures/subvars_advanced.sql +0 -0
  433. {execsql2-2.32.0 → execsql2-2.33.0}/tests/scripts/fixtures/template_export/greeting.tmpl +0 -0
  434. {execsql2-2.32.0 → execsql2-2.33.0}/tests/scripts/fixtures/template_export.sql +0 -0
  435. {execsql2-2.32.0 → execsql2-2.33.0}/tests/scripts/fixtures/timer.sql +0 -0
  436. {execsql2-2.32.0 → execsql2-2.33.0}/tests/scripts/fixtures/transactions.sql +0 -0
  437. {execsql2-2.32.0 → execsql2-2.33.0}/tests/scripts/fixtures/write_create_table.sql +0 -0
  438. {execsql2-2.32.0 → execsql2-2.33.0}/tests/scripts/fixtures/xlsx_ods_roundtrip.sql +0 -0
  439. {execsql2-2.32.0 → execsql2-2.33.0}/tests/scripts/test_sql_scripts.py +0 -0
  440. {execsql2-2.32.0 → execsql2-2.33.0}/tests/security/__init__.py +0 -0
  441. {execsql2-2.32.0 → execsql2-2.33.0}/tests/security/test_env_detection.py +0 -0
  442. {execsql2-2.32.0 → execsql2-2.33.0}/tests/security/test_expansion_bomb.py +0 -0
  443. {execsql2-2.32.0 → execsql2-2.33.0}/tests/security/test_import_regex_hardening.py +0 -0
  444. {execsql2-2.32.0 → execsql2-2.33.0}/tests/security/test_log_redaction.py +0 -0
  445. {execsql2-2.32.0 → execsql2-2.33.0}/tests/security/test_log_secrets.py +0 -0
  446. {execsql2-2.32.0 → execsql2-2.33.0}/tests/security/test_network_paths.py +0 -0
  447. {execsql2-2.32.0 → execsql2-2.33.0}/tests/security/test_odbc_connection_strings.py +0 -0
  448. {execsql2-2.32.0 → execsql2-2.33.0}/tests/security/test_odbc_injection.py +0 -0
  449. {execsql2-2.32.0 → execsql2-2.33.0}/tests/security/test_path_containment.py +0 -0
  450. {execsql2-2.32.0 → execsql2-2.33.0}/tests/security/test_substitution_injection.py +0 -0
  451. {execsql2-2.32.0 → execsql2-2.33.0}/tests/security/test_zip_bomb.py +0 -0
  452. {execsql2-2.32.0 → execsql2-2.33.0}/tests/templates/__init__.py +0 -0
  453. {execsql2-2.32.0 → execsql2-2.33.0}/tests/templates/conftest.py +0 -0
  454. {execsql2-2.32.0 → execsql2-2.33.0}/tests/templates/test_compare.py +0 -0
  455. {execsql2-2.32.0 → execsql2-2.33.0}/tests/templates/test_glossary.py +0 -0
  456. {execsql2-2.32.0 → execsql2-2.33.0}/tests/templates/test_static.py +0 -0
  457. {execsql2-2.32.0 → execsql2-2.33.0}/tests/templates/test_upsert.py +0 -0
  458. {execsql2-2.32.0 → execsql2-2.33.0}/tests/test_api.py +0 -0
  459. {execsql2-2.32.0 → execsql2-2.33.0}/tests/test_api_errors.py +0 -0
  460. {execsql2-2.32.0 → execsql2-2.33.0}/tests/test_api_extended.py +0 -0
  461. {execsql2-2.32.0 → execsql2-2.33.0}/tests/test_api_filewriter.py +0 -0
  462. {execsql2-2.32.0 → execsql2-2.33.0}/tests/test_api_isolation.py +0 -0
  463. {execsql2-2.32.0 → execsql2-2.33.0}/tests/test_api_subprocess.py +0 -0
  464. {execsql2-2.32.0 → execsql2-2.33.0}/tests/test_ast.py +0 -0
  465. {execsql2-2.32.0 → execsql2-2.33.0}/tests/test_ast_parser.py +0 -0
  466. {execsql2-2.32.0 → execsql2-2.33.0}/tests/test_bundled_templates.py +0 -0
  467. {execsql2-2.32.0 → execsql2-2.33.0}/tests/test_config.py +0 -0
  468. {execsql2-2.32.0 → execsql2-2.33.0}/tests/test_config_data.py +0 -0
  469. {execsql2-2.32.0 → execsql2-2.33.0}/tests/test_config_extended.py +0 -0
  470. {execsql2-2.32.0 → execsql2-2.33.0}/tests/test_date_inference.py +0 -0
  471. {execsql2-2.32.0 → execsql2-2.33.0}/tests/test_debug_repl.py +0 -0
  472. {execsql2-2.32.0 → execsql2-2.33.0}/tests/test_error_messages.py +0 -0
  473. {execsql2-2.32.0 → execsql2-2.33.0}/tests/test_exceptions.py +0 -0
  474. {execsql2-2.32.0 → execsql2-2.33.0}/tests/test_executor.py +0 -0
  475. {execsql2-2.32.0 → execsql2-2.33.0}/tests/test_executor_inprocess.py +0 -0
  476. {execsql2-2.32.0 → execsql2-2.33.0}/tests/test_format.py +0 -0
  477. {execsql2-2.32.0 → execsql2-2.33.0}/tests/test_interactive.py +0 -0
  478. {execsql2-2.32.0 → execsql2-2.33.0}/tests/test_mail.py +0 -0
  479. {execsql2-2.32.0 → execsql2-2.33.0}/tests/test_models.py +0 -0
  480. {execsql2-2.32.0 → execsql2-2.33.0}/tests/test_package.py +0 -0
  481. {execsql2-2.32.0 → execsql2-2.33.0}/tests/test_parser.py +0 -0
  482. {execsql2-2.32.0 → execsql2-2.33.0}/tests/test_parser_params.py +0 -0
  483. {execsql2-2.32.0 → execsql2-2.33.0}/tests/test_plugins.py +0 -0
  484. {execsql2-2.32.0 → execsql2-2.33.0}/tests/test_reference.py +0 -0
  485. {execsql2-2.32.0 → execsql2-2.33.0}/tests/test_registry.py +0 -0
  486. {execsql2-2.32.0 → execsql2-2.33.0}/tests/test_release_notes.py +0 -0
  487. {execsql2-2.32.0 → execsql2-2.33.0}/tests/test_state.py +0 -0
  488. {execsql2-2.32.0 → execsql2-2.33.0}/tests/test_tooling_versions.py +0 -0
  489. {execsql2-2.32.0 → execsql2-2.33.0}/tests/test_types.py +0 -0
  490. {execsql2-2.32.0 → execsql2-2.33.0}/tests/utils/__init__.py +0 -0
  491. {execsql2-2.32.0 → execsql2-2.33.0}/tests/utils/test_auth.py +0 -0
  492. {execsql2-2.32.0 → execsql2-2.33.0}/tests/utils/test_auth_extra.py +0 -0
  493. {execsql2-2.32.0 → execsql2-2.33.0}/tests/utils/test_crypto.py +0 -0
  494. {execsql2-2.32.0 → execsql2-2.33.0}/tests/utils/test_datetime.py +0 -0
  495. {execsql2-2.32.0 → execsql2-2.33.0}/tests/utils/test_encodedfile_context.py +0 -0
  496. {execsql2-2.32.0 → execsql2-2.33.0}/tests/utils/test_errors.py +0 -0
  497. {execsql2-2.32.0 → execsql2-2.33.0}/tests/utils/test_errors_extra.py +0 -0
  498. {execsql2-2.32.0 → execsql2-2.33.0}/tests/utils/test_fileio.py +0 -0
  499. {execsql2-2.32.0 → execsql2-2.33.0}/tests/utils/test_fileio_extra.py +0 -0
  500. {execsql2-2.32.0 → execsql2-2.33.0}/tests/utils/test_keyring_rules.py +0 -0
  501. {execsql2-2.32.0 → execsql2-2.33.0}/tests/utils/test_mail_tls.py +0 -0
  502. {execsql2-2.32.0 → execsql2-2.33.0}/tests/utils/test_numeric.py +0 -0
  503. {execsql2-2.32.0 → execsql2-2.33.0}/tests/utils/test_regex.py +0 -0
  504. {execsql2-2.32.0 → execsql2-2.33.0}/tests/utils/test_strings.py +0 -0
  505. {execsql2-2.32.0 → execsql2-2.33.0}/tests/utils/test_timer.py +0 -0
  506. {execsql2-2.32.0 → execsql2-2.33.0}/tests/utils/test_timer_extra.py +0 -0
  507. {execsql2-2.32.0 → execsql2-2.33.0}/zensical.toml +0 -0
@@ -13,6 +13,20 @@ ______________________________________________________________________
13
13
 
14
14
  ______________________________________________________________________
15
15
 
16
+ ## [2.33.0] - 2026-10-16
17
+
18
+ ### Added
19
+
20
+ - **`$PG_UPSERT_STATUS`** says how a `PG_UPSERT` ended (`committed`, `dry_run`, `no_changes`, `qa_failed`, `cancelled`, `db_error`), and **`$PG_UPSERT_ERROR`** holds the database error message. A run with nothing to load and a cancelled run used to look the same in the other variables.
21
+
22
+ ### Fixed
23
+
24
+ - **A failed metacommand sets `$LAST_ERROR` and `$ERROR_MESSAGE`** with `METACOMMAND_ERROR_HALT OFF`, so a script can tell what failed, not only that `METACOMMAND_ERROR()` is true; an `ON ERROR_HALT` handler (including `ON ERROR_HALT WRITE "…!{$LAST_ERROR}!"`) now gets `$LAST_ERROR` too. The error is also written to `execsql.log`.
25
+ - **`execsql.run()` reports a metacommand that failed under `METACOMMAND_ERROR_HALT OFF`** in `result.errors`, with `success` False, as it already did for a SQL error under `ERROR_HALT OFF`.
26
+ - **`PG_UPSERT` cancelled from a prompt** no longer leaves an earlier run's `$PG_UPSERT_*` values (such as `$PG_UPSERT_COMMITTED` `TRUE`) in place.
27
+
28
+ ______________________________________________________________________
29
+
16
30
  ## [2.32.0] - 2026-10-16
17
31
 
18
32
  ### Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: execsql2
3
- Version: 2.32.0
3
+ Version: 2.33.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: Homepage, https://execsql2.readthedocs.io/en/stable/
6
6
  Project-URL: Repository, https://github.com/geocoug/execsql
@@ -441,7 +441,7 @@ execsql format --no-sql --in-place scripts/
441
441
  ```yaml
442
442
  repos:
443
443
  - repo: https://github.com/geocoug/execsql
444
- rev: v2.32.0
444
+ rev: v2.33.0
445
445
  hooks:
446
446
  - id: execsql-format
447
447
  - id: execsql-lint
@@ -321,7 +321,7 @@ execsql format --no-sql --in-place scripts/
321
321
  ```yaml
322
322
  repos:
323
323
  - repo: https://github.com/geocoug/execsql
324
- rev: v2.32.0
324
+ rev: v2.33.0
325
325
  hooks:
326
326
  - id: execsql-format
327
327
  - id: execsql-lint
@@ -48,15 +48,15 @@ ______________________________________________________________________
48
48
 
49
49
  ### Metacommands
50
50
 
51
- | Metacommand | Description |
52
- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
53
- | `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. |
54
- | `BREAKPOINT` | Pause script execution and drop into an interactive debug REPL. See [Debugging](#debugging) below for full details. |
55
- | `CONFIG SHOW_PROGRESS` | Enable the Rich progress bar for IMPORT operations at runtime. |
56
- | `CONFIG LOG_SQL` | Enable SQL query audit logging — writes executed SQL to the log file. |
57
- | `PG_UPSERT` | QA-checked, FK-dependency-ordered upserts from staging to base schema on PostgreSQL via the optional [pg-upsert](https://pg-upsert.readthedocs.io/) dependency. Modes: `PG_UPSERT` (full pipeline), `PG_UPSERT QA` (checks only), `PG_UPSERT CHECK` (schema only). QA checks only the rows the given `METHOD` would write. QA failure does not raise — the result is reported via `$PG_UPSERT_QA_PASSED` so the caller's `IF` / `ASSERT` decides control flow. |
58
- | `IMPORT … FROM JSON` | Import a JSON file (array of objects or [JSON Lines](https://jsonlines.org/)) into a database table. Nested objects are flattened with dot-separated column names; arrays are stored as JSON strings. |
59
- | `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. |
51
+ | Metacommand | Description |
52
+ | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
53
+ | `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. |
54
+ | `BREAKPOINT` | Pause script execution and drop into an interactive debug REPL. See [Debugging](#debugging) below for full details. |
55
+ | `CONFIG SHOW_PROGRESS` | Enable the Rich progress bar for IMPORT operations at runtime. |
56
+ | `CONFIG LOG_SQL` | Enable SQL query audit logging — writes executed SQL to the log file. |
57
+ | `PG_UPSERT` | QA-checked, FK-dependency-ordered upserts from staging to base schema on PostgreSQL via the optional [pg-upsert](https://pg-upsert.readthedocs.io/) dependency. Modes: `PG_UPSERT` (full pipeline), `PG_UPSERT QA` (checks only), `PG_UPSERT CHECK` (schema only). QA checks only the rows the given `METHOD` would write. QA failure does not raise — the result is reported via `$PG_UPSERT_QA_PASSED` so the caller's `IF` / `ASSERT` decides control flow; `$PG_UPSERT_STATUS` says how the run ended. |
58
+ | `IMPORT … FROM JSON` | Import a JSON file (array of objects or [JSON Lines](https://jsonlines.org/)) into a database table. Nested objects are flattened with dot-separated column names; arrays are stored as JSON strings. |
59
+ | `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. |
60
60
 
61
61
  ### SCRIPT Enhancements
62
62
 
@@ -349,7 +349,7 @@ These are behavioral changes driven by security or correctness issues in the ups
349
349
  | `WRITE` after `CD` | A relative `WRITE ... TO` path is resolved against the working directory at the time of the `WRITE`, so it follows an earlier `CD` the way `EXPORT` and `IMPORT` already did. Upstream wrote it relative to the directory execsql started in, because its writer subprocess never saw the `CD`. |
350
350
  | Substitution variable cycles | 100-iteration limit prevents infinite loops on cyclic variable references. |
351
351
  | Script location in error messages | `ErrInfo.script_file` and `script_line_no` are now populated via `stamp_errinfo()` so error output includes "Line N of script foo.sql" context — restoring behavior present in the monolith. |
352
- | `$ERROR_MESSAGE` not updated | `$ERROR_MESSAGE` is now set on every error path: `exit_now()`, non-halting SQL errors, and non-halting metacommand errors. Previously it was initialized to `""` and never changed. |
352
+ | `$ERROR_MESSAGE` not updated | `$ERROR_MESSAGE` is now set on every error path: `exit_now()`, non-halting SQL errors, and non-halting metacommand errors. Previously it was initialized to `""` and never changed. `$LAST_ERROR` also holds a failed metacommand, with `METACOMMAND_ERROR_HALT OFF` and in an `ON ERROR_HALT` handler, as the documentation describes; upstream set it only for SQL errors and unknown metacommands. |
353
353
  | Metacommand error message lost | When `halt_on_metacommand_err` is `ON`, the original handler `ErrInfo` is now re-raised; the generic "Unknown metacommand" message no longer replaces the specific error from the handler. |
354
354
  | 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. |
355
355
  | `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. |
@@ -272,7 +272,7 @@ select id,name,created_at from users where active = true order by name;
272
272
  ```yaml
273
273
  repos:
274
274
  - repo: https://github.com/geocoug/execsql
275
- rev: v2.32.0
275
+ rev: v2.33.0
276
276
  hooks:
277
277
  - id: execsql-format
278
278
  - id: execsql-lint # optional: fail the commit on lint errors
@@ -158,7 +158,7 @@ The `execsql-lint` [pre-commit](https://pre-commit.com/) hook runs `execsql lint
158
158
  ```yaml
159
159
  repos:
160
160
  - repo: https://github.com/geocoug/execsql
161
- rev: v2.32.0
161
+ rev: v2.33.0
162
162
  hooks:
163
163
  - id: execsql-lint
164
164
  args: [--ignore, V002] # optional: any execsql lint option
@@ -1766,7 +1766,7 @@ Evaluates whether or not the specified value is equal to zero. If the value is n
1766
1766
  METACOMMAND_ERROR()
1767
1767
  ```
1768
1768
 
1769
- Evaluates whether the previous metacommand generated an error. This test for SQL errors will only be effective if the [METACOMMAND_ERROR_HALT OFF](#metacommanderrorhalt) metacommand has previously been issued. This conditional must be used in the first metacommand after any metacommand that might have encountered an error.
1769
+ Evaluates whether the previous metacommand generated an error. This test for SQL errors will only be effective if the [METACOMMAND_ERROR_HALT OFF](#metacommanderrorhalt) metacommand has previously been issued. This conditional must be used in the first metacommand after any metacommand that might have encountered an error: any metacommand that succeeds, even a WRITE, clears the flag. The error itself stays available until the next error: [$LAST_ERROR](substitution_vars.md#system_vars) holds the metacommand that failed and [$ERROR_MESSAGE](substitution_vars.md#system_vars) its message.
1770
1770
 
1771
1771
 
1772
1772
  ### *NEWER_DATE*
@@ -2109,7 +2109,7 @@ The [BREAK](#break) metacommand can be used to exit from a loop at any time.
2109
2109
  METACOMMAND_ERROR_HALT ON|OFF
2110
2110
  ```
2111
2111
 
2112
- When METACOMMAND_ERROR_HALT is set to ON, which is the default, any errors that occur during execution of a metacommand will cause an error message to be displayed immediately, and *execsql* to halt. When METACOMMAND_ERROR_HALT is set to OFF, then metacommand errors will be ignored, but can be evaluated with the [IF METACOMMAND_ERROR](#metacommanderror) conditional.
2112
+ When METACOMMAND_ERROR_HALT is set to ON, which is the default, any errors that occur during execution of a metacommand will cause an error message to be displayed immediately, and *execsql* to halt. When METACOMMAND_ERROR_HALT is set to OFF, then metacommand errors will be ignored, but can be evaluated with the [IF METACOMMAND_ERROR](#metacommanderror) conditional; `$LAST_ERROR` and `$ERROR_MESSAGE` hold the failed metacommand and its message.
2113
2113
 
2114
2114
  The METACOMMAND_ERROR_HALT metacommand does not itself set or reset the internal flag that indicates whether a metacommand has encountered an error. This is so that a "METACOMMAND_ERROR_HALT Off" command can be used immediately after a potentially-failing metacommand and not alter the error flag that is set by the previous command. Specifically, constructions like this:
2115
2115
 
@@ -2356,6 +2356,8 @@ Set after every `PG_UPSERT` execution:
2356
2356
  | `$PG_UPSERT_ROWS_UPDATED` | integer | Total rows updated across all tables |
2357
2357
  | `$PG_UPSERT_ROWS_INSERTED` | integer | Total rows inserted across all tables |
2358
2358
  | `$PG_UPSERT_COMMITTED` | TRUE/FALSE | Were changes committed? |
2359
+ | `$PG_UPSERT_STATUS` | string | How the run ended: `committed`, `dry_run` (changes made, then rolled back because `COMMIT` was not given), `no_changes` (QA passed, nothing to insert or update), `qa_failed`, `cancelled` or `db_error`. Empty after `PG_UPSERT QA` and `PG_UPSERT CHECK`, which do not upsert. |
2360
+ | `$PG_UPSERT_ERROR` | string | The database error message when `$PG_UPSERT_STATUS` is `db_error`; otherwise empty |
2359
2361
  | `$PG_UPSERT_STAGING_SCHEMA` | string | Staging schema name used |
2360
2362
  | `$PG_UPSERT_BASE_SCHEMA` | string | Base schema name used |
2361
2363
  | `$PG_UPSERT_TABLES` | string | Comma-separated list of table names processed |
@@ -2380,7 +2382,7 @@ Set after every `PG_UPSERT` execution:
2380
2382
 
2381
2383
  ### QA failure behavior
2382
2384
 
2383
- `PG_UPSERT`, `PG_UPSERT QA`, and `PG_UPSERT CHECK` do **not** raise an error when QA checks fail. The outcome is reported via `$PG_UPSERT_QA_PASSED` (and the per-table `$PG_UPSERT_TABLE_QA_PASSED`), so the script controls flow with `IF` or with execsql's standard error-halt configuration. When QA fails, the metacommand still runs `EXPORT_FAILURES`, populates all `$PG_UPSERT_*` substitution variables, and skips the upsert (no commit). Pair with `ASSERT` to halt explicitly:
2385
+ `PG_UPSERT`, `PG_UPSERT QA`, and `PG_UPSERT CHECK` do **not** raise an error when QA checks fail. The outcome is reported via `$PG_UPSERT_QA_PASSED` (and the per-table `$PG_UPSERT_TABLE_QA_PASSED`), so the script controls flow with `IF` or with execsql's standard error-halt configuration. When QA fails, the metacommand still runs `EXPORT_FAILURES`, populates all `$PG_UPSERT_*` substitution variables, and skips the upsert (no commit). To tell the ways a run can end apart, branch on `$PG_UPSERT_STATUS` rather than combining `$PG_UPSERT_QA_PASSED` with `$PG_UPSERT_COMMITTED`: a run with nothing to load (`no_changes`) and one the user cancelled (`cancelled`) both leave QA passed and nothing committed. Pair with `ASSERT` to halt explicitly:
2384
2386
 
2385
2387
  ```sql
2386
2388
  -- Soft failure: branch on the result, keep running.
@@ -2398,7 +2400,7 @@ Set after every `PG_UPSERT` execution:
2398
2400
 
2399
2401
  ### Database errors
2400
2402
 
2401
- A database error during the run (a deadlock, a lock timeout, a constraint or trigger error the QA checks did not catch) is different from a QA failure. pg-upsert rolls the run back, and `PG_UPSERT` fails like any other metacommand: the driver's message is in `$ERROR_MESSAGE`, and the script halts unless `METACOMMAND_ERROR_HALT` is off. The `$PG_UPSERT_*` variables describe the failed run (`$PG_UPSERT_COMMITTED` is `FALSE` and the row counts are `0`), not an earlier `PG_UPSERT`.
2403
+ A database error during the run (a deadlock, a lock timeout, a constraint or trigger error the QA checks did not catch) is different from a QA failure. pg-upsert rolls the run back, and `PG_UPSERT` fails like any other metacommand: the driver's message is in `$ERROR_MESSAGE`, and the script halts unless `METACOMMAND_ERROR_HALT` is off. The `$PG_UPSERT_*` variables describe the failed run (`$PG_UPSERT_STATUS` is `db_error`, `$PG_UPSERT_ERROR` holds the message, `$PG_UPSERT_COMMITTED` is `FALSE` and the row counts are `0`), not an earlier `PG_UPSERT`. Cancelling at one of pg-upsert's own prompts is not an error: `PG_UPSERT` returns, with `$PG_UPSERT_STATUS` `cancelled`.
2402
2404
 
2403
2405
  The rollback ends the connection's transaction, so under `AUTOCOMMIT OFF` or inside `BEGIN BATCH`, statements run earlier on the same connection are rolled back with it.
2404
2406
 
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "execsql-syntax",
3
- "version": "2.32.0",
3
+ "version": "2.33.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "execsql-syntax",
9
- "version": "2.32.0",
9
+ "version": "2.33.0",
10
10
  "license": "GPL-3.0-or-later",
11
11
  "dependencies": {
12
12
  "vscode-languageclient": "10.1.2"
@@ -2,7 +2,7 @@
2
2
  "name": "execsql-syntax",
3
3
  "displayName": "execsql",
4
4
  "description": "Syntax highlighting, lint findings, completion, hover, navigation, quick fixes and formatting for execsql scripts.",
5
- "version": "2.32.0",
5
+ "version": "2.33.0",
6
6
  "publisher": "geocoug",
7
7
  "license": "GPL-3.0-or-later",
8
8
  "icon": "icon.png",
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "execsql2"
7
- version = "2.32.0"
7
+ version = "2.33.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" }
@@ -188,7 +188,7 @@ line-ending = "auto"
188
188
  exclude = ["*.md"]
189
189
 
190
190
  [tool.bumpversion]
191
- current_version = "2.32.0"
191
+ current_version = "2.33.0"
192
192
  commit = true
193
193
  tag = true
194
194
  tag_name = "v{new_version}"
@@ -1364,6 +1364,16 @@ _SYSTEM_VARIABLES: tuple[SystemVariable, ...] = (
1364
1364
  "TRUE or FALSE: whether the changes were committed.",
1365
1365
  "metacommands.md#pg_upsert",
1366
1366
  ),
1367
+ SystemVariable(
1368
+ "$PG_UPSERT_STATUS",
1369
+ "How the run ended: committed, dry_run, no_changes, qa_failed, cancelled or db_error.",
1370
+ "metacommands.md#pg_upsert",
1371
+ ),
1372
+ SystemVariable(
1373
+ "$PG_UPSERT_ERROR",
1374
+ "The database error message when $PG_UPSERT_STATUS is db_error; otherwise empty.",
1375
+ "metacommands.md#pg_upsert",
1376
+ ),
1367
1377
  SystemVariable("$PG_UPSERT_STAGING_SCHEMA", "Staging schema name used.", "metacommands.md#pg_upsert"),
1368
1378
  SystemVariable("$PG_UPSERT_BASE_SCHEMA", "Base schema name used.", "metacommands.md#pg_upsert"),
1369
1379
  SystemVariable("$PG_UPSERT_TABLES", "Comma-separated list of table names processed.", "metacommands.md#pg_upsert"),
@@ -185,6 +185,11 @@ def _set_subvars(result: Any) -> None:
185
185
  sv("$PG_UPSERT_ROWS_UPDATED", str(result.total_updated))
186
186
  sv("$PG_UPSERT_ROWS_INSERTED", str(result.total_inserted))
187
187
  sv("$PG_UPSERT_COMMITTED", str(result.committed).upper())
188
+ # How the run ended: committed, dry_run, no_changes, qa_failed, cancelled
189
+ # or db_error. Empty for QA and CHECK, which do not run an upsert.
190
+ status = getattr(result, "status", None)
191
+ sv("$PG_UPSERT_STATUS", status.value if status is not None else "")
192
+ sv("$PG_UPSERT_ERROR", getattr(result, "error", None) or "")
188
193
  sv("$PG_UPSERT_STAGING_SCHEMA", result.staging_schema)
189
194
  sv("$PG_UPSERT_BASE_SCHEMA", result.base_schema)
190
195
  sv("$PG_UPSERT_TABLES", ", ".join(t.table_name for t in result.tables))
@@ -426,6 +431,14 @@ def _handle_pg_upsert_errors(fn: Any, metacommandline: str | None) -> Any:
426
431
  try:
427
432
  return fn()
428
433
  except UserCancelledError as exc:
434
+ # No result describes a run cancelled outside pg-upsert's own run();
435
+ # say what is known rather than leave an earlier run's values.
436
+ sv = _state.subvars.add_substitution
437
+ sv("$PG_UPSERT_STATUS", "cancelled")
438
+ sv("$PG_UPSERT_ERROR", "")
439
+ sv("$PG_UPSERT_COMMITTED", "FALSE")
440
+ sv("$PG_UPSERT_ROWS_UPDATED", "0")
441
+ sv("$PG_UPSERT_ROWS_INSERTED", "0")
429
442
  raise ErrInfo(
430
443
  "cmd",
431
444
  command_text=metacommandline,
@@ -108,6 +108,7 @@ class MetaCommand:
108
108
  """Match *cmd_str* against this entry's regex and, if it matches, invoke the handler.
109
109
 
110
110
  Returns ``(True, return_value)`` on a match, ``(False, None)`` otherwise.
111
+ Raises :class:`ErrInfo` when the handler fails.
111
112
  """
112
113
  # Runs the metacommand if the command string matches the regex.
113
114
  m = self.rx.match(cmd_str.strip())
@@ -125,13 +126,10 @@ class MetaCommand:
125
126
  er = ErrInfo("cmd", command_text=cmd_str, exception_msg=exception_desc())
126
127
  er.__cause__ = exc
127
128
  if er:
128
- if _state.status.halt_on_metacommand_err:
129
- from execsql.utils.errors import exit_now
130
-
131
- exit_now(1, er)
132
- if self.set_error_flag:
133
- _state.status.metacommand_error = True
134
- return True, None
129
+ # The caller records the error ($LAST_ERROR, $ERROR_MESSAGE,
130
+ # the log, the error history) and halts or carries on.
131
+ if self.set_error_flag or _state.status.halt_on_metacommand_err:
132
+ raise er
135
133
  else:
136
134
  if self.set_error_flag:
137
135
  _state.status.metacommand_error = False
@@ -350,7 +350,9 @@ def _exec_metacommand(
350
350
  if ctx.exec_log is not None:
351
351
  ctx.exec_log.log_status_info(f"Metacommand error: {logged_error(e)}")
352
352
  if status.halt_on_metacommand_err:
353
- raise e
353
+ exit_now(1, e)
354
+ if (manifest := _manifest.current()) is not None:
355
+ manifest.record_metacommand(cmd, source, line_no, metacommandlist)
354
356
  status.error_history.append((source, line_no, cmd, e.errmsg()))
355
357
  return None
356
358
  # No handler matched — truly unknown metacommand
@@ -567,6 +567,55 @@ def _upsert_outcomes(tmp_path, metacommands: str) -> dict[str, str]:
567
567
 
568
568
 
569
569
  class TestPgUpsert:
570
+ @pytest.mark.parametrize(
571
+ ("staging_rows", "commit", "expected"),
572
+ [
573
+ ("(1, 'new'), (2, 'two')", "COMMIT", "committed"),
574
+ ("(1, 'new'), (2, 'two')", "", "dry_run"),
575
+ ("", "COMMIT", "no_changes"),
576
+ ("(2, NULL)", "COMMIT", "qa_failed"),
577
+ ],
578
+ ids=["committed", "dry_run", "no_changes", "qa_failed"],
579
+ )
580
+ def test_status_says_how_the_run_ended(self, tmp_path, upsert_schemas, staging_rows, commit, expected):
581
+ if staging_rows:
582
+ _exec_pg(f"INSERT INTO ups_stg.books VALUES {staging_rows}")
583
+ outcomes = _upsert_outcomes(
584
+ tmp_path,
585
+ f"""\
586
+ -- !x! PG_UPSERT FROM ups_stg TO ups_base TABLES books {commit}
587
+ -- !x! WRITE "status=!!$PG_UPSERT_STATUS!!" TO OUT
588
+ -- !x! WRITE "error=!!$PG_UPSERT_ERROR!!" TO OUT
589
+ """,
590
+ )
591
+ assert outcomes == {"status": expected, "error": ""}
592
+
593
+ def test_a_database_error_is_status_db_error_with_its_message(self, tmp_path, upsert_schemas):
594
+ _exec_pg("INSERT INTO ups_stg.books VALUES (2, 'two')")
595
+ _exec_pg(
596
+ "CREATE FUNCTION ups_base.refuse() RETURNS trigger LANGUAGE plpgsql AS "
597
+ "$$ BEGIN RAISE EXCEPTION 'books are read-only'; END $$",
598
+ )
599
+ _exec_pg(
600
+ "CREATE TRIGGER refuse BEFORE INSERT ON ups_base.books FOR EACH ROW EXECUTE FUNCTION ups_base.refuse()",
601
+ )
602
+ _write_conf(tmp_path)
603
+ out, err = tmp_path / "out.txt", tmp_path / "error.txt"
604
+ script = write_script(
605
+ tmp_path,
606
+ f"""\
607
+ -- !x! METACOMMAND_ERROR_HALT OFF
608
+ -- !x! PG_UPSERT FROM ups_stg TO ups_base TABLES books COMMIT
609
+ -- !x! WRITE "status=!!$PG_UPSERT_STATUS!! committed=!!$PG_UPSERT_COMMITTED!!" TO {out}
610
+ -- !x! WRITE `!!$PG_UPSERT_ERROR!!` TO {err}
611
+ """,
612
+ )
613
+ result = _run_execsql_pg(tmp_path, script, timeout=60)
614
+ assert result.returncode == 0, f"stderr: {result.stderr}"
615
+ assert out.read_text().strip() == "status=db_error committed=FALSE"
616
+ assert "books are read-only" in err.read_text()
617
+ assert _query_pg("SELECT id FROM ups_base.books ORDER BY id") == [(1,)]
618
+
570
619
  def test_qa_follows_method(self, tmp_path, upsert_schemas):
571
620
  """A NULL title on a new row fails QA for upsert, but update never writes that row."""
572
621
  _exec_pg("INSERT INTO ups_stg.books VALUES (1, 'new'), (2, NULL)")
@@ -893,6 +893,40 @@ class TestUserCancelled:
893
893
  metacommandline="PG_UPSERT FROM staging TO public TABLES books INTERACTIVE",
894
894
  )
895
895
  assert "cancelled" in str(exc_info.value)
896
+ # No earlier run's values are left behind: nothing was committed.
897
+ calls = {c[0][0]: c[0][1] for c in state.subvars.add_substitution.call_args_list}
898
+ assert calls["$PG_UPSERT_STATUS"] == "cancelled"
899
+ assert calls["$PG_UPSERT_ERROR"] == ""
900
+ assert calls["$PG_UPSERT_COMMITTED"] == "FALSE"
901
+ assert (calls["$PG_UPSERT_ROWS_UPDATED"], calls["$PG_UPSERT_ROWS_INSERTED"]) == ("0", "0")
902
+
903
+
904
+ class TestRunStatus:
905
+ """$PG_UPSERT_STATUS and $PG_UPSERT_ERROR come from the result's status and error."""
906
+
907
+ def test_status_and_error_from_the_result(self, mock_state):
908
+ import enum
909
+
910
+ from execsql.metacommands.upsert import _set_subvars
911
+
912
+ class RunStatus(enum.Enum):
913
+ DB_ERROR = "db_error"
914
+
915
+ state, _ = mock_state
916
+ result = FakeUpsertResult()
917
+ result.status = RunStatus.DB_ERROR
918
+ result.error = "trigger said no"
919
+ _set_subvars(result)
920
+ calls = {c[0][0]: c[0][1] for c in state.subvars.add_substitution.call_args_list}
921
+ assert (calls["$PG_UPSERT_STATUS"], calls["$PG_UPSERT_ERROR"]) == ("db_error", "trigger said no")
922
+
923
+ def test_no_status_is_empty(self, mock_state):
924
+ from execsql.metacommands.upsert import _set_subvars
925
+
926
+ state, _ = mock_state
927
+ _set_subvars(FakeUpsertResult()) # QA and CHECK build a result without one
928
+ calls = {c[0][0]: c[0][1] for c in state.subvars.add_substitution.call_args_list}
929
+ assert (calls["$PG_UPSERT_STATUS"], calls["$PG_UPSERT_ERROR"]) == ("", "")
896
930
 
897
931
 
898
932
  # ---------------------------------------------------------------------------
@@ -100,17 +100,16 @@ class TestMetaCommandRun:
100
100
  assert mc.hitcount == 0
101
101
 
102
102
  def test_handler_raises_errinf_no_halt(self, engine_state):
103
- """ErrInfo from handler: sets metacommand_error, returns (True, None)."""
103
+ """ErrInfo from the handler goes to the caller, which records it ($LAST_ERROR, history)."""
104
104
  _state.status.halt_on_metacommand_err = False
105
105
 
106
106
  def bad_handler(**kw):
107
107
  raise ErrInfo("error", other_msg="oops")
108
108
 
109
109
  mc = MetaCommand(re.compile(r"^\s*FAIL\s*$", re.I), bad_handler, set_error_flag=True)
110
- matched, rv = mc.run("FAIL")
111
- assert matched is True
112
- assert rv is None
113
- assert _state.status.metacommand_error is True
110
+ with pytest.raises(ErrInfo, match="oops"):
111
+ mc.run("FAIL")
112
+ assert mc.hitcount == 0
114
113
 
115
114
  def test_handler_raises_general_exception_no_halt(self, engine_state):
116
115
  """Generic exception from handler is converted to ErrInfo."""
@@ -120,10 +119,19 @@ class TestMetaCommandRun:
120
119
  raise ValueError("boom")
121
120
 
122
121
  mc = MetaCommand(re.compile(r"^\s*BOOM\s*$", re.I), bad_handler, set_error_flag=True)
123
- matched, rv = mc.run("BOOM")
124
- assert matched is True
125
- assert rv is None
126
- assert _state.status.metacommand_error is True
122
+ with pytest.raises(ErrInfo) as excinfo:
123
+ mc.run("BOOM")
124
+ assert isinstance(excinfo.value.__cause__, ValueError)
125
+
126
+ def test_set_error_flag_false_error_without_halt_is_no_match(self, engine_state):
127
+ """As upstream: a failing METACOMMAND_ERROR_HALT-style command reports no match."""
128
+ _state.status.halt_on_metacommand_err = False
129
+
130
+ def bad_handler(**kw):
131
+ raise ErrInfo("error", other_msg="nope")
132
+
133
+ mc = MetaCommand(re.compile(r"^\s*NOFLAG\s*$", re.I), bad_handler, set_error_flag=False)
134
+ assert mc.run("NOFLAG") == (False, None)
127
135
 
128
136
  def test_successful_run_clears_metacommand_error(self, engine_state):
129
137
  _state.status.metacommand_error = True
@@ -154,20 +162,17 @@ class TestMetaCommandRun:
154
162
  with pytest.raises(SystemExit):
155
163
  mc.run("EXIT")
156
164
 
157
- def test_halt_on_metacommand_err_calls_exit_now(self, engine_state):
158
- """When halt_on_metacommand_err is True and handler raises, exit_now is called."""
165
+ def test_halting_is_left_to_the_caller(self, engine_state):
166
+ """With halt_on_metacommand_err, the error is raised; the executor records it and halts."""
159
167
  _state.status.halt_on_metacommand_err = True
160
168
 
161
169
  def bad_handler(**kw):
162
170
  raise ErrInfo("error", other_msg="halt me")
163
171
 
164
172
  mc = MetaCommand(re.compile(r"^\s*HALTNOW\s*$", re.I), bad_handler)
165
- # exit_now is imported lazily inside MetaCommand.run(), so patch at its source module
166
- with patch("execsql.utils.errors.exit_now") as mock_exit:
167
- mock_exit.side_effect = SystemExit(1)
168
- with pytest.raises(SystemExit):
169
- mc.run("HALTNOW")
170
- mock_exit.assert_called_once()
173
+ with patch("execsql.utils.errors.exit_now") as mock_exit, pytest.raises(ErrInfo, match="halt me"):
174
+ mc.run("HALTNOW")
175
+ mock_exit.assert_not_called()
171
176
 
172
177
  def test_repr_contains_pattern(self):
173
178
  mc = MetaCommand(re.compile(r"^\s*ABC\s*$", re.I), lambda **kw: None, "test cmd")
@@ -0,0 +1,98 @@
1
+ """What a failed metacommand leaves for the script: $LAST_ERROR, $ERROR_MESSAGE, METACOMMAND_ERROR().
2
+
3
+ With METACOMMAND_ERROR_HALT OFF the script carries on and can read both
4
+ variables; with it ON, an ON ERROR_HALT handler can. The flag clears on the
5
+ next metacommand that succeeds; the variables keep their values until the next
6
+ error.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import subprocess
12
+ import sys
13
+
14
+ from execsql import run
15
+
16
+ _FAILING = '-- !x! IMPORT TO NEW nofile FROM "does_not_exist.csv"\n'
17
+ _FAILED_COMMAND = 'IMPORT TO NEW nofile FROM "does_not_exist.csv"'
18
+
19
+
20
+ def _run(tmp_path, monkeypatch, sql: str):
21
+ monkeypatch.chdir(tmp_path)
22
+ result = run(sql=sql, dsn=f"sqlite:///{tmp_path / 't.db'}", new_db=True)
23
+ out = tmp_path / "out.txt"
24
+ return result, out.read_text().splitlines() if out.exists() else []
25
+
26
+
27
+ def test_without_halting_the_script_can_read_the_error(tmp_path, monkeypatch):
28
+ result, lines = _run(
29
+ tmp_path,
30
+ monkeypatch,
31
+ "-- !x! METACOMMAND_ERROR_HALT OFF\n" + _FAILING + "-- !x! IF (METACOMMAND_ERROR())\n"
32
+ "-- !x! WRITE `last=[!!$LAST_ERROR!!]` TO out.txt\n"
33
+ "-- !x! WRITE `message=[!!$ERROR_MESSAGE!!]` TO out.txt\n"
34
+ "-- !x! ENDIF\n",
35
+ )
36
+ assert lines[0] == f"last=[{_FAILED_COMMAND}]"
37
+ assert "does_not_exist.csv does not exist" in "\n".join(lines[1:])
38
+ # The failure is one of the run's errors, as a non-halting SQL error is.
39
+ assert not result.success
40
+ assert [e.sql for e in result.errors] == [_FAILED_COMMAND]
41
+
42
+
43
+ def test_the_flag_clears_on_success_and_the_variables_keep_their_values(tmp_path, monkeypatch):
44
+ _, lines = _run(
45
+ tmp_path,
46
+ monkeypatch,
47
+ "-- !x! METACOMMAND_ERROR_HALT OFF\n" + _FAILING + "-- !x! WRITE `next` TO out.txt\n"
48
+ "-- !x! IF (NOT METACOMMAND_ERROR())\n"
49
+ "-- !x! WRITE `cleared; last=[!!$LAST_ERROR!!]` TO out.txt\n"
50
+ "-- !x! ENDIF\n",
51
+ )
52
+ assert lines == ["next", f"cleared; last=[{_FAILED_COMMAND}]"]
53
+
54
+
55
+ def test_an_on_error_halt_handler_can_read_the_error(tmp_path, monkeypatch):
56
+ result, lines = _run(
57
+ tmp_path,
58
+ monkeypatch,
59
+ "-- !x! BEGIN SCRIPT on_err\n"
60
+ "-- !x! WRITE `handler last=[!!$LAST_ERROR!!]` TO out.txt\n"
61
+ "-- !x! END SCRIPT\n"
62
+ "-- !x! ON ERROR_HALT EXECUTE SCRIPT on_err\n" + _FAILING + "-- !x! WRITE `not reached` TO out.txt\n",
63
+ )
64
+ assert lines == [f"handler last=[{_FAILED_COMMAND}]"]
65
+ assert not result.success
66
+
67
+
68
+ def test_the_documented_on_error_halt_write_example(tmp_path, monkeypatch):
69
+ _, lines = _run(
70
+ tmp_path,
71
+ monkeypatch,
72
+ '-- !x! ON ERROR_HALT WRITE "Error in line: !{$LAST_ERROR}!" TO out.txt\n' + _FAILING,
73
+ )
74
+ assert lines == [f"Error in line: {_FAILED_COMMAND}"]
75
+
76
+
77
+ def _cli(tmp_path, script: str) -> subprocess.CompletedProcess:
78
+ (tmp_path / "s.sql").write_text(script)
79
+ return subprocess.run(
80
+ [sys.executable, "-m", "execsql", "run", "-tl", "-n", "s.sql", "t.db"],
81
+ cwd=tmp_path,
82
+ capture_output=True,
83
+ text=True,
84
+ timeout=120,
85
+ check=False,
86
+ )
87
+
88
+
89
+ def test_the_cli_logs_a_non_halting_error_and_exits_zero(tmp_path):
90
+ proc = _cli(tmp_path, "-- !x! METACOMMAND_ERROR_HALT OFF\n" + _FAILING)
91
+ assert proc.returncode == 0, proc.stderr
92
+ assert "Metacommand error:" in (tmp_path / "execsql.log").read_text()
93
+
94
+
95
+ def test_the_cli_still_halts_with_status_1(tmp_path):
96
+ proc = _cli(tmp_path, _FAILING + "-- !x! WRITE `not reached`\n")
97
+ assert proc.returncode == 1
98
+ assert "not reached" not in proc.stdout
@@ -807,15 +807,15 @@ class TestMetaCommand:
807
807
  assert captured["varname"] == "myvar"
808
808
  assert captured["value"] == "hello"
809
809
 
810
- def test_run_sets_error_flag_on_exception(self):
810
+ def test_run_raises_the_handler_error(self):
811
811
  rx = re.compile(r".*", re.I)
812
812
 
813
813
  def bad_handler(**kw):
814
814
  raise ValueError("oops")
815
815
 
816
816
  mc = MetaCommand(rx, bad_handler, set_error_flag=True)
817
- mc.run("anything")
818
- assert _state.status.metacommand_error is True
817
+ with pytest.raises(ErrInfo):
818
+ mc.run("anything")
819
819
 
820
820
  def test_run_clears_error_flag_on_success(self):
821
821
  _state.status.metacommand_error = True
@@ -912,7 +912,7 @@ wheels = [
912
912
 
913
913
  [[package]]
914
914
  name = "execsql2"
915
- version = "2.32.0"
915
+ version = "2.33.0"
916
916
  source = { editable = "." }
917
917
  dependencies = [
918
918
  { name = "python-dateutil" },
File without changes
File without changes
File without changes