execsql2 2.12.5__tar.gz → 2.12.6__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 (300) hide show
  1. {execsql2-2.12.5 → execsql2-2.12.6}/CHANGELOG.md +13 -0
  2. {execsql2-2.12.5 → execsql2-2.12.6}/PKG-INFO +3 -3
  3. {execsql2-2.12.5 → execsql2-2.12.6}/docs/reference/metacommands.md +5 -0
  4. {execsql2-2.12.5 → execsql2-2.12.6}/pyproject.toml +3 -3
  5. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/metacommands/upsert.py +28 -3
  6. {execsql2-2.12.5 → execsql2-2.12.6}/tests/metacommands/test_pg_upsert.py +116 -1
  7. {execsql2-2.12.5 → execsql2-2.12.6}/uv.lock +5 -5
  8. {execsql2-2.12.5 → execsql2-2.12.6}/.claude/agents/dba.md +0 -0
  9. {execsql2-2.12.5 → execsql2-2.12.6}/.claude/agents/herald.md +0 -0
  10. {execsql2-2.12.5 → execsql2-2.12.6}/.claude/agents/inspector.md +0 -0
  11. {execsql2-2.12.5 → execsql2-2.12.6}/.claude/agents/liaison.md +0 -0
  12. {execsql2-2.12.5 → execsql2-2.12.6}/.claude/agents/oracle.md +0 -0
  13. {execsql2-2.12.5 → execsql2-2.12.6}/.claude/agents/patcher.md +0 -0
  14. {execsql2-2.12.5 → execsql2-2.12.6}/.claude/agents/qa.md +0 -0
  15. {execsql2-2.12.5 → execsql2-2.12.6}/.claude/agents/scribe.md +0 -0
  16. {execsql2-2.12.5 → execsql2-2.12.6}/.claude/commands/code-oracle.md +0 -0
  17. {execsql2-2.12.5 → execsql2-2.12.6}/.claude/commands/migrate.md +0 -0
  18. {execsql2-2.12.5 → execsql2-2.12.6}/.claude/commands/review-changes.md +0 -0
  19. {execsql2-2.12.5 → execsql2-2.12.6}/.claude/commands/test-module.md +0 -0
  20. {execsql2-2.12.5 → execsql2-2.12.6}/.claude/commands/update-changelog.md +0 -0
  21. {execsql2-2.12.5 → execsql2-2.12.6}/.claude/commands/where-is.md +0 -0
  22. {execsql2-2.12.5 → execsql2-2.12.6}/.claude/project_context.md +0 -0
  23. {execsql2-2.12.5 → execsql2-2.12.6}/.claude/state/status.md +0 -0
  24. {execsql2-2.12.5 → execsql2-2.12.6}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  25. {execsql2-2.12.5 → execsql2-2.12.6}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  26. {execsql2-2.12.5 → execsql2-2.12.6}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  27. {execsql2-2.12.5 → execsql2-2.12.6}/.github/workflows/ci-cd.yml +0 -0
  28. {execsql2-2.12.5 → execsql2-2.12.6}/.gitignore +0 -0
  29. {execsql2-2.12.5 → execsql2-2.12.6}/.pre-commit-config.yaml +0 -0
  30. {execsql2-2.12.5 → execsql2-2.12.6}/.pre-commit-hooks.yaml +0 -0
  31. {execsql2-2.12.5 → execsql2-2.12.6}/.python-version +0 -0
  32. {execsql2-2.12.5 → execsql2-2.12.6}/.readthedocs.yaml +0 -0
  33. {execsql2-2.12.5 → execsql2-2.12.6}/CLAUDE.md +0 -0
  34. {execsql2-2.12.5 → execsql2-2.12.6}/CONTRIBUTING.md +0 -0
  35. {execsql2-2.12.5 → execsql2-2.12.6}/LICENSE.txt +0 -0
  36. {execsql2-2.12.5 → execsql2-2.12.6}/NOTICE +0 -0
  37. {execsql2-2.12.5 → execsql2-2.12.6}/README.md +0 -0
  38. {execsql2-2.12.5 → execsql2-2.12.6}/SECURITY.md +0 -0
  39. {execsql2-2.12.5 → execsql2-2.12.6}/docs/about/contributors.md +0 -0
  40. {execsql2-2.12.5 → execsql2-2.12.6}/docs/about/copyright.md +0 -0
  41. {execsql2-2.12.5 → execsql2-2.12.6}/docs/about/divergence.md +0 -0
  42. {execsql2-2.12.5 → execsql2-2.12.6}/docs/api/cli.md +0 -0
  43. {execsql2-2.12.5 → execsql2-2.12.6}/docs/api/db.md +0 -0
  44. {execsql2-2.12.5 → execsql2-2.12.6}/docs/api/exporters.md +0 -0
  45. {execsql2-2.12.5 → execsql2-2.12.6}/docs/api/importers.md +0 -0
  46. {execsql2-2.12.5 → execsql2-2.12.6}/docs/api/index.md +0 -0
  47. {execsql2-2.12.5 → execsql2-2.12.6}/docs/api/metacommands.md +0 -0
  48. {execsql2-2.12.5 → execsql2-2.12.6}/docs/dev/adding_db_adapters.md +0 -0
  49. {execsql2-2.12.5 → execsql2-2.12.6}/docs/dev/adding_exporters.md +0 -0
  50. {execsql2-2.12.5 → execsql2-2.12.6}/docs/dev/adding_importers.md +0 -0
  51. {execsql2-2.12.5 → execsql2-2.12.6}/docs/dev/adding_metacommands.md +0 -0
  52. {execsql2-2.12.5 → execsql2-2.12.6}/docs/dev/architecture.md +0 -0
  53. {execsql2-2.12.5 → execsql2-2.12.6}/docs/getting-started/installation.md +0 -0
  54. {execsql2-2.12.5 → execsql2-2.12.6}/docs/getting-started/requirements.md +0 -0
  55. {execsql2-2.12.5 → execsql2-2.12.6}/docs/getting-started/syntax.md +0 -0
  56. {execsql2-2.12.5 → execsql2-2.12.6}/docs/guides/debugging.md +0 -0
  57. {execsql2-2.12.5 → execsql2-2.12.6}/docs/guides/documentation.md +0 -0
  58. {execsql2-2.12.5 → execsql2-2.12.6}/docs/guides/encoding.md +0 -0
  59. {execsql2-2.12.5 → execsql2-2.12.6}/docs/guides/examples.md +0 -0
  60. {execsql2-2.12.5 → execsql2-2.12.6}/docs/guides/formatter.md +0 -0
  61. {execsql2-2.12.5 → execsql2-2.12.6}/docs/guides/logging.md +0 -0
  62. {execsql2-2.12.5 → execsql2-2.12.6}/docs/guides/sql_syntax.md +0 -0
  63. {execsql2-2.12.5 → execsql2-2.12.6}/docs/guides/usage.md +0 -0
  64. {execsql2-2.12.5 → execsql2-2.12.6}/docs/guides/using_scripts.md +0 -0
  65. {execsql2-2.12.5 → execsql2-2.12.6}/docs/images/Compare_planets.png +0 -0
  66. {execsql2-2.12.5 → execsql2-2.12.6}/docs/images/actions.png +0 -0
  67. {execsql2-2.12.5 → execsql2-2.12.6}/docs/images/actions2.png +0 -0
  68. {execsql2-2.12.5 → execsql2-2.12.6}/docs/images/checkboxes.png +0 -0
  69. {execsql2-2.12.5 → execsql2-2.12.6}/docs/images/connect.b64 +0 -0
  70. {execsql2-2.12.5 → execsql2-2.12.6}/docs/images/connect.png +0 -0
  71. {execsql2-2.12.5 → execsql2-2.12.6}/docs/images/create_conf.png +0 -0
  72. {execsql2-2.12.5 → execsql2-2.12.6}/docs/images/data_error1_screenshot.jpg +0 -0
  73. {execsql2-2.12.5 → execsql2-2.12.6}/docs/images/entry_form.png +0 -0
  74. {execsql2-2.12.5 → execsql2-2.12.6}/docs/images/execsql_console.png +0 -0
  75. {execsql2-2.12.5 → execsql2-2.12.6}/docs/images/execsql_logo_01.png +0 -0
  76. {execsql2-2.12.5 → execsql2-2.12.6}/docs/images/fatals.png +0 -0
  77. {execsql2-2.12.5 → execsql2-2.12.6}/docs/images/logo_small.png +0 -0
  78. {execsql2-2.12.5 → execsql2-2.12.6}/docs/images/pause_terminal.png +0 -0
  79. {execsql2-2.12.5 → execsql2-2.12.6}/docs/images/pause_terminal_sm.b64 +0 -0
  80. {execsql2-2.12.5 → execsql2-2.12.6}/docs/images/pause_terminal_sm.png +0 -0
  81. {execsql2-2.12.5 → execsql2-2.12.6}/docs/images/prompt_compare.png +0 -0
  82. {execsql2-2.12.5 → execsql2-2.12.6}/docs/images/set_build_commands.jpg +0 -0
  83. {execsql2-2.12.5 → execsql2-2.12.6}/docs/images/unit_conversions.b64 +0 -0
  84. {execsql2-2.12.5 → execsql2-2.12.6}/docs/images/unit_conversions_029.png +0 -0
  85. {execsql2-2.12.5 → execsql2-2.12.6}/docs/images/unmatched.png +0 -0
  86. {execsql2-2.12.5 → execsql2-2.12.6}/docs/images/vim_execsql_highlight.png +0 -0
  87. {execsql2-2.12.5 → execsql2-2.12.6}/docs/index.md +0 -0
  88. {execsql2-2.12.5 → execsql2-2.12.6}/docs/reference/configuration.md +0 -0
  89. {execsql2-2.12.5 → execsql2-2.12.6}/docs/reference/security.md +0 -0
  90. {execsql2-2.12.5 → execsql2-2.12.6}/docs/reference/substitution_vars.md +0 -0
  91. {execsql2-2.12.5 → execsql2-2.12.6}/extras/vscode-execsql/README.md +0 -0
  92. {execsql2-2.12.5 → execsql2-2.12.6}/extras/vscode-execsql/package.json +0 -0
  93. {execsql2-2.12.5 → execsql2-2.12.6}/extras/vscode-execsql/syntaxes/execsql.tmLanguage.json +0 -0
  94. {execsql2-2.12.5 → execsql2-2.12.6}/justfile +0 -0
  95. {execsql2-2.12.5 → execsql2-2.12.6}/scripts/generate_vscode_grammar.py +0 -0
  96. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/__init__.py +0 -0
  97. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/__main__.py +0 -0
  98. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/cli/__init__.py +0 -0
  99. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/cli/dsn.py +0 -0
  100. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/cli/help.py +0 -0
  101. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/cli/lint.py +0 -0
  102. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/cli/run.py +0 -0
  103. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/config.py +0 -0
  104. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/constants.py +0 -0
  105. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/db/__init__.py +0 -0
  106. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/db/access.py +0 -0
  107. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/db/base.py +0 -0
  108. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/db/dsn.py +0 -0
  109. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/db/duckdb.py +0 -0
  110. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/db/factory.py +0 -0
  111. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/db/firebird.py +0 -0
  112. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/db/mysql.py +0 -0
  113. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/db/oracle.py +0 -0
  114. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/db/postgres.py +0 -0
  115. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/db/sqlite.py +0 -0
  116. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/db/sqlserver.py +0 -0
  117. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/debug/__init__.py +0 -0
  118. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/debug/repl.py +0 -0
  119. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/exceptions.py +0 -0
  120. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/exporters/__init__.py +0 -0
  121. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/exporters/base.py +0 -0
  122. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/exporters/delimited.py +0 -0
  123. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/exporters/duckdb.py +0 -0
  124. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/exporters/feather.py +0 -0
  125. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/exporters/html.py +0 -0
  126. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/exporters/json.py +0 -0
  127. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/exporters/latex.py +0 -0
  128. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/exporters/markdown.py +0 -0
  129. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/exporters/ods.py +0 -0
  130. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/exporters/parquet.py +0 -0
  131. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/exporters/pretty.py +0 -0
  132. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/exporters/protocol.py +0 -0
  133. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/exporters/raw.py +0 -0
  134. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/exporters/sqlite.py +0 -0
  135. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/exporters/templates.py +0 -0
  136. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/exporters/values.py +0 -0
  137. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/exporters/xls.py +0 -0
  138. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/exporters/xlsx.py +0 -0
  139. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/exporters/xml.py +0 -0
  140. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/exporters/yaml.py +0 -0
  141. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/exporters/zip.py +0 -0
  142. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/format.py +0 -0
  143. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/gui/__init__.py +0 -0
  144. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/gui/base.py +0 -0
  145. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/gui/console.py +0 -0
  146. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/gui/desktop.py +0 -0
  147. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/gui/tui.py +0 -0
  148. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/importers/__init__.py +0 -0
  149. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/importers/base.py +0 -0
  150. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/importers/csv.py +0 -0
  151. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/importers/feather.py +0 -0
  152. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/importers/ods.py +0 -0
  153. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/importers/xls.py +0 -0
  154. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/metacommands/__init__.py +0 -0
  155. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/metacommands/conditions.py +0 -0
  156. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/metacommands/connect.py +0 -0
  157. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/metacommands/control.py +0 -0
  158. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/metacommands/data.py +0 -0
  159. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/metacommands/debug.py +0 -0
  160. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/metacommands/dispatch.py +0 -0
  161. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/metacommands/io.py +0 -0
  162. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/metacommands/io_export.py +0 -0
  163. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/metacommands/io_fileops.py +0 -0
  164. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/metacommands/io_import.py +0 -0
  165. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/metacommands/io_write.py +0 -0
  166. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/metacommands/prompt.py +0 -0
  167. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/metacommands/script_ext.py +0 -0
  168. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/metacommands/system.py +0 -0
  169. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/models.py +0 -0
  170. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/parser.py +0 -0
  171. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/py.typed +0 -0
  172. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/script/__init__.py +0 -0
  173. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/script/control.py +0 -0
  174. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/script/engine.py +0 -0
  175. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/script/variables.py +0 -0
  176. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/state.py +0 -0
  177. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/types.py +0 -0
  178. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/utils/__init__.py +0 -0
  179. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/utils/auth.py +0 -0
  180. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/utils/crypto.py +0 -0
  181. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/utils/datetime.py +0 -0
  182. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/utils/errors.py +0 -0
  183. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/utils/fileio.py +0 -0
  184. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/utils/gui.py +0 -0
  185. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/utils/mail.py +0 -0
  186. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/utils/numeric.py +0 -0
  187. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/utils/regex.py +0 -0
  188. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/utils/strings.py +0 -0
  189. {execsql2-2.12.5 → execsql2-2.12.6}/src/execsql/utils/timer.py +0 -0
  190. {execsql2-2.12.5 → execsql2-2.12.6}/templates/README.md +0 -0
  191. {execsql2-2.12.5 → execsql2-2.12.6}/templates/config_settings.sqlite +0 -0
  192. {execsql2-2.12.5 → execsql2-2.12.6}/templates/example_config_prompt.sql +0 -0
  193. {execsql2-2.12.5 → execsql2-2.12.6}/templates/execsql.conf +0 -0
  194. {execsql2-2.12.5 → execsql2-2.12.6}/templates/make_config_db.sql +0 -0
  195. {execsql2-2.12.5 → execsql2-2.12.6}/templates/md_compare.sql +0 -0
  196. {execsql2-2.12.5 → execsql2-2.12.6}/templates/md_glossary.sql +0 -0
  197. {execsql2-2.12.5 → execsql2-2.12.6}/templates/md_upsert.sql +0 -0
  198. {execsql2-2.12.5 → execsql2-2.12.6}/templates/pg_compare.sql +0 -0
  199. {execsql2-2.12.5 → execsql2-2.12.6}/templates/pg_glossary.sql +0 -0
  200. {execsql2-2.12.5 → execsql2-2.12.6}/templates/pg_upsert.sql +0 -0
  201. {execsql2-2.12.5 → execsql2-2.12.6}/templates/script_template.sql +0 -0
  202. {execsql2-2.12.5 → execsql2-2.12.6}/templates/ss_compare.sql +0 -0
  203. {execsql2-2.12.5 → execsql2-2.12.6}/templates/ss_glossary.sql +0 -0
  204. {execsql2-2.12.5 → execsql2-2.12.6}/templates/ss_upsert.sql +0 -0
  205. {execsql2-2.12.5 → execsql2-2.12.6}/tests/__init__.py +0 -0
  206. {execsql2-2.12.5 → execsql2-2.12.6}/tests/cli/__init__.py +0 -0
  207. {execsql2-2.12.5 → execsql2-2.12.6}/tests/cli/test_cli.py +0 -0
  208. {execsql2-2.12.5 → execsql2-2.12.6}/tests/cli/test_cli_e2e.py +0 -0
  209. {execsql2-2.12.5 → execsql2-2.12.6}/tests/cli/test_cli_run.py +0 -0
  210. {execsql2-2.12.5 → execsql2-2.12.6}/tests/cli/test_lint.py +0 -0
  211. {execsql2-2.12.5 → execsql2-2.12.6}/tests/cli/test_ping.py +0 -0
  212. {execsql2-2.12.5 → execsql2-2.12.6}/tests/cli/test_profile.py +0 -0
  213. {execsql2-2.12.5 → execsql2-2.12.6}/tests/conftest.py +0 -0
  214. {execsql2-2.12.5 → execsql2-2.12.6}/tests/db/__init__.py +0 -0
  215. {execsql2-2.12.5 → execsql2-2.12.6}/tests/db/test_base.py +0 -0
  216. {execsql2-2.12.5 → execsql2-2.12.6}/tests/db/test_duckdb.py +0 -0
  217. {execsql2-2.12.5 → execsql2-2.12.6}/tests/db/test_factory.py +0 -0
  218. {execsql2-2.12.5 → execsql2-2.12.6}/tests/db/test_postgres.py +0 -0
  219. {execsql2-2.12.5 → execsql2-2.12.6}/tests/db/test_sqlite.py +0 -0
  220. {execsql2-2.12.5 → execsql2-2.12.6}/tests/db/test_sqlite_extra.py +0 -0
  221. {execsql2-2.12.5 → execsql2-2.12.6}/tests/exporters/__init__.py +0 -0
  222. {execsql2-2.12.5 → execsql2-2.12.6}/tests/exporters/test_base.py +0 -0
  223. {execsql2-2.12.5 → execsql2-2.12.6}/tests/exporters/test_db.py +0 -0
  224. {execsql2-2.12.5 → execsql2-2.12.6}/tests/exporters/test_delimited.py +0 -0
  225. {execsql2-2.12.5 → execsql2-2.12.6}/tests/exporters/test_duckdb_exporter.py +0 -0
  226. {execsql2-2.12.5 → execsql2-2.12.6}/tests/exporters/test_exporters.py +0 -0
  227. {execsql2-2.12.5 → execsql2-2.12.6}/tests/exporters/test_feather.py +0 -0
  228. {execsql2-2.12.5 → execsql2-2.12.6}/tests/exporters/test_html_latex.py +0 -0
  229. {execsql2-2.12.5 → execsql2-2.12.6}/tests/exporters/test_json.py +0 -0
  230. {execsql2-2.12.5 → execsql2-2.12.6}/tests/exporters/test_markdown.py +0 -0
  231. {execsql2-2.12.5 → execsql2-2.12.6}/tests/exporters/test_ods.py +0 -0
  232. {execsql2-2.12.5 → execsql2-2.12.6}/tests/exporters/test_parquet.py +0 -0
  233. {execsql2-2.12.5 → execsql2-2.12.6}/tests/exporters/test_sqlite_exporter.py +0 -0
  234. {execsql2-2.12.5 → execsql2-2.12.6}/tests/exporters/test_templates.py +0 -0
  235. {execsql2-2.12.5 → execsql2-2.12.6}/tests/exporters/test_xls_xlsx.py +0 -0
  236. {execsql2-2.12.5 → execsql2-2.12.6}/tests/exporters/test_xlsx.py +0 -0
  237. {execsql2-2.12.5 → execsql2-2.12.6}/tests/exporters/test_xml.py +0 -0
  238. {execsql2-2.12.5 → execsql2-2.12.6}/tests/exporters/test_yaml.py +0 -0
  239. {execsql2-2.12.5 → execsql2-2.12.6}/tests/exporters/test_zip.py +0 -0
  240. {execsql2-2.12.5 → execsql2-2.12.6}/tests/gui/__init__.py +0 -0
  241. {execsql2-2.12.5 → execsql2-2.12.6}/tests/gui/test_backends.py +0 -0
  242. {execsql2-2.12.5 → execsql2-2.12.6}/tests/importers/__init__.py +0 -0
  243. {execsql2-2.12.5 → execsql2-2.12.6}/tests/importers/test_csv_importer.py +0 -0
  244. {execsql2-2.12.5 → execsql2-2.12.6}/tests/importers/test_feather_importer.py +0 -0
  245. {execsql2-2.12.5 → execsql2-2.12.6}/tests/importers/test_ods_importer.py +0 -0
  246. {execsql2-2.12.5 → execsql2-2.12.6}/tests/importers/test_xls_importer.py +0 -0
  247. {execsql2-2.12.5 → execsql2-2.12.6}/tests/integration/__init__.py +0 -0
  248. {execsql2-2.12.5 → execsql2-2.12.6}/tests/integration/conftest.py +0 -0
  249. {execsql2-2.12.5 → execsql2-2.12.6}/tests/integration/test_dsn.py +0 -0
  250. {execsql2-2.12.5 → execsql2-2.12.6}/tests/integration/test_duckdb.py +0 -0
  251. {execsql2-2.12.5 → execsql2-2.12.6}/tests/integration/test_mysql.py +0 -0
  252. {execsql2-2.12.5 → execsql2-2.12.6}/tests/integration/test_postgres.py +0 -0
  253. {execsql2-2.12.5 → execsql2-2.12.6}/tests/integration/test_sqlite.py +0 -0
  254. {execsql2-2.12.5 → execsql2-2.12.6}/tests/metacommands/__init__.py +0 -0
  255. {execsql2-2.12.5 → execsql2-2.12.6}/tests/metacommands/test_assert.py +0 -0
  256. {execsql2-2.12.5 → execsql2-2.12.6}/tests/metacommands/test_breakpoint.py +0 -0
  257. {execsql2-2.12.5 → execsql2-2.12.6}/tests/metacommands/test_connect.py +0 -0
  258. {execsql2-2.12.5 → execsql2-2.12.6}/tests/metacommands/test_io_export.py +0 -0
  259. {execsql2-2.12.5 → execsql2-2.12.6}/tests/metacommands/test_io_import.py +0 -0
  260. {execsql2-2.12.5 → execsql2-2.12.6}/tests/metacommands/test_metacommands.py +0 -0
  261. {execsql2-2.12.5 → execsql2-2.12.6}/tests/metacommands/test_metacommands_connect.py +0 -0
  262. {execsql2-2.12.5 → execsql2-2.12.6}/tests/metacommands/test_metacommands_data.py +0 -0
  263. {execsql2-2.12.5 → execsql2-2.12.6}/tests/metacommands/test_metacommands_extended.py +0 -0
  264. {execsql2-2.12.5 → execsql2-2.12.6}/tests/metacommands/test_metacommands_fileops_extra.py +0 -0
  265. {execsql2-2.12.5 → execsql2-2.12.6}/tests/metacommands/test_metacommands_io.py +0 -0
  266. {execsql2-2.12.5 → execsql2-2.12.6}/tests/metacommands/test_metacommands_io_write_extra.py +0 -0
  267. {execsql2-2.12.5 → execsql2-2.12.6}/tests/metacommands/test_metacommands_script_ext.py +0 -0
  268. {execsql2-2.12.5 → execsql2-2.12.6}/tests/metacommands/test_metacommands_system.py +0 -0
  269. {execsql2-2.12.5 → execsql2-2.12.6}/tests/metacommands/test_metacommands_system_extra.py +0 -0
  270. {execsql2-2.12.5 → execsql2-2.12.6}/tests/metacommands/test_row_count.py +0 -0
  271. {execsql2-2.12.5 → execsql2-2.12.6}/tests/test_config.py +0 -0
  272. {execsql2-2.12.5 → execsql2-2.12.6}/tests/test_config_data.py +0 -0
  273. {execsql2-2.12.5 → execsql2-2.12.6}/tests/test_constants.py +0 -0
  274. {execsql2-2.12.5 → execsql2-2.12.6}/tests/test_engine.py +0 -0
  275. {execsql2-2.12.5 → execsql2-2.12.6}/tests/test_error_messages.py +0 -0
  276. {execsql2-2.12.5 → execsql2-2.12.6}/tests/test_exceptions.py +0 -0
  277. {execsql2-2.12.5 → execsql2-2.12.6}/tests/test_format.py +0 -0
  278. {execsql2-2.12.5 → execsql2-2.12.6}/tests/test_mail.py +0 -0
  279. {execsql2-2.12.5 → execsql2-2.12.6}/tests/test_models.py +0 -0
  280. {execsql2-2.12.5 → execsql2-2.12.6}/tests/test_package.py +0 -0
  281. {execsql2-2.12.5 → execsql2-2.12.6}/tests/test_parser.py +0 -0
  282. {execsql2-2.12.5 → execsql2-2.12.6}/tests/test_registry.py +0 -0
  283. {execsql2-2.12.5 → execsql2-2.12.6}/tests/test_script.py +0 -0
  284. {execsql2-2.12.5 → execsql2-2.12.6}/tests/test_state.py +0 -0
  285. {execsql2-2.12.5 → execsql2-2.12.6}/tests/test_types.py +0 -0
  286. {execsql2-2.12.5 → execsql2-2.12.6}/tests/utils/__init__.py +0 -0
  287. {execsql2-2.12.5 → execsql2-2.12.6}/tests/utils/test_auth.py +0 -0
  288. {execsql2-2.12.5 → execsql2-2.12.6}/tests/utils/test_auth_extra.py +0 -0
  289. {execsql2-2.12.5 → execsql2-2.12.6}/tests/utils/test_crypto.py +0 -0
  290. {execsql2-2.12.5 → execsql2-2.12.6}/tests/utils/test_datetime.py +0 -0
  291. {execsql2-2.12.5 → execsql2-2.12.6}/tests/utils/test_errors.py +0 -0
  292. {execsql2-2.12.5 → execsql2-2.12.6}/tests/utils/test_errors_extra.py +0 -0
  293. {execsql2-2.12.5 → execsql2-2.12.6}/tests/utils/test_fileio.py +0 -0
  294. {execsql2-2.12.5 → execsql2-2.12.6}/tests/utils/test_fileio_extra.py +0 -0
  295. {execsql2-2.12.5 → execsql2-2.12.6}/tests/utils/test_numeric.py +0 -0
  296. {execsql2-2.12.5 → execsql2-2.12.6}/tests/utils/test_regex.py +0 -0
  297. {execsql2-2.12.5 → execsql2-2.12.6}/tests/utils/test_strings.py +0 -0
  298. {execsql2-2.12.5 → execsql2-2.12.6}/tests/utils/test_timer.py +0 -0
  299. {execsql2-2.12.5 → execsql2-2.12.6}/tests/utils/test_timer_extra.py +0 -0
  300. {execsql2-2.12.5 → execsql2-2.12.6}/zensical.toml +0 -0
@@ -13,8 +13,21 @@ ______________________________________________________________________
13
13
 
14
14
  ______________________________________________________________________
15
15
 
16
+ ## [2.12.6] - 2026-04-03
17
+
18
+ ### Added
19
+
20
+ - `PG_UPSERT` now supports per-table progress via pg-upsert's callback API. New substitution variables `$PG_UPSERT_CURRENT_TABLE`, `$PG_UPSERT_TABLE_QA_PASSED`, `$PG_UPSERT_TABLE_ROWS_UPDATED`, and `$PG_UPSERT_TABLE_ROWS_INSERTED` are updated as each table is processed.
21
+ - New `CLEANUP` keyword for `PG_UPSERT` — drops all `ups_*` temporary tables and views after execution. Without it, temp objects persist for inspection (default).
22
+
23
+ ______________________________________________________________________
24
+
16
25
  ## [2.12.5] - 2026-04-03
17
26
 
27
+ ### Fixed
28
+
29
+ - Fixed CLI test `test_nonexistent_file_error_message_is_clear` failing with `ValueError: stderr not separately captured` when CliRunner mixes stderr into stdout.
30
+
18
31
  ______________________________________________________________________
19
32
 
20
33
  ## [2.12.4] - 2026-04-03
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: execsql2
3
- Version: 2.12.5
3
+ Version: 2.12.6
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
@@ -51,7 +51,7 @@ Requires-Dist: keyring; extra == 'all'
51
51
  Requires-Dist: odfpy; extra == 'all'
52
52
  Requires-Dist: openpyxl; extra == 'all'
53
53
  Requires-Dist: oracledb; extra == 'all'
54
- Requires-Dist: pg-upsert>=1.17.0; extra == 'all'
54
+ Requires-Dist: pg-upsert>=1.18.0; extra == 'all'
55
55
  Requires-Dist: polars; extra == 'all'
56
56
  Requires-Dist: psycopg2-binary; extra == 'all'
57
57
  Requires-Dist: pymysql; extra == 'all'
@@ -109,7 +109,7 @@ Requires-Dist: oracledb; extra == 'oracle'
109
109
  Provides-Extra: postgres
110
110
  Requires-Dist: psycopg2-binary; extra == 'postgres'
111
111
  Provides-Extra: upsert
112
- Requires-Dist: pg-upsert>=1.17.0; extra == 'upsert'
112
+ Requires-Dist: pg-upsert>=1.18.0; extra == 'upsert'
113
113
  Description-Content-Type: text/markdown
114
114
 
115
115
  > [!NOTE]
@@ -2099,6 +2099,7 @@ Keywords can appear in any order after the table list.
2099
2099
  | `INTERACTIVE` | Enable pg-upsert's interactive UI dialogs (tkinter or textual) for reviewing QA failures. Without it, runs non-interactively. |
2100
2100
  | `COMPACT` | Use compact grid format for QA summary instead of detailed per-table panels. |
2101
2101
  | `LOGFILE <path>` | Append pg-upsert's plain-text log output to the given file. Supports quoted paths for spaces: `LOGFILE "path/to/log.txt"`. |
2102
+ | `CLEANUP` | Drop all `ups_*` temporary tables and views after execution. Without it, temp objects persist for inspection (default). |
2102
2103
 
2103
2104
  ### Substitution variables
2104
2105
 
@@ -2118,6 +2119,10 @@ Set after every `PG_UPSERT` execution:
2118
2119
  | `$PG_UPSERT_STARTED_AT` | string | ISO 8601 start timestamp |
2119
2120
  | `$PG_UPSERT_FINISHED_AT` | string | ISO 8601 end timestamp |
2120
2121
  | `$PG_UPSERT_RESULT_JSON` | JSON string | Full result for detailed inspection |
2122
+ | `$PG_UPSERT_CURRENT_TABLE` | string | Last table processed (updated per table during execution) |
2123
+ | `$PG_UPSERT_TABLE_QA_PASSED` | TRUE/FALSE | QA result for the current table (updated per table) |
2124
+ | `$PG_UPSERT_TABLE_ROWS_UPDATED` | integer | Rows updated for the current table (updated per table) |
2125
+ | `$PG_UPSERT_TABLE_ROWS_INSERTED` | integer | Rows inserted for the current table (updated per table) |
2121
2126
 
2122
2127
  !!! note "Using `$PG_UPSERT_RESULT_JSON` with WRITE"
2123
2128
  The JSON value is stored as compact single-line JSON. Because it contains double quotes (`"`), square brackets (`[]`), and apostrophes may appear in data, use tilde or backtick delimiters with WRITE:
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "execsql2"
7
- version = "2.12.5"
7
+ version = "2.12.6"
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" }
@@ -58,7 +58,7 @@ odbc = ["pyodbc"]
58
58
  # Feature bundles
59
59
  formats = ["odfpy", "xlrd", "openpyxl", "Jinja2", "polars", "tables", "PyYAML"]
60
60
  auth = ["keyring"]
61
- upsert = ["pg-upsert>=1.17.0"]
61
+ upsert = ["pg-upsert>=1.18.0"]
62
62
  # Convenience groups
63
63
  all-db = [
64
64
  "psycopg2-binary",
@@ -161,7 +161,7 @@ skip-magic-trailing-comma = false
161
161
  line-ending = "auto"
162
162
 
163
163
  [tool.bumpversion]
164
- current_version = "2.12.5"
164
+ current_version = "2.12.6"
165
165
  commit = true
166
166
  commit_args = "--no-verify"
167
167
  tag = true
@@ -26,21 +26,22 @@ from execsql.utils.errors import exception_desc
26
26
 
27
27
  _KW_METHOD = re.compile(r"\bMETHOD\s+(upsert|update|insert)\b", re.IGNORECASE)
28
28
  _KW_EXCLUDE = re.compile(
29
- r"\bEXCLUDE\s+([\w\s,]+?)(?=\s+(?:METHOD|COMMIT|INTERACTIVE|COMPACT|EXCLUDE_NULL|LOGFILE)\b|\s*$)",
29
+ r"\bEXCLUDE\s+([\w\s,]+?)(?=\s+(?:METHOD|COMMIT|INTERACTIVE|COMPACT|EXCLUDE_NULL|LOGFILE|CLEANUP)\b|\s*$)",
30
30
  re.IGNORECASE,
31
31
  )
32
32
  _KW_EXCLUDE_NULL = re.compile(
33
- r"\bEXCLUDE_NULL\s+([\w\s,]+?)(?=\s+(?:METHOD|COMMIT|INTERACTIVE|COMPACT|EXCLUDE|LOGFILE)\b|\s*$)",
33
+ r"\bEXCLUDE_NULL\s+([\w\s,]+?)(?=\s+(?:METHOD|COMMIT|INTERACTIVE|COMPACT|EXCLUDE|LOGFILE|CLEANUP)\b|\s*$)",
34
34
  re.IGNORECASE,
35
35
  )
36
36
  _KW_COMMIT = re.compile(r"\bCOMMIT\b", re.IGNORECASE)
37
37
  _KW_INTERACTIVE = re.compile(r"\bINTERACTIVE\b", re.IGNORECASE)
38
38
  _KW_COMPACT = re.compile(r"\bCOMPACT\b", re.IGNORECASE)
39
+ _KW_CLEANUP = re.compile(r"\bCLEANUP\b", re.IGNORECASE)
39
40
  _KW_LOGFILE = re.compile(r"""\bLOGFILE\s+(?:"([^"]+)"|'([^']+)'|(\S+))""", re.IGNORECASE)
40
41
 
41
42
  # All recognized keywords — used to split table names from options.
42
43
  _ALL_KEYWORDS = re.compile(
43
- r"\b(?:METHOD|COMMIT|INTERACTIVE|COMPACT|EXCLUDE_NULL|EXCLUDE|LOGFILE)\b",
44
+ r"\b(?:METHOD|COMMIT|INTERACTIVE|COMPACT|EXCLUDE_NULL|EXCLUDE|LOGFILE|CLEANUP)\b",
44
45
  re.IGNORECASE,
45
46
  )
46
47
 
@@ -98,6 +99,7 @@ def _parse_tables_and_options(tail: str) -> dict[str, Any]:
98
99
  "exclude_cols": exclude_cols,
99
100
  "exclude_null_check_cols": exclude_null,
100
101
  "logfile": logfile,
102
+ "cleanup": bool(_KW_CLEANUP.search(opts_part)),
101
103
  }
102
104
 
103
105
 
@@ -210,6 +212,22 @@ def _build_result_from_qa_errors(ups: Any) -> Any:
210
212
  )
211
213
 
212
214
 
215
+ def _make_callback() -> Any:
216
+ """Return a pg-upsert pipeline callback that sets per-table subvars."""
217
+ from pg_upsert import CallbackEvent
218
+
219
+ def _on_event(event: Any) -> None:
220
+ sv = _state.subvars.add_substitution
221
+ sv("$PG_UPSERT_CURRENT_TABLE", event.table)
222
+ if event.event == CallbackEvent.QA_TABLE_COMPLETE:
223
+ sv("$PG_UPSERT_TABLE_QA_PASSED", str(event.qa_passed).upper())
224
+ elif event.event == CallbackEvent.UPSERT_TABLE_COMPLETE:
225
+ sv("$PG_UPSERT_TABLE_ROWS_UPDATED", str(event.rows_updated))
226
+ sv("$PG_UPSERT_TABLE_ROWS_INSERTED", str(event.rows_inserted))
227
+
228
+ return _on_event
229
+
230
+
213
231
  def _create_pgupsert(
214
232
  db: Any,
215
233
  staging_schema: str,
@@ -235,6 +253,7 @@ def _create_pgupsert(
235
253
  exclude_cols=opts["exclude_cols"],
236
254
  exclude_null_check_cols=opts["exclude_null_check_cols"],
237
255
  ui_mode=ui_mode,
256
+ callback=_make_callback(),
238
257
  )
239
258
  return ups
240
259
 
@@ -362,6 +381,8 @@ def x_pg_upsert(**kwargs: Any) -> None:
362
381
  _detach_log_handlers(loggers, handlers, prev_levels)
363
382
 
364
383
  _set_subvars(result)
384
+ if opts.get("cleanup"):
385
+ ups.cleanup()
365
386
 
366
387
  if not result.qa_passed:
367
388
  raise ErrInfo(
@@ -399,6 +420,8 @@ def x_pg_upsert_qa(**kwargs: Any) -> None:
399
420
 
400
421
  result = _build_result_from_qa_errors(ups)
401
422
  _set_subvars(result)
423
+ if opts.get("cleanup"):
424
+ ups.cleanup()
402
425
 
403
426
  if not result.qa_passed:
404
427
  raise ErrInfo(
@@ -439,6 +462,8 @@ def x_pg_upsert_check(**kwargs: Any) -> None:
439
462
 
440
463
  result = _build_result_from_qa_errors(ups)
441
464
  _set_subvars(result)
465
+ if opts.get("cleanup"):
466
+ ups.cleanup()
442
467
 
443
468
  if not result.qa_passed:
444
469
  raise ErrInfo(
@@ -230,7 +230,7 @@ class TestParseTablesAndOptions:
230
230
 
231
231
  def test_all_keywords_combined(self):
232
232
  result = _parse_tables_and_options(
233
- 'books, authors METHOD update EXCLUDE rev_time EXCLUDE_NULL created_at INTERACTIVE COMPACT LOGFILE "upsert.log" COMMIT',
233
+ 'books, authors METHOD update EXCLUDE rev_time EXCLUDE_NULL created_at INTERACTIVE COMPACT LOGFILE "upsert.log" CLEANUP COMMIT',
234
234
  )
235
235
  assert result["tables"] == ["books", "authors"]
236
236
  assert result["method"] == "update"
@@ -240,6 +240,7 @@ class TestParseTablesAndOptions:
240
240
  assert result["compact"] is True
241
241
  assert result["commit"] is True
242
242
  assert result["logfile"] == "upsert.log"
243
+ assert result["cleanup"] is True
243
244
 
244
245
  def test_keyword_order_independent(self):
245
246
  r1 = _parse_tables_and_options("books COMPACT INTERACTIVE COMMIT")
@@ -995,3 +996,117 @@ class TestLoggerLevelRestoration:
995
996
 
996
997
  assert display_logger.level == orig_display
997
998
  assert main_logger.level == orig_main
999
+
1000
+
1001
+ # ---------------------------------------------------------------------------
1002
+ # CLEANUP keyword tests
1003
+ # ---------------------------------------------------------------------------
1004
+
1005
+
1006
+ class TestCleanup:
1007
+ def test_cleanup_keyword_parsed(self):
1008
+ result = _parse_tables_and_options("books CLEANUP COMMIT")
1009
+ assert result["cleanup"] is True
1010
+
1011
+ def test_no_cleanup_by_default(self):
1012
+ result = _parse_tables_and_options("books COMMIT")
1013
+ assert result["cleanup"] is False
1014
+
1015
+ def test_cleanup_calls_ups_cleanup(self, mock_state):
1016
+ state, db = mock_state
1017
+ fake_result = FakeUpsertResult(
1018
+ tables=[FakeTableResult(table_name="books")],
1019
+ staging_schema="staging",
1020
+ base_schema="public",
1021
+ )
1022
+
1023
+ with (
1024
+ patch("execsql.metacommands.upsert._require_pg_upsert"),
1025
+ patch("execsql.metacommands.upsert._create_pgupsert") as mock_create,
1026
+ ):
1027
+ mock_ups = mock_create.return_value
1028
+ mock_ups.run.return_value = fake_result
1029
+ x_pg_upsert(
1030
+ staging_schema="staging",
1031
+ base_schema="public",
1032
+ tail="books CLEANUP COMMIT",
1033
+ metacommandline="PG_UPSERT FROM staging TO public TABLES books CLEANUP COMMIT",
1034
+ )
1035
+ mock_ups.cleanup.assert_called_once()
1036
+
1037
+ def test_no_cleanup_without_keyword(self, mock_state):
1038
+ state, db = mock_state
1039
+ fake_result = FakeUpsertResult(
1040
+ tables=[FakeTableResult(table_name="books")],
1041
+ staging_schema="staging",
1042
+ base_schema="public",
1043
+ )
1044
+
1045
+ with (
1046
+ patch("execsql.metacommands.upsert._require_pg_upsert"),
1047
+ patch("execsql.metacommands.upsert._create_pgupsert") as mock_create,
1048
+ ):
1049
+ mock_ups = mock_create.return_value
1050
+ mock_ups.run.return_value = fake_result
1051
+ x_pg_upsert(
1052
+ staging_schema="staging",
1053
+ base_schema="public",
1054
+ tail="books COMMIT",
1055
+ metacommandline="PG_UPSERT FROM staging TO public TABLES books COMMIT",
1056
+ )
1057
+ mock_ups.cleanup.assert_not_called()
1058
+
1059
+
1060
+ # ---------------------------------------------------------------------------
1061
+ # Callback per-table subvar tests
1062
+ # ---------------------------------------------------------------------------
1063
+
1064
+
1065
+ class TestCallback:
1066
+ def test_callback_sets_qa_table_subvars(self, mock_state):
1067
+ from execsql.metacommands.upsert import _make_callback
1068
+
1069
+ state, db = mock_state
1070
+
1071
+ # Create a fake QA_TABLE_COMPLETE event
1072
+ mock_event = MagicMock()
1073
+ mock_event.event = MagicMock()
1074
+ mock_event.event.value = "qa_table_complete"
1075
+ mock_event.table = "books"
1076
+ mock_event.qa_passed = True
1077
+
1078
+ # We need the real CallbackEvent enum for comparison
1079
+ # Patch _state so the callback can access subvars
1080
+ with patch("execsql.metacommands.upsert._state", state):
1081
+ cb = _make_callback()
1082
+
1083
+ # Simulate CallbackEvent.QA_TABLE_COMPLETE match
1084
+ from pg_upsert import CallbackEvent
1085
+
1086
+ mock_event.event = CallbackEvent.QA_TABLE_COMPLETE
1087
+ cb(mock_event)
1088
+
1089
+ calls = {c[0][0]: c[0][1] for c in state.subvars.add_substitution.call_args_list}
1090
+ assert calls["$PG_UPSERT_CURRENT_TABLE"] == "books"
1091
+ assert calls["$PG_UPSERT_TABLE_QA_PASSED"] == "TRUE"
1092
+
1093
+ def test_callback_sets_upsert_table_subvars(self, mock_state):
1094
+ from execsql.metacommands.upsert import _make_callback
1095
+ from pg_upsert import CallbackEvent
1096
+
1097
+ state, db = mock_state
1098
+
1099
+ mock_event = MagicMock()
1100
+ mock_event.event = CallbackEvent.UPSERT_TABLE_COMPLETE
1101
+ mock_event.table = "authors"
1102
+ mock_event.rows_updated = 15
1103
+ mock_event.rows_inserted = 3
1104
+
1105
+ with patch("execsql.metacommands.upsert._state", state):
1106
+ cb = _make_callback()
1107
+ cb(mock_event)
1108
+
1109
+ calls = {c[0][0]: c[0][1] for c in state.subvars.add_substitution.call_args_list}
1110
+ assert calls["$PG_UPSERT_CURRENT_TABLE"] == "authors"
1111
+ assert calls["$PG_UPSERT_TABLE_ROWS_UPDATED"] == "15"
1112
+ assert calls["$PG_UPSERT_TABLE_ROWS_INSERTED"] == "3"
@@ -648,7 +648,7 @@ wheels = [
648
648
 
649
649
  [[package]]
650
650
  name = "execsql2"
651
- version = "2.12.5"
651
+ version = "2.12.6"
652
652
  source = { editable = "." }
653
653
  dependencies = [
654
654
  { name = "rich" },
@@ -766,7 +766,7 @@ requires-dist = [
766
766
  { name = "openpyxl", marker = "extra == 'formats'" },
767
767
  { name = "oracledb", marker = "extra == 'all-db'" },
768
768
  { name = "oracledb", marker = "extra == 'oracle'" },
769
- { name = "pg-upsert", marker = "extra == 'upsert'", specifier = ">=1.17.0" },
769
+ { name = "pg-upsert", marker = "extra == 'upsert'", specifier = ">=1.18.0" },
770
770
  { name = "polars", marker = "extra == 'formats'" },
771
771
  { name = "pre-commit", marker = "extra == 'dev'", specifier = ">=3.5.0" },
772
772
  { name = "psycopg2-binary", marker = "extra == 'all-db'" },
@@ -1780,7 +1780,7 @@ wheels = [
1780
1780
 
1781
1781
  [[package]]
1782
1782
  name = "pg-upsert"
1783
- version = "1.17.0"
1783
+ version = "1.18.0"
1784
1784
  source = { registry = "https://pypi.org/simple" }
1785
1785
  dependencies = [
1786
1786
  { name = "psycopg2-binary" },
@@ -1788,9 +1788,9 @@ dependencies = [
1788
1788
  { name = "rich" },
1789
1789
  { name = "typer" },
1790
1790
  ]
1791
- sdist = { url = "https://files.pythonhosted.org/packages/8c/a1/bb8eff0005eead3de3b1c650e1f3085aede27835901f5f8fef9723b28bd7/pg_upsert-1.17.0.tar.gz", hash = "sha256:fb561100f9268d5477d691675e8cc4101398d1b572290c7bb36e23bc72ea6767", size = 1444254, upload-time = "2026-04-03T16:11:56.624Z" }
1791
+ sdist = { url = "https://files.pythonhosted.org/packages/43/86/2e6adaa45b6746635e77ce7dc94729af28bcedc9f2daa139a5cbeb00926d/pg_upsert-1.18.0.tar.gz", hash = "sha256:1188d56f8dc6b3436f509b43a280e6f0c932e3e896615b657094c4a24c41af82", size = 1446377, upload-time = "2026-04-03T18:38:55.253Z" }
1792
1792
  wheels = [
1793
- { url = "https://files.pythonhosted.org/packages/bf/80/bf5197d1422a3ac116f80192dc09928439d4e54712de85ee7f7b0d0d7d06/pg_upsert-1.17.0-py3-none-any.whl", hash = "sha256:cf81a94a6e2deeb2cab4b2a1f80ea67ab748892a5e4c3c4e75f14acd226313d2", size = 78942, upload-time = "2026-04-03T16:11:55.102Z" },
1793
+ { url = "https://files.pythonhosted.org/packages/3e/24/ecf173ed388766a090896d18ca1234264e1b4f69fff5a103d32703f8d935/pg_upsert-1.18.0-py3-none-any.whl", hash = "sha256:eeb4c27f89bd421d58818359043aad9b4593775f6b677d1eaed832ece6c11645", size = 80421, upload-time = "2026-04-03T18:38:53.689Z" },
1794
1794
  ]
1795
1795
 
1796
1796
  [[package]]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes