execsql2 2.12.3__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.3 → execsql2-2.12.6}/CHANGELOG.md +25 -0
  2. {execsql2-2.12.3 → execsql2-2.12.6}/PKG-INFO +4 -1
  3. {execsql2-2.12.3 → execsql2-2.12.6}/docs/about/divergence.md +7 -6
  4. {execsql2-2.12.3 → execsql2-2.12.6}/docs/getting-started/installation.md +1 -0
  5. {execsql2-2.12.3 → execsql2-2.12.6}/docs/reference/metacommands.md +121 -0
  6. {execsql2-2.12.3 → execsql2-2.12.6}/extras/vscode-execsql/syntaxes/execsql.tmLanguage.json +1 -1
  7. {execsql2-2.12.3 → execsql2-2.12.6}/pyproject.toml +4 -2
  8. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/metacommands/__init__.py +9 -0
  9. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/metacommands/dispatch.py +30 -0
  10. execsql2-2.12.6/src/execsql/metacommands/upsert.py +473 -0
  11. {execsql2-2.12.3 → execsql2-2.12.6}/tests/cli/test_cli.py +6 -1
  12. execsql2-2.12.6/tests/metacommands/test_pg_upsert.py +1112 -0
  13. {execsql2-2.12.3 → execsql2-2.12.6}/uv.lock +30 -18
  14. {execsql2-2.12.3 → execsql2-2.12.6}/.claude/agents/dba.md +0 -0
  15. {execsql2-2.12.3 → execsql2-2.12.6}/.claude/agents/herald.md +0 -0
  16. {execsql2-2.12.3 → execsql2-2.12.6}/.claude/agents/inspector.md +0 -0
  17. {execsql2-2.12.3 → execsql2-2.12.6}/.claude/agents/liaison.md +0 -0
  18. {execsql2-2.12.3 → execsql2-2.12.6}/.claude/agents/oracle.md +0 -0
  19. {execsql2-2.12.3 → execsql2-2.12.6}/.claude/agents/patcher.md +0 -0
  20. {execsql2-2.12.3 → execsql2-2.12.6}/.claude/agents/qa.md +0 -0
  21. {execsql2-2.12.3 → execsql2-2.12.6}/.claude/agents/scribe.md +0 -0
  22. {execsql2-2.12.3 → execsql2-2.12.6}/.claude/commands/code-oracle.md +0 -0
  23. {execsql2-2.12.3 → execsql2-2.12.6}/.claude/commands/migrate.md +0 -0
  24. {execsql2-2.12.3 → execsql2-2.12.6}/.claude/commands/review-changes.md +0 -0
  25. {execsql2-2.12.3 → execsql2-2.12.6}/.claude/commands/test-module.md +0 -0
  26. {execsql2-2.12.3 → execsql2-2.12.6}/.claude/commands/update-changelog.md +0 -0
  27. {execsql2-2.12.3 → execsql2-2.12.6}/.claude/commands/where-is.md +0 -0
  28. {execsql2-2.12.3 → execsql2-2.12.6}/.claude/project_context.md +0 -0
  29. {execsql2-2.12.3 → execsql2-2.12.6}/.claude/state/status.md +0 -0
  30. {execsql2-2.12.3 → execsql2-2.12.6}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  31. {execsql2-2.12.3 → execsql2-2.12.6}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  32. {execsql2-2.12.3 → execsql2-2.12.6}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  33. {execsql2-2.12.3 → execsql2-2.12.6}/.github/workflows/ci-cd.yml +0 -0
  34. {execsql2-2.12.3 → execsql2-2.12.6}/.gitignore +0 -0
  35. {execsql2-2.12.3 → execsql2-2.12.6}/.pre-commit-config.yaml +0 -0
  36. {execsql2-2.12.3 → execsql2-2.12.6}/.pre-commit-hooks.yaml +0 -0
  37. {execsql2-2.12.3 → execsql2-2.12.6}/.python-version +0 -0
  38. {execsql2-2.12.3 → execsql2-2.12.6}/.readthedocs.yaml +0 -0
  39. {execsql2-2.12.3 → execsql2-2.12.6}/CLAUDE.md +0 -0
  40. {execsql2-2.12.3 → execsql2-2.12.6}/CONTRIBUTING.md +0 -0
  41. {execsql2-2.12.3 → execsql2-2.12.6}/LICENSE.txt +0 -0
  42. {execsql2-2.12.3 → execsql2-2.12.6}/NOTICE +0 -0
  43. {execsql2-2.12.3 → execsql2-2.12.6}/README.md +0 -0
  44. {execsql2-2.12.3 → execsql2-2.12.6}/SECURITY.md +0 -0
  45. {execsql2-2.12.3 → execsql2-2.12.6}/docs/about/contributors.md +0 -0
  46. {execsql2-2.12.3 → execsql2-2.12.6}/docs/about/copyright.md +0 -0
  47. {execsql2-2.12.3 → execsql2-2.12.6}/docs/api/cli.md +0 -0
  48. {execsql2-2.12.3 → execsql2-2.12.6}/docs/api/db.md +0 -0
  49. {execsql2-2.12.3 → execsql2-2.12.6}/docs/api/exporters.md +0 -0
  50. {execsql2-2.12.3 → execsql2-2.12.6}/docs/api/importers.md +0 -0
  51. {execsql2-2.12.3 → execsql2-2.12.6}/docs/api/index.md +0 -0
  52. {execsql2-2.12.3 → execsql2-2.12.6}/docs/api/metacommands.md +0 -0
  53. {execsql2-2.12.3 → execsql2-2.12.6}/docs/dev/adding_db_adapters.md +0 -0
  54. {execsql2-2.12.3 → execsql2-2.12.6}/docs/dev/adding_exporters.md +0 -0
  55. {execsql2-2.12.3 → execsql2-2.12.6}/docs/dev/adding_importers.md +0 -0
  56. {execsql2-2.12.3 → execsql2-2.12.6}/docs/dev/adding_metacommands.md +0 -0
  57. {execsql2-2.12.3 → execsql2-2.12.6}/docs/dev/architecture.md +0 -0
  58. {execsql2-2.12.3 → execsql2-2.12.6}/docs/getting-started/requirements.md +0 -0
  59. {execsql2-2.12.3 → execsql2-2.12.6}/docs/getting-started/syntax.md +0 -0
  60. {execsql2-2.12.3 → execsql2-2.12.6}/docs/guides/debugging.md +0 -0
  61. {execsql2-2.12.3 → execsql2-2.12.6}/docs/guides/documentation.md +0 -0
  62. {execsql2-2.12.3 → execsql2-2.12.6}/docs/guides/encoding.md +0 -0
  63. {execsql2-2.12.3 → execsql2-2.12.6}/docs/guides/examples.md +0 -0
  64. {execsql2-2.12.3 → execsql2-2.12.6}/docs/guides/formatter.md +0 -0
  65. {execsql2-2.12.3 → execsql2-2.12.6}/docs/guides/logging.md +0 -0
  66. {execsql2-2.12.3 → execsql2-2.12.6}/docs/guides/sql_syntax.md +0 -0
  67. {execsql2-2.12.3 → execsql2-2.12.6}/docs/guides/usage.md +0 -0
  68. {execsql2-2.12.3 → execsql2-2.12.6}/docs/guides/using_scripts.md +0 -0
  69. {execsql2-2.12.3 → execsql2-2.12.6}/docs/images/Compare_planets.png +0 -0
  70. {execsql2-2.12.3 → execsql2-2.12.6}/docs/images/actions.png +0 -0
  71. {execsql2-2.12.3 → execsql2-2.12.6}/docs/images/actions2.png +0 -0
  72. {execsql2-2.12.3 → execsql2-2.12.6}/docs/images/checkboxes.png +0 -0
  73. {execsql2-2.12.3 → execsql2-2.12.6}/docs/images/connect.b64 +0 -0
  74. {execsql2-2.12.3 → execsql2-2.12.6}/docs/images/connect.png +0 -0
  75. {execsql2-2.12.3 → execsql2-2.12.6}/docs/images/create_conf.png +0 -0
  76. {execsql2-2.12.3 → execsql2-2.12.6}/docs/images/data_error1_screenshot.jpg +0 -0
  77. {execsql2-2.12.3 → execsql2-2.12.6}/docs/images/entry_form.png +0 -0
  78. {execsql2-2.12.3 → execsql2-2.12.6}/docs/images/execsql_console.png +0 -0
  79. {execsql2-2.12.3 → execsql2-2.12.6}/docs/images/execsql_logo_01.png +0 -0
  80. {execsql2-2.12.3 → execsql2-2.12.6}/docs/images/fatals.png +0 -0
  81. {execsql2-2.12.3 → execsql2-2.12.6}/docs/images/logo_small.png +0 -0
  82. {execsql2-2.12.3 → execsql2-2.12.6}/docs/images/pause_terminal.png +0 -0
  83. {execsql2-2.12.3 → execsql2-2.12.6}/docs/images/pause_terminal_sm.b64 +0 -0
  84. {execsql2-2.12.3 → execsql2-2.12.6}/docs/images/pause_terminal_sm.png +0 -0
  85. {execsql2-2.12.3 → execsql2-2.12.6}/docs/images/prompt_compare.png +0 -0
  86. {execsql2-2.12.3 → execsql2-2.12.6}/docs/images/set_build_commands.jpg +0 -0
  87. {execsql2-2.12.3 → execsql2-2.12.6}/docs/images/unit_conversions.b64 +0 -0
  88. {execsql2-2.12.3 → execsql2-2.12.6}/docs/images/unit_conversions_029.png +0 -0
  89. {execsql2-2.12.3 → execsql2-2.12.6}/docs/images/unmatched.png +0 -0
  90. {execsql2-2.12.3 → execsql2-2.12.6}/docs/images/vim_execsql_highlight.png +0 -0
  91. {execsql2-2.12.3 → execsql2-2.12.6}/docs/index.md +0 -0
  92. {execsql2-2.12.3 → execsql2-2.12.6}/docs/reference/configuration.md +0 -0
  93. {execsql2-2.12.3 → execsql2-2.12.6}/docs/reference/security.md +0 -0
  94. {execsql2-2.12.3 → execsql2-2.12.6}/docs/reference/substitution_vars.md +0 -0
  95. {execsql2-2.12.3 → execsql2-2.12.6}/extras/vscode-execsql/README.md +0 -0
  96. {execsql2-2.12.3 → execsql2-2.12.6}/extras/vscode-execsql/package.json +0 -0
  97. {execsql2-2.12.3 → execsql2-2.12.6}/justfile +0 -0
  98. {execsql2-2.12.3 → execsql2-2.12.6}/scripts/generate_vscode_grammar.py +0 -0
  99. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/__init__.py +0 -0
  100. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/__main__.py +0 -0
  101. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/cli/__init__.py +0 -0
  102. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/cli/dsn.py +0 -0
  103. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/cli/help.py +0 -0
  104. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/cli/lint.py +0 -0
  105. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/cli/run.py +0 -0
  106. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/config.py +0 -0
  107. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/constants.py +0 -0
  108. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/db/__init__.py +0 -0
  109. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/db/access.py +0 -0
  110. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/db/base.py +0 -0
  111. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/db/dsn.py +0 -0
  112. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/db/duckdb.py +0 -0
  113. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/db/factory.py +0 -0
  114. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/db/firebird.py +0 -0
  115. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/db/mysql.py +0 -0
  116. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/db/oracle.py +0 -0
  117. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/db/postgres.py +0 -0
  118. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/db/sqlite.py +0 -0
  119. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/db/sqlserver.py +0 -0
  120. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/debug/__init__.py +0 -0
  121. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/debug/repl.py +0 -0
  122. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/exceptions.py +0 -0
  123. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/exporters/__init__.py +0 -0
  124. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/exporters/base.py +0 -0
  125. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/exporters/delimited.py +0 -0
  126. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/exporters/duckdb.py +0 -0
  127. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/exporters/feather.py +0 -0
  128. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/exporters/html.py +0 -0
  129. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/exporters/json.py +0 -0
  130. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/exporters/latex.py +0 -0
  131. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/exporters/markdown.py +0 -0
  132. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/exporters/ods.py +0 -0
  133. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/exporters/parquet.py +0 -0
  134. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/exporters/pretty.py +0 -0
  135. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/exporters/protocol.py +0 -0
  136. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/exporters/raw.py +0 -0
  137. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/exporters/sqlite.py +0 -0
  138. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/exporters/templates.py +0 -0
  139. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/exporters/values.py +0 -0
  140. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/exporters/xls.py +0 -0
  141. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/exporters/xlsx.py +0 -0
  142. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/exporters/xml.py +0 -0
  143. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/exporters/yaml.py +0 -0
  144. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/exporters/zip.py +0 -0
  145. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/format.py +0 -0
  146. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/gui/__init__.py +0 -0
  147. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/gui/base.py +0 -0
  148. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/gui/console.py +0 -0
  149. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/gui/desktop.py +0 -0
  150. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/gui/tui.py +0 -0
  151. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/importers/__init__.py +0 -0
  152. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/importers/base.py +0 -0
  153. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/importers/csv.py +0 -0
  154. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/importers/feather.py +0 -0
  155. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/importers/ods.py +0 -0
  156. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/importers/xls.py +0 -0
  157. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/metacommands/conditions.py +0 -0
  158. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/metacommands/connect.py +0 -0
  159. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/metacommands/control.py +0 -0
  160. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/metacommands/data.py +0 -0
  161. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/metacommands/debug.py +0 -0
  162. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/metacommands/io.py +0 -0
  163. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/metacommands/io_export.py +0 -0
  164. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/metacommands/io_fileops.py +0 -0
  165. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/metacommands/io_import.py +0 -0
  166. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/metacommands/io_write.py +0 -0
  167. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/metacommands/prompt.py +0 -0
  168. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/metacommands/script_ext.py +0 -0
  169. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/metacommands/system.py +0 -0
  170. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/models.py +0 -0
  171. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/parser.py +0 -0
  172. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/py.typed +0 -0
  173. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/script/__init__.py +0 -0
  174. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/script/control.py +0 -0
  175. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/script/engine.py +0 -0
  176. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/script/variables.py +0 -0
  177. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/state.py +0 -0
  178. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/types.py +0 -0
  179. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/utils/__init__.py +0 -0
  180. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/utils/auth.py +0 -0
  181. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/utils/crypto.py +0 -0
  182. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/utils/datetime.py +0 -0
  183. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/utils/errors.py +0 -0
  184. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/utils/fileio.py +0 -0
  185. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/utils/gui.py +0 -0
  186. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/utils/mail.py +0 -0
  187. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/utils/numeric.py +0 -0
  188. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/utils/regex.py +0 -0
  189. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/utils/strings.py +0 -0
  190. {execsql2-2.12.3 → execsql2-2.12.6}/src/execsql/utils/timer.py +0 -0
  191. {execsql2-2.12.3 → execsql2-2.12.6}/templates/README.md +0 -0
  192. {execsql2-2.12.3 → execsql2-2.12.6}/templates/config_settings.sqlite +0 -0
  193. {execsql2-2.12.3 → execsql2-2.12.6}/templates/example_config_prompt.sql +0 -0
  194. {execsql2-2.12.3 → execsql2-2.12.6}/templates/execsql.conf +0 -0
  195. {execsql2-2.12.3 → execsql2-2.12.6}/templates/make_config_db.sql +0 -0
  196. {execsql2-2.12.3 → execsql2-2.12.6}/templates/md_compare.sql +0 -0
  197. {execsql2-2.12.3 → execsql2-2.12.6}/templates/md_glossary.sql +0 -0
  198. {execsql2-2.12.3 → execsql2-2.12.6}/templates/md_upsert.sql +0 -0
  199. {execsql2-2.12.3 → execsql2-2.12.6}/templates/pg_compare.sql +0 -0
  200. {execsql2-2.12.3 → execsql2-2.12.6}/templates/pg_glossary.sql +0 -0
  201. {execsql2-2.12.3 → execsql2-2.12.6}/templates/pg_upsert.sql +0 -0
  202. {execsql2-2.12.3 → execsql2-2.12.6}/templates/script_template.sql +0 -0
  203. {execsql2-2.12.3 → execsql2-2.12.6}/templates/ss_compare.sql +0 -0
  204. {execsql2-2.12.3 → execsql2-2.12.6}/templates/ss_glossary.sql +0 -0
  205. {execsql2-2.12.3 → execsql2-2.12.6}/templates/ss_upsert.sql +0 -0
  206. {execsql2-2.12.3 → execsql2-2.12.6}/tests/__init__.py +0 -0
  207. {execsql2-2.12.3 → execsql2-2.12.6}/tests/cli/__init__.py +0 -0
  208. {execsql2-2.12.3 → execsql2-2.12.6}/tests/cli/test_cli_e2e.py +0 -0
  209. {execsql2-2.12.3 → execsql2-2.12.6}/tests/cli/test_cli_run.py +0 -0
  210. {execsql2-2.12.3 → execsql2-2.12.6}/tests/cli/test_lint.py +0 -0
  211. {execsql2-2.12.3 → execsql2-2.12.6}/tests/cli/test_ping.py +0 -0
  212. {execsql2-2.12.3 → execsql2-2.12.6}/tests/cli/test_profile.py +0 -0
  213. {execsql2-2.12.3 → execsql2-2.12.6}/tests/conftest.py +0 -0
  214. {execsql2-2.12.3 → execsql2-2.12.6}/tests/db/__init__.py +0 -0
  215. {execsql2-2.12.3 → execsql2-2.12.6}/tests/db/test_base.py +0 -0
  216. {execsql2-2.12.3 → execsql2-2.12.6}/tests/db/test_duckdb.py +0 -0
  217. {execsql2-2.12.3 → execsql2-2.12.6}/tests/db/test_factory.py +0 -0
  218. {execsql2-2.12.3 → execsql2-2.12.6}/tests/db/test_postgres.py +0 -0
  219. {execsql2-2.12.3 → execsql2-2.12.6}/tests/db/test_sqlite.py +0 -0
  220. {execsql2-2.12.3 → execsql2-2.12.6}/tests/db/test_sqlite_extra.py +0 -0
  221. {execsql2-2.12.3 → execsql2-2.12.6}/tests/exporters/__init__.py +0 -0
  222. {execsql2-2.12.3 → execsql2-2.12.6}/tests/exporters/test_base.py +0 -0
  223. {execsql2-2.12.3 → execsql2-2.12.6}/tests/exporters/test_db.py +0 -0
  224. {execsql2-2.12.3 → execsql2-2.12.6}/tests/exporters/test_delimited.py +0 -0
  225. {execsql2-2.12.3 → execsql2-2.12.6}/tests/exporters/test_duckdb_exporter.py +0 -0
  226. {execsql2-2.12.3 → execsql2-2.12.6}/tests/exporters/test_exporters.py +0 -0
  227. {execsql2-2.12.3 → execsql2-2.12.6}/tests/exporters/test_feather.py +0 -0
  228. {execsql2-2.12.3 → execsql2-2.12.6}/tests/exporters/test_html_latex.py +0 -0
  229. {execsql2-2.12.3 → execsql2-2.12.6}/tests/exporters/test_json.py +0 -0
  230. {execsql2-2.12.3 → execsql2-2.12.6}/tests/exporters/test_markdown.py +0 -0
  231. {execsql2-2.12.3 → execsql2-2.12.6}/tests/exporters/test_ods.py +0 -0
  232. {execsql2-2.12.3 → execsql2-2.12.6}/tests/exporters/test_parquet.py +0 -0
  233. {execsql2-2.12.3 → execsql2-2.12.6}/tests/exporters/test_sqlite_exporter.py +0 -0
  234. {execsql2-2.12.3 → execsql2-2.12.6}/tests/exporters/test_templates.py +0 -0
  235. {execsql2-2.12.3 → execsql2-2.12.6}/tests/exporters/test_xls_xlsx.py +0 -0
  236. {execsql2-2.12.3 → execsql2-2.12.6}/tests/exporters/test_xlsx.py +0 -0
  237. {execsql2-2.12.3 → execsql2-2.12.6}/tests/exporters/test_xml.py +0 -0
  238. {execsql2-2.12.3 → execsql2-2.12.6}/tests/exporters/test_yaml.py +0 -0
  239. {execsql2-2.12.3 → execsql2-2.12.6}/tests/exporters/test_zip.py +0 -0
  240. {execsql2-2.12.3 → execsql2-2.12.6}/tests/gui/__init__.py +0 -0
  241. {execsql2-2.12.3 → execsql2-2.12.6}/tests/gui/test_backends.py +0 -0
  242. {execsql2-2.12.3 → execsql2-2.12.6}/tests/importers/__init__.py +0 -0
  243. {execsql2-2.12.3 → execsql2-2.12.6}/tests/importers/test_csv_importer.py +0 -0
  244. {execsql2-2.12.3 → execsql2-2.12.6}/tests/importers/test_feather_importer.py +0 -0
  245. {execsql2-2.12.3 → execsql2-2.12.6}/tests/importers/test_ods_importer.py +0 -0
  246. {execsql2-2.12.3 → execsql2-2.12.6}/tests/importers/test_xls_importer.py +0 -0
  247. {execsql2-2.12.3 → execsql2-2.12.6}/tests/integration/__init__.py +0 -0
  248. {execsql2-2.12.3 → execsql2-2.12.6}/tests/integration/conftest.py +0 -0
  249. {execsql2-2.12.3 → execsql2-2.12.6}/tests/integration/test_dsn.py +0 -0
  250. {execsql2-2.12.3 → execsql2-2.12.6}/tests/integration/test_duckdb.py +0 -0
  251. {execsql2-2.12.3 → execsql2-2.12.6}/tests/integration/test_mysql.py +0 -0
  252. {execsql2-2.12.3 → execsql2-2.12.6}/tests/integration/test_postgres.py +0 -0
  253. {execsql2-2.12.3 → execsql2-2.12.6}/tests/integration/test_sqlite.py +0 -0
  254. {execsql2-2.12.3 → execsql2-2.12.6}/tests/metacommands/__init__.py +0 -0
  255. {execsql2-2.12.3 → execsql2-2.12.6}/tests/metacommands/test_assert.py +0 -0
  256. {execsql2-2.12.3 → execsql2-2.12.6}/tests/metacommands/test_breakpoint.py +0 -0
  257. {execsql2-2.12.3 → execsql2-2.12.6}/tests/metacommands/test_connect.py +0 -0
  258. {execsql2-2.12.3 → execsql2-2.12.6}/tests/metacommands/test_io_export.py +0 -0
  259. {execsql2-2.12.3 → execsql2-2.12.6}/tests/metacommands/test_io_import.py +0 -0
  260. {execsql2-2.12.3 → execsql2-2.12.6}/tests/metacommands/test_metacommands.py +0 -0
  261. {execsql2-2.12.3 → execsql2-2.12.6}/tests/metacommands/test_metacommands_connect.py +0 -0
  262. {execsql2-2.12.3 → execsql2-2.12.6}/tests/metacommands/test_metacommands_data.py +0 -0
  263. {execsql2-2.12.3 → execsql2-2.12.6}/tests/metacommands/test_metacommands_extended.py +0 -0
  264. {execsql2-2.12.3 → execsql2-2.12.6}/tests/metacommands/test_metacommands_fileops_extra.py +0 -0
  265. {execsql2-2.12.3 → execsql2-2.12.6}/tests/metacommands/test_metacommands_io.py +0 -0
  266. {execsql2-2.12.3 → execsql2-2.12.6}/tests/metacommands/test_metacommands_io_write_extra.py +0 -0
  267. {execsql2-2.12.3 → execsql2-2.12.6}/tests/metacommands/test_metacommands_script_ext.py +0 -0
  268. {execsql2-2.12.3 → execsql2-2.12.6}/tests/metacommands/test_metacommands_system.py +0 -0
  269. {execsql2-2.12.3 → execsql2-2.12.6}/tests/metacommands/test_metacommands_system_extra.py +0 -0
  270. {execsql2-2.12.3 → execsql2-2.12.6}/tests/metacommands/test_row_count.py +0 -0
  271. {execsql2-2.12.3 → execsql2-2.12.6}/tests/test_config.py +0 -0
  272. {execsql2-2.12.3 → execsql2-2.12.6}/tests/test_config_data.py +0 -0
  273. {execsql2-2.12.3 → execsql2-2.12.6}/tests/test_constants.py +0 -0
  274. {execsql2-2.12.3 → execsql2-2.12.6}/tests/test_engine.py +0 -0
  275. {execsql2-2.12.3 → execsql2-2.12.6}/tests/test_error_messages.py +0 -0
  276. {execsql2-2.12.3 → execsql2-2.12.6}/tests/test_exceptions.py +0 -0
  277. {execsql2-2.12.3 → execsql2-2.12.6}/tests/test_format.py +0 -0
  278. {execsql2-2.12.3 → execsql2-2.12.6}/tests/test_mail.py +0 -0
  279. {execsql2-2.12.3 → execsql2-2.12.6}/tests/test_models.py +0 -0
  280. {execsql2-2.12.3 → execsql2-2.12.6}/tests/test_package.py +0 -0
  281. {execsql2-2.12.3 → execsql2-2.12.6}/tests/test_parser.py +0 -0
  282. {execsql2-2.12.3 → execsql2-2.12.6}/tests/test_registry.py +0 -0
  283. {execsql2-2.12.3 → execsql2-2.12.6}/tests/test_script.py +0 -0
  284. {execsql2-2.12.3 → execsql2-2.12.6}/tests/test_state.py +0 -0
  285. {execsql2-2.12.3 → execsql2-2.12.6}/tests/test_types.py +0 -0
  286. {execsql2-2.12.3 → execsql2-2.12.6}/tests/utils/__init__.py +0 -0
  287. {execsql2-2.12.3 → execsql2-2.12.6}/tests/utils/test_auth.py +0 -0
  288. {execsql2-2.12.3 → execsql2-2.12.6}/tests/utils/test_auth_extra.py +0 -0
  289. {execsql2-2.12.3 → execsql2-2.12.6}/tests/utils/test_crypto.py +0 -0
  290. {execsql2-2.12.3 → execsql2-2.12.6}/tests/utils/test_datetime.py +0 -0
  291. {execsql2-2.12.3 → execsql2-2.12.6}/tests/utils/test_errors.py +0 -0
  292. {execsql2-2.12.3 → execsql2-2.12.6}/tests/utils/test_errors_extra.py +0 -0
  293. {execsql2-2.12.3 → execsql2-2.12.6}/tests/utils/test_fileio.py +0 -0
  294. {execsql2-2.12.3 → execsql2-2.12.6}/tests/utils/test_fileio_extra.py +0 -0
  295. {execsql2-2.12.3 → execsql2-2.12.6}/tests/utils/test_numeric.py +0 -0
  296. {execsql2-2.12.3 → execsql2-2.12.6}/tests/utils/test_regex.py +0 -0
  297. {execsql2-2.12.3 → execsql2-2.12.6}/tests/utils/test_strings.py +0 -0
  298. {execsql2-2.12.3 → execsql2-2.12.6}/tests/utils/test_timer.py +0 -0
  299. {execsql2-2.12.3 → execsql2-2.12.6}/tests/utils/test_timer_extra.py +0 -0
  300. {execsql2-2.12.3 → execsql2-2.12.6}/zensical.toml +0 -0
@@ -13,6 +13,31 @@ ______________________________________________________________________
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
+
25
+ ## [2.12.5] - 2026-04-03
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
+
31
+ ______________________________________________________________________
32
+
33
+ ## [2.12.4] - 2026-04-03
34
+
35
+ ### Added
36
+
37
+ - New `PG_UPSERT` metacommand for QA-checked, FK-dependency-ordered upserts from a staging schema to a base schema on PostgreSQL. Integrates [pg-upsert](https://pg-upsert.readthedocs.io/) as an optional dependency (`pip install execsql2[upsert]`). Three modes: full pipeline (`PG_UPSERT FROM ... TO ... TABLES ...`), QA-only (`PG_UPSERT QA ...`), and schema check (`PG_UPSERT CHECK ...`). Supports `METHOD`, `COMMIT`, `INTERACTIVE`, `COMPACT`, `EXCLUDE`, `EXCLUDE_NULL`, and `LOGFILE` keywords. Sets 12 `$PG_UPSERT_*` substitution variables after execution.
38
+
39
+ ______________________________________________________________________
40
+
16
41
  ## [2.12.3] - 2026-04-02
17
42
 
18
43
  ### Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: execsql2
3
- Version: 2.12.3
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,6 +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.18.0; extra == 'all'
54
55
  Requires-Dist: polars; extra == 'all'
55
56
  Requires-Dist: psycopg2-binary; extra == 'all'
56
57
  Requires-Dist: pymysql; extra == 'all'
@@ -107,6 +108,8 @@ Provides-Extra: oracle
107
108
  Requires-Dist: oracledb; extra == 'oracle'
108
109
  Provides-Extra: postgres
109
110
  Requires-Dist: psycopg2-binary; extra == 'postgres'
111
+ Provides-Extra: upsert
112
+ Requires-Dist: pg-upsert>=1.18.0; extra == 'upsert'
110
113
  Description-Content-Type: text/markdown
111
114
 
112
115
  > [!NOTE]
@@ -41,12 +41,13 @@ ______________________________________________________________________
41
41
 
42
42
  ### Metacommands
43
43
 
44
- | Metacommand | Description |
45
- | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
46
- | `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. |
47
- | `BREAKPOINT` | Pause script execution and drop into an interactive debug REPL. See [Debugging](#debugging) below for full details. |
48
- | `CONFIG SHOW_PROGRESS` | Enable the Rich progress bar for IMPORT operations at runtime. |
49
- | `CONFIG LOG_SQL` | Enable SQL query audit logging — writes executed SQL to the log file. |
44
+ | Metacommand | Description |
45
+ | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
46
+ | `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. |
47
+ | `BREAKPOINT` | Pause script execution and drop into an interactive debug REPL. See [Debugging](#debugging) below for full details. |
48
+ | `CONFIG SHOW_PROGRESS` | Enable the Rich progress bar for IMPORT operations at runtime. |
49
+ | `CONFIG LOG_SQL` | Enable SQL query audit logging — writes executed SQL to the log file. |
50
+ | `PG_UPSERT` | QA-checked, FK-dependency-ordered upserts from staging to base schema on PostgreSQL. Integrates [pg-upsert](https://pg-upsert.readthedocs.io/) as an optional dependency. Three modes: full pipeline, QA-only, and schema check. |
50
51
 
51
52
  ### Conditional Tests
52
53
 
@@ -25,6 +25,7 @@ pip install "execsql2[duckdb]" # DuckDB
25
25
  pip install "execsql2[mssql]" # MS SQL Server / ODBC
26
26
  pip install "execsql2[formats]" # ODS, Excel, Jinja2, Feather, Parquet, HDF5
27
27
  pip install "execsql2[auth]" # OS keyring integration
28
+ pip install "execsql2[upsert]" # PG_UPSERT metacommand (pg-upsert)
28
29
  pip install "execsql2[all-db]" # All database drivers
29
30
  pip install "execsql2[all]" # Everything
30
31
  ```
@@ -2066,6 +2066,127 @@ If the "HALT" action is taken, either as a result of user input or as a result o
2066
2066
  Double quotes (as shown above), apostrophes, or square brackets can be used to delimit the text.
2067
2067
 
2068
2068
 
2069
+ ## PG_UPSERT { #pg_upsert }
2070
+
2071
+ ```
2072
+ PG_UPSERT FROM <staging_schema> TO <base_schema> TABLES <table1>, <table2> [options]
2073
+ PG_UPSERT QA FROM <staging_schema> TO <base_schema> TABLES <table1>, <table2> [options]
2074
+ PG_UPSERT CHECK FROM <staging_schema> TO <base_schema> TABLES <table1>, <table2>
2075
+ ```
2076
+
2077
+ Performs QA-checked, FK-dependency-ordered upserts from a staging schema to a base schema on PostgreSQL. Integrates [pg-upsert](https://pg-upsert.readthedocs.io/) as an optional dependency.
2078
+
2079
+ **Requires:** `pip install execsql2[upsert]`
2080
+
2081
+ **Requires:** A PostgreSQL connection. Raises an error if the current DBMS is not PostgreSQL.
2082
+
2083
+ ### Modes
2084
+
2085
+ - **Full pipeline** (`PG_UPSERT FROM ... TO ... TABLES ...`): Runs all QA checks, then upserts data (update + insert by default), with optional commit.
2086
+ - **QA only** (`PG_UPSERT QA FROM ... TO ... TABLES ...`): Runs all QA checks without upserting. Never commits.
2087
+ - **Schema check** (`PG_UPSERT CHECK FROM ... TO ... TABLES ...`): Checks column existence and type compatibility only. Never commits.
2088
+
2089
+ ### Optional keywords
2090
+
2091
+ Keywords can appear in any order after the table list.
2092
+
2093
+ | Keyword | Description |
2094
+ |---------|-------------|
2095
+ | `METHOD upsert\|update\|insert` | Upsert strategy. `upsert` (default) updates existing + inserts new rows. `update` only updates. `insert` only inserts. Full mode only. |
2096
+ | `COMMIT` | Commit changes if QA passes. Without it, changes are rolled back (dry-run). Full mode only. |
2097
+ | `EXCLUDE col1, col2` | Columns to skip during upsert. |
2098
+ | `EXCLUDE_NULL col1, col2` | Columns to skip in null QA checks. |
2099
+ | `INTERACTIVE` | Enable pg-upsert's interactive UI dialogs (tkinter or textual) for reviewing QA failures. Without it, runs non-interactively. |
2100
+ | `COMPACT` | Use compact grid format for QA summary instead of detailed per-table panels. |
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). |
2103
+
2104
+ ### Substitution variables
2105
+
2106
+ Set after every `PG_UPSERT` execution:
2107
+
2108
+ | Variable | Type | Description |
2109
+ |----------|------|-------------|
2110
+ | `$PG_UPSERT_QA_PASSED` | TRUE/FALSE | Did all QA checks pass? |
2111
+ | `$PG_UPSERT_ROWS_UPDATED` | integer | Total rows updated across all tables |
2112
+ | `$PG_UPSERT_ROWS_INSERTED` | integer | Total rows inserted across all tables |
2113
+ | `$PG_UPSERT_COMMITTED` | TRUE/FALSE | Were changes committed? |
2114
+ | `$PG_UPSERT_STAGING_SCHEMA` | string | Staging schema name used |
2115
+ | `$PG_UPSERT_BASE_SCHEMA` | string | Base schema name used |
2116
+ | `$PG_UPSERT_TABLES` | string | Comma-separated list of table names processed |
2117
+ | `$PG_UPSERT_METHOD` | string | Upsert method used |
2118
+ | `$PG_UPSERT_DURATION` | float | Elapsed time in seconds |
2119
+ | `$PG_UPSERT_STARTED_AT` | string | ISO 8601 start timestamp |
2120
+ | `$PG_UPSERT_FINISHED_AT` | string | ISO 8601 end timestamp |
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) |
2126
+
2127
+ !!! note "Using `$PG_UPSERT_RESULT_JSON` with WRITE"
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:
2129
+
2130
+ ```sql
2131
+ -- !x! WRITE ~!!$PG_UPSERT_RESULT_JSON!!~
2132
+ -- !x! WRITE `!!$PG_UPSERT_RESULT_JSON!!`
2133
+ ```
2134
+
2135
+ ### Temporary objects
2136
+
2137
+ pg-upsert creates temporary tables and views (all prefixed `ups_`) that persist after the metacommand completes. Users can query these for debugging and inspection — for example, `SELECT * FROM ups_control` shows per-table QA results and row counts.
2138
+
2139
+ For the full list of temporary objects and their schemas, see the [pg-upsert Temporary Objects Reference](https://pg-upsert.readthedocs.io/en/latest/architecture/#temporary-objects-reference).
2140
+
2141
+ ### Examples
2142
+
2143
+ ```sql
2144
+ -- Full pipeline: QA + upsert + commit
2145
+ -- !x! PG_UPSERT FROM staging TO public TABLES books, authors COMMIT
2146
+
2147
+ -- Dry run (no commit) with update-only method
2148
+ -- !x! PG_UPSERT FROM staging TO public TABLES books METHOD update
2149
+
2150
+ -- QA only with interactive review
2151
+ -- !x! PG_UPSERT QA FROM staging TO public TABLES books, authors INTERACTIVE
2152
+
2153
+ -- Schema check only
2154
+ -- !x! PG_UPSERT CHECK FROM staging TO public TABLES books, authors
2155
+
2156
+ -- Log pg-upsert output to a file
2157
+ -- !x! PG_UPSERT FROM staging TO public TABLES books, authors LOGFILE "upsert.log" COMMIT
2158
+
2159
+ -- Use substitution variables after upsert
2160
+ -- !x! PG_UPSERT FROM staging TO public TABLES books COMMIT
2161
+ -- !x! WRITE "Updated !!$PG_UPSERT_ROWS_UPDATED!! rows, inserted !!$PG_UPSERT_ROWS_INSERTED!! in !!$PG_UPSERT_DURATION!! seconds"
2162
+
2163
+ -- Inspect pg-upsert's control table after a run
2164
+ -- !x! PG_UPSERT FROM staging TO public TABLES books, authors, genres COMMIT
2165
+ -- !x! EXPORT ups_control TO stdout AS txt
2166
+
2167
+ -- Export QA results to a CSV file for review
2168
+ -- !x! PG_UPSERT QA FROM staging TO public TABLES books, authors
2169
+ -- !x! EXPORT ups_control TO "qa_results.csv" AS csv
2170
+
2171
+ -- Check for type mismatches between schemas and display them
2172
+ -- !x! PG_UPSERT CHECK FROM staging TO public TABLES books, authors
2173
+ -- !x! EXPORT ups_type_mismatches TO stdout AS txt
2174
+
2175
+ -- Conditional logic based on QA results
2176
+ -- !x! PG_UPSERT FROM staging TO public TABLES books, authors
2177
+ -- !x! IF !!$PG_UPSERT_QA_PASSED!! = TRUE
2178
+ -- !x! WRITE "All QA checks passed — proceeding with commit"
2179
+ -- !x! ELSE
2180
+ -- !x! WRITE "QA failed — inspect ups_control for details"
2181
+ -- !x! EXPORT ups_control TO stdout AS txt
2182
+ -- !x! ENDIF
2183
+
2184
+ -- Write the full JSON result using tilde delimiters (JSON contains " and [])
2185
+ -- !x! PG_UPSERT FROM staging TO public TABLES books COMMIT
2186
+ -- !x! WRITE ~!!$PG_UPSERT_RESULT_JSON!!~
2187
+ ```
2188
+
2189
+
2069
2190
  ## PG_VACUUM
2070
2191
 
2071
2192
  ```
@@ -93,7 +93,7 @@
93
93
  },
94
94
  "action-keywords": {
95
95
  "comment": "sub, write, execute script, export, etc.",
96
- "match": "(?i)\\b(reset\\s+dialog_canceled|write\\s+create_table|export_metadata|sub_querystring|execute\\s+script|append\\s+script|extend\\s+script|reset\\s+counter|export\\s+query|sub_tempfile|write\\s+script|import_file|set\\s+counter|sub_decrypt|sub_encrypt|autocommit|breakpoint|copy\\s+query|disconnect|select_sub|sub_append|system_cmd|pg_vacuum|sub_empty|sub_local|connect|include|max_int|rm_file|subdata|sub_add|sub_ini|assert|export|import|rm_sub|debug|email|serve|write|copy|log|run|sub|use|zip|cd)\\b",
96
+ "match": "(?i)\\b(reset\\s+dialog_canceled|write\\s+create_table|export_metadata|pg_upsert\\s+check|sub_querystring|execute\\s+script|append\\s+script|extend\\s+script|reset\\s+counter|export\\s+query|pg_upsert\\s+qa|sub_tempfile|write\\s+script|import_file|set\\s+counter|sub_decrypt|sub_encrypt|autocommit|breakpoint|copy\\s+query|disconnect|select_sub|sub_append|system_cmd|pg_upsert|pg_vacuum|sub_empty|sub_local|connect|include|max_int|rm_file|subdata|sub_add|sub_ini|assert|export|import|rm_sub|debug|email|serve|write|copy|log|run|sub|use|zip|cd)\\b",
97
97
  "name": "keyword.other.execsql"
98
98
  },
99
99
  "config-event-keywords": {
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "execsql2"
7
- version = "2.12.3"
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,6 +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.18.0"]
61
62
  # Convenience groups
62
63
  all-db = [
63
64
  "psycopg2-binary",
@@ -71,6 +72,7 @@ all = [
71
72
  "execsql2[all-db]",
72
73
  "execsql2[formats]",
73
74
  "execsql2[auth]",
75
+ "execsql2[upsert]",
74
76
  ]
75
77
  dev = [
76
78
  "build>=1.2.2.post1",
@@ -159,7 +161,7 @@ skip-magic-trailing-comma = false
159
161
  line-ending = "auto"
160
162
 
161
163
  [tool.bumpversion]
162
- current_version = "2.12.3"
164
+ current_version = "2.12.6"
163
165
  commit = true
164
166
  commit_args = "--no-verify"
165
167
  tag = true
@@ -168,6 +168,11 @@ from execsql.metacommands.script_ext import (
168
168
  x_extendscript_sql,
169
169
  x_executescript,
170
170
  )
171
+ from execsql.metacommands.upsert import (
172
+ x_pg_upsert,
173
+ x_pg_upsert_check,
174
+ x_pg_upsert_qa,
175
+ )
171
176
  from execsql.metacommands.system import (
172
177
  x_system_cmd,
173
178
  x_email,
@@ -401,6 +406,10 @@ __all__ = [
401
406
  "x_write_warnings",
402
407
  "x_gui_level",
403
408
  "x_execute",
409
+ # upsert handlers
410
+ "x_pg_upsert",
411
+ "x_pg_upsert_check",
412
+ "x_pg_upsert_qa",
404
413
  # regex helpers
405
414
  "ins_rxs",
406
415
  "ins_quoted_rx",
@@ -168,6 +168,11 @@ from execsql.metacommands.script_ext import (
168
168
  x_extendscript_metacommand,
169
169
  x_extendscript_sql,
170
170
  )
171
+ from execsql.metacommands.upsert import (
172
+ x_pg_upsert,
173
+ x_pg_upsert_check,
174
+ x_pg_upsert_qa,
175
+ )
171
176
  from execsql.metacommands.system import (
172
177
  x_cancel_halt,
173
178
  x_cancel_halt_email,
@@ -2142,6 +2147,31 @@ def build_dispatch_table() -> MetaCommandList:
2142
2147
  x_write,
2143
2148
  )
2144
2149
 
2150
+ # ------------------------------------------------------------------
2151
+ # PG_UPSERT — pg-upsert integration (optional dependency)
2152
+ # ------------------------------------------------------------------
2153
+ # Order matters: CHECK and QA patterns must precede the general pattern
2154
+ # so that "PG_UPSERT CHECK ..." and "PG_UPSERT QA ..." are matched
2155
+ # before "PG_UPSERT FROM ...".
2156
+ mcl.add(
2157
+ r"^\s*PG_UPSERT\s+CHECK\s+FROM\s+(?P<staging_schema>\S+)\s+TO\s+(?P<base_schema>\S+)\s+TABLES\s+(?P<tail>.+)$",
2158
+ x_pg_upsert_check,
2159
+ description="PG_UPSERT CHECK",
2160
+ category="action",
2161
+ )
2162
+ mcl.add(
2163
+ r"^\s*PG_UPSERT\s+QA\s+FROM\s+(?P<staging_schema>\S+)\s+TO\s+(?P<base_schema>\S+)\s+TABLES\s+(?P<tail>.+)$",
2164
+ x_pg_upsert_qa,
2165
+ description="PG_UPSERT QA",
2166
+ category="action",
2167
+ )
2168
+ mcl.add(
2169
+ r"^\s*PG_UPSERT\s+FROM\s+(?P<staging_schema>\S+)\s+TO\s+(?P<base_schema>\S+)\s+TABLES\s+(?P<tail>.+)$",
2170
+ x_pg_upsert,
2171
+ description="PG_UPSERT",
2172
+ category="action",
2173
+ )
2174
+
2145
2175
  # ------------------------------------------------------------------
2146
2176
  # SUB (top-level variable assignment — kept near end so more specific
2147
2177
  # SUB_* patterns above take precedence)