execsql2 2.12.3__tar.gz → 2.12.5__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.5}/CHANGELOG.md +12 -0
  2. {execsql2-2.12.3 → execsql2-2.12.5}/PKG-INFO +4 -1
  3. {execsql2-2.12.3 → execsql2-2.12.5}/docs/about/divergence.md +7 -6
  4. {execsql2-2.12.3 → execsql2-2.12.5}/docs/getting-started/installation.md +1 -0
  5. {execsql2-2.12.3 → execsql2-2.12.5}/docs/reference/metacommands.md +116 -0
  6. {execsql2-2.12.3 → execsql2-2.12.5}/extras/vscode-execsql/syntaxes/execsql.tmLanguage.json +1 -1
  7. {execsql2-2.12.3 → execsql2-2.12.5}/pyproject.toml +4 -2
  8. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/metacommands/__init__.py +9 -0
  9. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/metacommands/dispatch.py +30 -0
  10. execsql2-2.12.5/src/execsql/metacommands/upsert.py +448 -0
  11. {execsql2-2.12.3 → execsql2-2.12.5}/tests/cli/test_cli.py +6 -1
  12. execsql2-2.12.5/tests/metacommands/test_pg_upsert.py +997 -0
  13. {execsql2-2.12.3 → execsql2-2.12.5}/uv.lock +30 -18
  14. {execsql2-2.12.3 → execsql2-2.12.5}/.claude/agents/dba.md +0 -0
  15. {execsql2-2.12.3 → execsql2-2.12.5}/.claude/agents/herald.md +0 -0
  16. {execsql2-2.12.3 → execsql2-2.12.5}/.claude/agents/inspector.md +0 -0
  17. {execsql2-2.12.3 → execsql2-2.12.5}/.claude/agents/liaison.md +0 -0
  18. {execsql2-2.12.3 → execsql2-2.12.5}/.claude/agents/oracle.md +0 -0
  19. {execsql2-2.12.3 → execsql2-2.12.5}/.claude/agents/patcher.md +0 -0
  20. {execsql2-2.12.3 → execsql2-2.12.5}/.claude/agents/qa.md +0 -0
  21. {execsql2-2.12.3 → execsql2-2.12.5}/.claude/agents/scribe.md +0 -0
  22. {execsql2-2.12.3 → execsql2-2.12.5}/.claude/commands/code-oracle.md +0 -0
  23. {execsql2-2.12.3 → execsql2-2.12.5}/.claude/commands/migrate.md +0 -0
  24. {execsql2-2.12.3 → execsql2-2.12.5}/.claude/commands/review-changes.md +0 -0
  25. {execsql2-2.12.3 → execsql2-2.12.5}/.claude/commands/test-module.md +0 -0
  26. {execsql2-2.12.3 → execsql2-2.12.5}/.claude/commands/update-changelog.md +0 -0
  27. {execsql2-2.12.3 → execsql2-2.12.5}/.claude/commands/where-is.md +0 -0
  28. {execsql2-2.12.3 → execsql2-2.12.5}/.claude/project_context.md +0 -0
  29. {execsql2-2.12.3 → execsql2-2.12.5}/.claude/state/status.md +0 -0
  30. {execsql2-2.12.3 → execsql2-2.12.5}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  31. {execsql2-2.12.3 → execsql2-2.12.5}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  32. {execsql2-2.12.3 → execsql2-2.12.5}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  33. {execsql2-2.12.3 → execsql2-2.12.5}/.github/workflows/ci-cd.yml +0 -0
  34. {execsql2-2.12.3 → execsql2-2.12.5}/.gitignore +0 -0
  35. {execsql2-2.12.3 → execsql2-2.12.5}/.pre-commit-config.yaml +0 -0
  36. {execsql2-2.12.3 → execsql2-2.12.5}/.pre-commit-hooks.yaml +0 -0
  37. {execsql2-2.12.3 → execsql2-2.12.5}/.python-version +0 -0
  38. {execsql2-2.12.3 → execsql2-2.12.5}/.readthedocs.yaml +0 -0
  39. {execsql2-2.12.3 → execsql2-2.12.5}/CLAUDE.md +0 -0
  40. {execsql2-2.12.3 → execsql2-2.12.5}/CONTRIBUTING.md +0 -0
  41. {execsql2-2.12.3 → execsql2-2.12.5}/LICENSE.txt +0 -0
  42. {execsql2-2.12.3 → execsql2-2.12.5}/NOTICE +0 -0
  43. {execsql2-2.12.3 → execsql2-2.12.5}/README.md +0 -0
  44. {execsql2-2.12.3 → execsql2-2.12.5}/SECURITY.md +0 -0
  45. {execsql2-2.12.3 → execsql2-2.12.5}/docs/about/contributors.md +0 -0
  46. {execsql2-2.12.3 → execsql2-2.12.5}/docs/about/copyright.md +0 -0
  47. {execsql2-2.12.3 → execsql2-2.12.5}/docs/api/cli.md +0 -0
  48. {execsql2-2.12.3 → execsql2-2.12.5}/docs/api/db.md +0 -0
  49. {execsql2-2.12.3 → execsql2-2.12.5}/docs/api/exporters.md +0 -0
  50. {execsql2-2.12.3 → execsql2-2.12.5}/docs/api/importers.md +0 -0
  51. {execsql2-2.12.3 → execsql2-2.12.5}/docs/api/index.md +0 -0
  52. {execsql2-2.12.3 → execsql2-2.12.5}/docs/api/metacommands.md +0 -0
  53. {execsql2-2.12.3 → execsql2-2.12.5}/docs/dev/adding_db_adapters.md +0 -0
  54. {execsql2-2.12.3 → execsql2-2.12.5}/docs/dev/adding_exporters.md +0 -0
  55. {execsql2-2.12.3 → execsql2-2.12.5}/docs/dev/adding_importers.md +0 -0
  56. {execsql2-2.12.3 → execsql2-2.12.5}/docs/dev/adding_metacommands.md +0 -0
  57. {execsql2-2.12.3 → execsql2-2.12.5}/docs/dev/architecture.md +0 -0
  58. {execsql2-2.12.3 → execsql2-2.12.5}/docs/getting-started/requirements.md +0 -0
  59. {execsql2-2.12.3 → execsql2-2.12.5}/docs/getting-started/syntax.md +0 -0
  60. {execsql2-2.12.3 → execsql2-2.12.5}/docs/guides/debugging.md +0 -0
  61. {execsql2-2.12.3 → execsql2-2.12.5}/docs/guides/documentation.md +0 -0
  62. {execsql2-2.12.3 → execsql2-2.12.5}/docs/guides/encoding.md +0 -0
  63. {execsql2-2.12.3 → execsql2-2.12.5}/docs/guides/examples.md +0 -0
  64. {execsql2-2.12.3 → execsql2-2.12.5}/docs/guides/formatter.md +0 -0
  65. {execsql2-2.12.3 → execsql2-2.12.5}/docs/guides/logging.md +0 -0
  66. {execsql2-2.12.3 → execsql2-2.12.5}/docs/guides/sql_syntax.md +0 -0
  67. {execsql2-2.12.3 → execsql2-2.12.5}/docs/guides/usage.md +0 -0
  68. {execsql2-2.12.3 → execsql2-2.12.5}/docs/guides/using_scripts.md +0 -0
  69. {execsql2-2.12.3 → execsql2-2.12.5}/docs/images/Compare_planets.png +0 -0
  70. {execsql2-2.12.3 → execsql2-2.12.5}/docs/images/actions.png +0 -0
  71. {execsql2-2.12.3 → execsql2-2.12.5}/docs/images/actions2.png +0 -0
  72. {execsql2-2.12.3 → execsql2-2.12.5}/docs/images/checkboxes.png +0 -0
  73. {execsql2-2.12.3 → execsql2-2.12.5}/docs/images/connect.b64 +0 -0
  74. {execsql2-2.12.3 → execsql2-2.12.5}/docs/images/connect.png +0 -0
  75. {execsql2-2.12.3 → execsql2-2.12.5}/docs/images/create_conf.png +0 -0
  76. {execsql2-2.12.3 → execsql2-2.12.5}/docs/images/data_error1_screenshot.jpg +0 -0
  77. {execsql2-2.12.3 → execsql2-2.12.5}/docs/images/entry_form.png +0 -0
  78. {execsql2-2.12.3 → execsql2-2.12.5}/docs/images/execsql_console.png +0 -0
  79. {execsql2-2.12.3 → execsql2-2.12.5}/docs/images/execsql_logo_01.png +0 -0
  80. {execsql2-2.12.3 → execsql2-2.12.5}/docs/images/fatals.png +0 -0
  81. {execsql2-2.12.3 → execsql2-2.12.5}/docs/images/logo_small.png +0 -0
  82. {execsql2-2.12.3 → execsql2-2.12.5}/docs/images/pause_terminal.png +0 -0
  83. {execsql2-2.12.3 → execsql2-2.12.5}/docs/images/pause_terminal_sm.b64 +0 -0
  84. {execsql2-2.12.3 → execsql2-2.12.5}/docs/images/pause_terminal_sm.png +0 -0
  85. {execsql2-2.12.3 → execsql2-2.12.5}/docs/images/prompt_compare.png +0 -0
  86. {execsql2-2.12.3 → execsql2-2.12.5}/docs/images/set_build_commands.jpg +0 -0
  87. {execsql2-2.12.3 → execsql2-2.12.5}/docs/images/unit_conversions.b64 +0 -0
  88. {execsql2-2.12.3 → execsql2-2.12.5}/docs/images/unit_conversions_029.png +0 -0
  89. {execsql2-2.12.3 → execsql2-2.12.5}/docs/images/unmatched.png +0 -0
  90. {execsql2-2.12.3 → execsql2-2.12.5}/docs/images/vim_execsql_highlight.png +0 -0
  91. {execsql2-2.12.3 → execsql2-2.12.5}/docs/index.md +0 -0
  92. {execsql2-2.12.3 → execsql2-2.12.5}/docs/reference/configuration.md +0 -0
  93. {execsql2-2.12.3 → execsql2-2.12.5}/docs/reference/security.md +0 -0
  94. {execsql2-2.12.3 → execsql2-2.12.5}/docs/reference/substitution_vars.md +0 -0
  95. {execsql2-2.12.3 → execsql2-2.12.5}/extras/vscode-execsql/README.md +0 -0
  96. {execsql2-2.12.3 → execsql2-2.12.5}/extras/vscode-execsql/package.json +0 -0
  97. {execsql2-2.12.3 → execsql2-2.12.5}/justfile +0 -0
  98. {execsql2-2.12.3 → execsql2-2.12.5}/scripts/generate_vscode_grammar.py +0 -0
  99. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/__init__.py +0 -0
  100. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/__main__.py +0 -0
  101. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/cli/__init__.py +0 -0
  102. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/cli/dsn.py +0 -0
  103. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/cli/help.py +0 -0
  104. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/cli/lint.py +0 -0
  105. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/cli/run.py +0 -0
  106. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/config.py +0 -0
  107. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/constants.py +0 -0
  108. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/db/__init__.py +0 -0
  109. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/db/access.py +0 -0
  110. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/db/base.py +0 -0
  111. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/db/dsn.py +0 -0
  112. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/db/duckdb.py +0 -0
  113. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/db/factory.py +0 -0
  114. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/db/firebird.py +0 -0
  115. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/db/mysql.py +0 -0
  116. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/db/oracle.py +0 -0
  117. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/db/postgres.py +0 -0
  118. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/db/sqlite.py +0 -0
  119. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/db/sqlserver.py +0 -0
  120. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/debug/__init__.py +0 -0
  121. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/debug/repl.py +0 -0
  122. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/exceptions.py +0 -0
  123. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/exporters/__init__.py +0 -0
  124. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/exporters/base.py +0 -0
  125. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/exporters/delimited.py +0 -0
  126. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/exporters/duckdb.py +0 -0
  127. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/exporters/feather.py +0 -0
  128. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/exporters/html.py +0 -0
  129. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/exporters/json.py +0 -0
  130. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/exporters/latex.py +0 -0
  131. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/exporters/markdown.py +0 -0
  132. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/exporters/ods.py +0 -0
  133. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/exporters/parquet.py +0 -0
  134. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/exporters/pretty.py +0 -0
  135. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/exporters/protocol.py +0 -0
  136. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/exporters/raw.py +0 -0
  137. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/exporters/sqlite.py +0 -0
  138. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/exporters/templates.py +0 -0
  139. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/exporters/values.py +0 -0
  140. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/exporters/xls.py +0 -0
  141. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/exporters/xlsx.py +0 -0
  142. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/exporters/xml.py +0 -0
  143. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/exporters/yaml.py +0 -0
  144. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/exporters/zip.py +0 -0
  145. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/format.py +0 -0
  146. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/gui/__init__.py +0 -0
  147. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/gui/base.py +0 -0
  148. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/gui/console.py +0 -0
  149. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/gui/desktop.py +0 -0
  150. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/gui/tui.py +0 -0
  151. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/importers/__init__.py +0 -0
  152. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/importers/base.py +0 -0
  153. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/importers/csv.py +0 -0
  154. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/importers/feather.py +0 -0
  155. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/importers/ods.py +0 -0
  156. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/importers/xls.py +0 -0
  157. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/metacommands/conditions.py +0 -0
  158. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/metacommands/connect.py +0 -0
  159. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/metacommands/control.py +0 -0
  160. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/metacommands/data.py +0 -0
  161. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/metacommands/debug.py +0 -0
  162. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/metacommands/io.py +0 -0
  163. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/metacommands/io_export.py +0 -0
  164. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/metacommands/io_fileops.py +0 -0
  165. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/metacommands/io_import.py +0 -0
  166. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/metacommands/io_write.py +0 -0
  167. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/metacommands/prompt.py +0 -0
  168. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/metacommands/script_ext.py +0 -0
  169. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/metacommands/system.py +0 -0
  170. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/models.py +0 -0
  171. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/parser.py +0 -0
  172. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/py.typed +0 -0
  173. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/script/__init__.py +0 -0
  174. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/script/control.py +0 -0
  175. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/script/engine.py +0 -0
  176. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/script/variables.py +0 -0
  177. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/state.py +0 -0
  178. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/types.py +0 -0
  179. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/utils/__init__.py +0 -0
  180. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/utils/auth.py +0 -0
  181. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/utils/crypto.py +0 -0
  182. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/utils/datetime.py +0 -0
  183. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/utils/errors.py +0 -0
  184. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/utils/fileio.py +0 -0
  185. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/utils/gui.py +0 -0
  186. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/utils/mail.py +0 -0
  187. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/utils/numeric.py +0 -0
  188. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/utils/regex.py +0 -0
  189. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/utils/strings.py +0 -0
  190. {execsql2-2.12.3 → execsql2-2.12.5}/src/execsql/utils/timer.py +0 -0
  191. {execsql2-2.12.3 → execsql2-2.12.5}/templates/README.md +0 -0
  192. {execsql2-2.12.3 → execsql2-2.12.5}/templates/config_settings.sqlite +0 -0
  193. {execsql2-2.12.3 → execsql2-2.12.5}/templates/example_config_prompt.sql +0 -0
  194. {execsql2-2.12.3 → execsql2-2.12.5}/templates/execsql.conf +0 -0
  195. {execsql2-2.12.3 → execsql2-2.12.5}/templates/make_config_db.sql +0 -0
  196. {execsql2-2.12.3 → execsql2-2.12.5}/templates/md_compare.sql +0 -0
  197. {execsql2-2.12.3 → execsql2-2.12.5}/templates/md_glossary.sql +0 -0
  198. {execsql2-2.12.3 → execsql2-2.12.5}/templates/md_upsert.sql +0 -0
  199. {execsql2-2.12.3 → execsql2-2.12.5}/templates/pg_compare.sql +0 -0
  200. {execsql2-2.12.3 → execsql2-2.12.5}/templates/pg_glossary.sql +0 -0
  201. {execsql2-2.12.3 → execsql2-2.12.5}/templates/pg_upsert.sql +0 -0
  202. {execsql2-2.12.3 → execsql2-2.12.5}/templates/script_template.sql +0 -0
  203. {execsql2-2.12.3 → execsql2-2.12.5}/templates/ss_compare.sql +0 -0
  204. {execsql2-2.12.3 → execsql2-2.12.5}/templates/ss_glossary.sql +0 -0
  205. {execsql2-2.12.3 → execsql2-2.12.5}/templates/ss_upsert.sql +0 -0
  206. {execsql2-2.12.3 → execsql2-2.12.5}/tests/__init__.py +0 -0
  207. {execsql2-2.12.3 → execsql2-2.12.5}/tests/cli/__init__.py +0 -0
  208. {execsql2-2.12.3 → execsql2-2.12.5}/tests/cli/test_cli_e2e.py +0 -0
  209. {execsql2-2.12.3 → execsql2-2.12.5}/tests/cli/test_cli_run.py +0 -0
  210. {execsql2-2.12.3 → execsql2-2.12.5}/tests/cli/test_lint.py +0 -0
  211. {execsql2-2.12.3 → execsql2-2.12.5}/tests/cli/test_ping.py +0 -0
  212. {execsql2-2.12.3 → execsql2-2.12.5}/tests/cli/test_profile.py +0 -0
  213. {execsql2-2.12.3 → execsql2-2.12.5}/tests/conftest.py +0 -0
  214. {execsql2-2.12.3 → execsql2-2.12.5}/tests/db/__init__.py +0 -0
  215. {execsql2-2.12.3 → execsql2-2.12.5}/tests/db/test_base.py +0 -0
  216. {execsql2-2.12.3 → execsql2-2.12.5}/tests/db/test_duckdb.py +0 -0
  217. {execsql2-2.12.3 → execsql2-2.12.5}/tests/db/test_factory.py +0 -0
  218. {execsql2-2.12.3 → execsql2-2.12.5}/tests/db/test_postgres.py +0 -0
  219. {execsql2-2.12.3 → execsql2-2.12.5}/tests/db/test_sqlite.py +0 -0
  220. {execsql2-2.12.3 → execsql2-2.12.5}/tests/db/test_sqlite_extra.py +0 -0
  221. {execsql2-2.12.3 → execsql2-2.12.5}/tests/exporters/__init__.py +0 -0
  222. {execsql2-2.12.3 → execsql2-2.12.5}/tests/exporters/test_base.py +0 -0
  223. {execsql2-2.12.3 → execsql2-2.12.5}/tests/exporters/test_db.py +0 -0
  224. {execsql2-2.12.3 → execsql2-2.12.5}/tests/exporters/test_delimited.py +0 -0
  225. {execsql2-2.12.3 → execsql2-2.12.5}/tests/exporters/test_duckdb_exporter.py +0 -0
  226. {execsql2-2.12.3 → execsql2-2.12.5}/tests/exporters/test_exporters.py +0 -0
  227. {execsql2-2.12.3 → execsql2-2.12.5}/tests/exporters/test_feather.py +0 -0
  228. {execsql2-2.12.3 → execsql2-2.12.5}/tests/exporters/test_html_latex.py +0 -0
  229. {execsql2-2.12.3 → execsql2-2.12.5}/tests/exporters/test_json.py +0 -0
  230. {execsql2-2.12.3 → execsql2-2.12.5}/tests/exporters/test_markdown.py +0 -0
  231. {execsql2-2.12.3 → execsql2-2.12.5}/tests/exporters/test_ods.py +0 -0
  232. {execsql2-2.12.3 → execsql2-2.12.5}/tests/exporters/test_parquet.py +0 -0
  233. {execsql2-2.12.3 → execsql2-2.12.5}/tests/exporters/test_sqlite_exporter.py +0 -0
  234. {execsql2-2.12.3 → execsql2-2.12.5}/tests/exporters/test_templates.py +0 -0
  235. {execsql2-2.12.3 → execsql2-2.12.5}/tests/exporters/test_xls_xlsx.py +0 -0
  236. {execsql2-2.12.3 → execsql2-2.12.5}/tests/exporters/test_xlsx.py +0 -0
  237. {execsql2-2.12.3 → execsql2-2.12.5}/tests/exporters/test_xml.py +0 -0
  238. {execsql2-2.12.3 → execsql2-2.12.5}/tests/exporters/test_yaml.py +0 -0
  239. {execsql2-2.12.3 → execsql2-2.12.5}/tests/exporters/test_zip.py +0 -0
  240. {execsql2-2.12.3 → execsql2-2.12.5}/tests/gui/__init__.py +0 -0
  241. {execsql2-2.12.3 → execsql2-2.12.5}/tests/gui/test_backends.py +0 -0
  242. {execsql2-2.12.3 → execsql2-2.12.5}/tests/importers/__init__.py +0 -0
  243. {execsql2-2.12.3 → execsql2-2.12.5}/tests/importers/test_csv_importer.py +0 -0
  244. {execsql2-2.12.3 → execsql2-2.12.5}/tests/importers/test_feather_importer.py +0 -0
  245. {execsql2-2.12.3 → execsql2-2.12.5}/tests/importers/test_ods_importer.py +0 -0
  246. {execsql2-2.12.3 → execsql2-2.12.5}/tests/importers/test_xls_importer.py +0 -0
  247. {execsql2-2.12.3 → execsql2-2.12.5}/tests/integration/__init__.py +0 -0
  248. {execsql2-2.12.3 → execsql2-2.12.5}/tests/integration/conftest.py +0 -0
  249. {execsql2-2.12.3 → execsql2-2.12.5}/tests/integration/test_dsn.py +0 -0
  250. {execsql2-2.12.3 → execsql2-2.12.5}/tests/integration/test_duckdb.py +0 -0
  251. {execsql2-2.12.3 → execsql2-2.12.5}/tests/integration/test_mysql.py +0 -0
  252. {execsql2-2.12.3 → execsql2-2.12.5}/tests/integration/test_postgres.py +0 -0
  253. {execsql2-2.12.3 → execsql2-2.12.5}/tests/integration/test_sqlite.py +0 -0
  254. {execsql2-2.12.3 → execsql2-2.12.5}/tests/metacommands/__init__.py +0 -0
  255. {execsql2-2.12.3 → execsql2-2.12.5}/tests/metacommands/test_assert.py +0 -0
  256. {execsql2-2.12.3 → execsql2-2.12.5}/tests/metacommands/test_breakpoint.py +0 -0
  257. {execsql2-2.12.3 → execsql2-2.12.5}/tests/metacommands/test_connect.py +0 -0
  258. {execsql2-2.12.3 → execsql2-2.12.5}/tests/metacommands/test_io_export.py +0 -0
  259. {execsql2-2.12.3 → execsql2-2.12.5}/tests/metacommands/test_io_import.py +0 -0
  260. {execsql2-2.12.3 → execsql2-2.12.5}/tests/metacommands/test_metacommands.py +0 -0
  261. {execsql2-2.12.3 → execsql2-2.12.5}/tests/metacommands/test_metacommands_connect.py +0 -0
  262. {execsql2-2.12.3 → execsql2-2.12.5}/tests/metacommands/test_metacommands_data.py +0 -0
  263. {execsql2-2.12.3 → execsql2-2.12.5}/tests/metacommands/test_metacommands_extended.py +0 -0
  264. {execsql2-2.12.3 → execsql2-2.12.5}/tests/metacommands/test_metacommands_fileops_extra.py +0 -0
  265. {execsql2-2.12.3 → execsql2-2.12.5}/tests/metacommands/test_metacommands_io.py +0 -0
  266. {execsql2-2.12.3 → execsql2-2.12.5}/tests/metacommands/test_metacommands_io_write_extra.py +0 -0
  267. {execsql2-2.12.3 → execsql2-2.12.5}/tests/metacommands/test_metacommands_script_ext.py +0 -0
  268. {execsql2-2.12.3 → execsql2-2.12.5}/tests/metacommands/test_metacommands_system.py +0 -0
  269. {execsql2-2.12.3 → execsql2-2.12.5}/tests/metacommands/test_metacommands_system_extra.py +0 -0
  270. {execsql2-2.12.3 → execsql2-2.12.5}/tests/metacommands/test_row_count.py +0 -0
  271. {execsql2-2.12.3 → execsql2-2.12.5}/tests/test_config.py +0 -0
  272. {execsql2-2.12.3 → execsql2-2.12.5}/tests/test_config_data.py +0 -0
  273. {execsql2-2.12.3 → execsql2-2.12.5}/tests/test_constants.py +0 -0
  274. {execsql2-2.12.3 → execsql2-2.12.5}/tests/test_engine.py +0 -0
  275. {execsql2-2.12.3 → execsql2-2.12.5}/tests/test_error_messages.py +0 -0
  276. {execsql2-2.12.3 → execsql2-2.12.5}/tests/test_exceptions.py +0 -0
  277. {execsql2-2.12.3 → execsql2-2.12.5}/tests/test_format.py +0 -0
  278. {execsql2-2.12.3 → execsql2-2.12.5}/tests/test_mail.py +0 -0
  279. {execsql2-2.12.3 → execsql2-2.12.5}/tests/test_models.py +0 -0
  280. {execsql2-2.12.3 → execsql2-2.12.5}/tests/test_package.py +0 -0
  281. {execsql2-2.12.3 → execsql2-2.12.5}/tests/test_parser.py +0 -0
  282. {execsql2-2.12.3 → execsql2-2.12.5}/tests/test_registry.py +0 -0
  283. {execsql2-2.12.3 → execsql2-2.12.5}/tests/test_script.py +0 -0
  284. {execsql2-2.12.3 → execsql2-2.12.5}/tests/test_state.py +0 -0
  285. {execsql2-2.12.3 → execsql2-2.12.5}/tests/test_types.py +0 -0
  286. {execsql2-2.12.3 → execsql2-2.12.5}/tests/utils/__init__.py +0 -0
  287. {execsql2-2.12.3 → execsql2-2.12.5}/tests/utils/test_auth.py +0 -0
  288. {execsql2-2.12.3 → execsql2-2.12.5}/tests/utils/test_auth_extra.py +0 -0
  289. {execsql2-2.12.3 → execsql2-2.12.5}/tests/utils/test_crypto.py +0 -0
  290. {execsql2-2.12.3 → execsql2-2.12.5}/tests/utils/test_datetime.py +0 -0
  291. {execsql2-2.12.3 → execsql2-2.12.5}/tests/utils/test_errors.py +0 -0
  292. {execsql2-2.12.3 → execsql2-2.12.5}/tests/utils/test_errors_extra.py +0 -0
  293. {execsql2-2.12.3 → execsql2-2.12.5}/tests/utils/test_fileio.py +0 -0
  294. {execsql2-2.12.3 → execsql2-2.12.5}/tests/utils/test_fileio_extra.py +0 -0
  295. {execsql2-2.12.3 → execsql2-2.12.5}/tests/utils/test_numeric.py +0 -0
  296. {execsql2-2.12.3 → execsql2-2.12.5}/tests/utils/test_regex.py +0 -0
  297. {execsql2-2.12.3 → execsql2-2.12.5}/tests/utils/test_strings.py +0 -0
  298. {execsql2-2.12.3 → execsql2-2.12.5}/tests/utils/test_timer.py +0 -0
  299. {execsql2-2.12.3 → execsql2-2.12.5}/tests/utils/test_timer_extra.py +0 -0
  300. {execsql2-2.12.3 → execsql2-2.12.5}/zensical.toml +0 -0
@@ -13,6 +13,18 @@ ______________________________________________________________________
13
13
 
14
14
  ______________________________________________________________________
15
15
 
16
+ ## [2.12.5] - 2026-04-03
17
+
18
+ ______________________________________________________________________
19
+
20
+ ## [2.12.4] - 2026-04-03
21
+
22
+ ### Added
23
+
24
+ - 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.
25
+
26
+ ______________________________________________________________________
27
+
16
28
  ## [2.12.3] - 2026-04-02
17
29
 
18
30
  ### Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: execsql2
3
- Version: 2.12.3
3
+ Version: 2.12.5
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.17.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.17.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,122 @@ 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
+
2103
+ ### Substitution variables
2104
+
2105
+ Set after every `PG_UPSERT` execution:
2106
+
2107
+ | Variable | Type | Description |
2108
+ |----------|------|-------------|
2109
+ | `$PG_UPSERT_QA_PASSED` | TRUE/FALSE | Did all QA checks pass? |
2110
+ | `$PG_UPSERT_ROWS_UPDATED` | integer | Total rows updated across all tables |
2111
+ | `$PG_UPSERT_ROWS_INSERTED` | integer | Total rows inserted across all tables |
2112
+ | `$PG_UPSERT_COMMITTED` | TRUE/FALSE | Were changes committed? |
2113
+ | `$PG_UPSERT_STAGING_SCHEMA` | string | Staging schema name used |
2114
+ | `$PG_UPSERT_BASE_SCHEMA` | string | Base schema name used |
2115
+ | `$PG_UPSERT_TABLES` | string | Comma-separated list of table names processed |
2116
+ | `$PG_UPSERT_METHOD` | string | Upsert method used |
2117
+ | `$PG_UPSERT_DURATION` | float | Elapsed time in seconds |
2118
+ | `$PG_UPSERT_STARTED_AT` | string | ISO 8601 start timestamp |
2119
+ | `$PG_UPSERT_FINISHED_AT` | string | ISO 8601 end timestamp |
2120
+ | `$PG_UPSERT_RESULT_JSON` | JSON string | Full result for detailed inspection |
2121
+
2122
+ !!! note "Using `$PG_UPSERT_RESULT_JSON` with WRITE"
2123
+ 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:
2124
+
2125
+ ```sql
2126
+ -- !x! WRITE ~!!$PG_UPSERT_RESULT_JSON!!~
2127
+ -- !x! WRITE `!!$PG_UPSERT_RESULT_JSON!!`
2128
+ ```
2129
+
2130
+ ### Temporary objects
2131
+
2132
+ 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.
2133
+
2134
+ 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).
2135
+
2136
+ ### Examples
2137
+
2138
+ ```sql
2139
+ -- Full pipeline: QA + upsert + commit
2140
+ -- !x! PG_UPSERT FROM staging TO public TABLES books, authors COMMIT
2141
+
2142
+ -- Dry run (no commit) with update-only method
2143
+ -- !x! PG_UPSERT FROM staging TO public TABLES books METHOD update
2144
+
2145
+ -- QA only with interactive review
2146
+ -- !x! PG_UPSERT QA FROM staging TO public TABLES books, authors INTERACTIVE
2147
+
2148
+ -- Schema check only
2149
+ -- !x! PG_UPSERT CHECK FROM staging TO public TABLES books, authors
2150
+
2151
+ -- Log pg-upsert output to a file
2152
+ -- !x! PG_UPSERT FROM staging TO public TABLES books, authors LOGFILE "upsert.log" COMMIT
2153
+
2154
+ -- Use substitution variables after upsert
2155
+ -- !x! PG_UPSERT FROM staging TO public TABLES books COMMIT
2156
+ -- !x! WRITE "Updated !!$PG_UPSERT_ROWS_UPDATED!! rows, inserted !!$PG_UPSERT_ROWS_INSERTED!! in !!$PG_UPSERT_DURATION!! seconds"
2157
+
2158
+ -- Inspect pg-upsert's control table after a run
2159
+ -- !x! PG_UPSERT FROM staging TO public TABLES books, authors, genres COMMIT
2160
+ -- !x! EXPORT ups_control TO stdout AS txt
2161
+
2162
+ -- Export QA results to a CSV file for review
2163
+ -- !x! PG_UPSERT QA FROM staging TO public TABLES books, authors
2164
+ -- !x! EXPORT ups_control TO "qa_results.csv" AS csv
2165
+
2166
+ -- Check for type mismatches between schemas and display them
2167
+ -- !x! PG_UPSERT CHECK FROM staging TO public TABLES books, authors
2168
+ -- !x! EXPORT ups_type_mismatches TO stdout AS txt
2169
+
2170
+ -- Conditional logic based on QA results
2171
+ -- !x! PG_UPSERT FROM staging TO public TABLES books, authors
2172
+ -- !x! IF !!$PG_UPSERT_QA_PASSED!! = TRUE
2173
+ -- !x! WRITE "All QA checks passed — proceeding with commit"
2174
+ -- !x! ELSE
2175
+ -- !x! WRITE "QA failed — inspect ups_control for details"
2176
+ -- !x! EXPORT ups_control TO stdout AS txt
2177
+ -- !x! ENDIF
2178
+
2179
+ -- Write the full JSON result using tilde delimiters (JSON contains " and [])
2180
+ -- !x! PG_UPSERT FROM staging TO public TABLES books COMMIT
2181
+ -- !x! WRITE ~!!$PG_UPSERT_RESULT_JSON!!~
2182
+ ```
2183
+
2184
+
2069
2185
  ## PG_VACUUM
2070
2186
 
2071
2187
  ```
@@ -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.5"
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.17.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.5"
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)