execsql2 2.15.7__tar.gz → 2.15.8__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 (296) hide show
  1. {execsql2-2.15.7 → execsql2-2.15.8}/CHANGELOG.md +19 -0
  2. {execsql2-2.15.7 → execsql2-2.15.8}/PKG-INFO +3 -3
  3. {execsql2-2.15.7 → execsql2-2.15.8}/docs/about/divergence.md +8 -8
  4. {execsql2-2.15.7 → execsql2-2.15.8}/pyproject.toml +3 -3
  5. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/metacommands/upsert.py +20 -10
  6. {execsql2-2.15.7 → execsql2-2.15.8}/tests/metacommands/test_pg_upsert.py +46 -23
  7. {execsql2-2.15.7 → execsql2-2.15.8}/uv.lock +5 -5
  8. {execsql2-2.15.7 → execsql2-2.15.8}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  9. {execsql2-2.15.7 → execsql2-2.15.8}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  10. {execsql2-2.15.7 → execsql2-2.15.8}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  11. {execsql2-2.15.7 → execsql2-2.15.8}/.github/workflows/ci-cd.yml +0 -0
  12. {execsql2-2.15.7 → execsql2-2.15.8}/.gitignore +0 -0
  13. {execsql2-2.15.7 → execsql2-2.15.8}/.pre-commit-config.yaml +0 -0
  14. {execsql2-2.15.7 → execsql2-2.15.8}/.pre-commit-hooks.yaml +0 -0
  15. {execsql2-2.15.7 → execsql2-2.15.8}/.python-version +0 -0
  16. {execsql2-2.15.7 → execsql2-2.15.8}/.readthedocs.yaml +0 -0
  17. {execsql2-2.15.7 → execsql2-2.15.8}/CONTRIBUTING.md +0 -0
  18. {execsql2-2.15.7 → execsql2-2.15.8}/LICENSE.txt +0 -0
  19. {execsql2-2.15.7 → execsql2-2.15.8}/NOTICE +0 -0
  20. {execsql2-2.15.7 → execsql2-2.15.8}/README.md +0 -0
  21. {execsql2-2.15.7 → execsql2-2.15.8}/SECURITY.md +0 -0
  22. {execsql2-2.15.7 → execsql2-2.15.8}/docs/about/contributors.md +0 -0
  23. {execsql2-2.15.7 → execsql2-2.15.8}/docs/about/copyright.md +0 -0
  24. {execsql2-2.15.7 → execsql2-2.15.8}/docs/api/cli.md +0 -0
  25. {execsql2-2.15.7 → execsql2-2.15.8}/docs/api/db.md +0 -0
  26. {execsql2-2.15.7 → execsql2-2.15.8}/docs/api/exporters.md +0 -0
  27. {execsql2-2.15.7 → execsql2-2.15.8}/docs/api/importers.md +0 -0
  28. {execsql2-2.15.7 → execsql2-2.15.8}/docs/api/index.md +0 -0
  29. {execsql2-2.15.7 → execsql2-2.15.8}/docs/api/metacommands.md +0 -0
  30. {execsql2-2.15.7 → execsql2-2.15.8}/docs/dev/adding_db_adapters.md +0 -0
  31. {execsql2-2.15.7 → execsql2-2.15.8}/docs/dev/adding_exporters.md +0 -0
  32. {execsql2-2.15.7 → execsql2-2.15.8}/docs/dev/adding_importers.md +0 -0
  33. {execsql2-2.15.7 → execsql2-2.15.8}/docs/dev/adding_metacommands.md +0 -0
  34. {execsql2-2.15.7 → execsql2-2.15.8}/docs/dev/architecture.md +0 -0
  35. {execsql2-2.15.7 → execsql2-2.15.8}/docs/getting-started/installation.md +0 -0
  36. {execsql2-2.15.7 → execsql2-2.15.8}/docs/getting-started/requirements.md +0 -0
  37. {execsql2-2.15.7 → execsql2-2.15.8}/docs/getting-started/syntax.md +0 -0
  38. {execsql2-2.15.7 → execsql2-2.15.8}/docs/guides/debugging.md +0 -0
  39. {execsql2-2.15.7 → execsql2-2.15.8}/docs/guides/documentation.md +0 -0
  40. {execsql2-2.15.7 → execsql2-2.15.8}/docs/guides/encoding.md +0 -0
  41. {execsql2-2.15.7 → execsql2-2.15.8}/docs/guides/examples.md +0 -0
  42. {execsql2-2.15.7 → execsql2-2.15.8}/docs/guides/formatter.md +0 -0
  43. {execsql2-2.15.7 → execsql2-2.15.8}/docs/guides/logging.md +0 -0
  44. {execsql2-2.15.7 → execsql2-2.15.8}/docs/guides/sql_syntax.md +0 -0
  45. {execsql2-2.15.7 → execsql2-2.15.8}/docs/guides/usage.md +0 -0
  46. {execsql2-2.15.7 → execsql2-2.15.8}/docs/guides/using_scripts.md +0 -0
  47. {execsql2-2.15.7 → execsql2-2.15.8}/docs/images/Compare_planets.png +0 -0
  48. {execsql2-2.15.7 → execsql2-2.15.8}/docs/images/actions.png +0 -0
  49. {execsql2-2.15.7 → execsql2-2.15.8}/docs/images/actions2.png +0 -0
  50. {execsql2-2.15.7 → execsql2-2.15.8}/docs/images/checkboxes.png +0 -0
  51. {execsql2-2.15.7 → execsql2-2.15.8}/docs/images/connect.b64 +0 -0
  52. {execsql2-2.15.7 → execsql2-2.15.8}/docs/images/connect.png +0 -0
  53. {execsql2-2.15.7 → execsql2-2.15.8}/docs/images/create_conf.png +0 -0
  54. {execsql2-2.15.7 → execsql2-2.15.8}/docs/images/data_error1_screenshot.jpg +0 -0
  55. {execsql2-2.15.7 → execsql2-2.15.8}/docs/images/entry_form.png +0 -0
  56. {execsql2-2.15.7 → execsql2-2.15.8}/docs/images/execsql_console.png +0 -0
  57. {execsql2-2.15.7 → execsql2-2.15.8}/docs/images/execsql_logo_01.png +0 -0
  58. {execsql2-2.15.7 → execsql2-2.15.8}/docs/images/fatals.png +0 -0
  59. {execsql2-2.15.7 → execsql2-2.15.8}/docs/images/logo_small.png +0 -0
  60. {execsql2-2.15.7 → execsql2-2.15.8}/docs/images/pause_terminal.png +0 -0
  61. {execsql2-2.15.7 → execsql2-2.15.8}/docs/images/pause_terminal_sm.b64 +0 -0
  62. {execsql2-2.15.7 → execsql2-2.15.8}/docs/images/pause_terminal_sm.png +0 -0
  63. {execsql2-2.15.7 → execsql2-2.15.8}/docs/images/prompt_compare.png +0 -0
  64. {execsql2-2.15.7 → execsql2-2.15.8}/docs/images/set_build_commands.jpg +0 -0
  65. {execsql2-2.15.7 → execsql2-2.15.8}/docs/images/unit_conversions.b64 +0 -0
  66. {execsql2-2.15.7 → execsql2-2.15.8}/docs/images/unit_conversions_029.png +0 -0
  67. {execsql2-2.15.7 → execsql2-2.15.8}/docs/images/unmatched.png +0 -0
  68. {execsql2-2.15.7 → execsql2-2.15.8}/docs/images/vim_execsql_highlight.png +0 -0
  69. {execsql2-2.15.7 → execsql2-2.15.8}/docs/index.md +0 -0
  70. {execsql2-2.15.7 → execsql2-2.15.8}/docs/reference/configuration.md +0 -0
  71. {execsql2-2.15.7 → execsql2-2.15.8}/docs/reference/metacommands.md +0 -0
  72. {execsql2-2.15.7 → execsql2-2.15.8}/docs/reference/security.md +0 -0
  73. {execsql2-2.15.7 → execsql2-2.15.8}/docs/reference/substitution_vars.md +0 -0
  74. {execsql2-2.15.7 → execsql2-2.15.8}/extras/vscode-execsql/README.md +0 -0
  75. {execsql2-2.15.7 → execsql2-2.15.8}/extras/vscode-execsql/package.json +0 -0
  76. {execsql2-2.15.7 → execsql2-2.15.8}/extras/vscode-execsql/syntaxes/execsql.tmLanguage.json +0 -0
  77. {execsql2-2.15.7 → execsql2-2.15.8}/justfile +0 -0
  78. {execsql2-2.15.7 → execsql2-2.15.8}/scripts/generate_vscode_grammar.py +0 -0
  79. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/__init__.py +0 -0
  80. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/__main__.py +0 -0
  81. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/cli/__init__.py +0 -0
  82. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/cli/dsn.py +0 -0
  83. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/cli/help.py +0 -0
  84. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/cli/lint.py +0 -0
  85. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/cli/run.py +0 -0
  86. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/config.py +0 -0
  87. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/db/__init__.py +0 -0
  88. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/db/access.py +0 -0
  89. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/db/base.py +0 -0
  90. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/db/dsn.py +0 -0
  91. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/db/duckdb.py +0 -0
  92. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/db/factory.py +0 -0
  93. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/db/firebird.py +0 -0
  94. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/db/mysql.py +0 -0
  95. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/db/oracle.py +0 -0
  96. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/db/postgres.py +0 -0
  97. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/db/sqlite.py +0 -0
  98. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/db/sqlserver.py +0 -0
  99. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/debug/__init__.py +0 -0
  100. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/debug/repl.py +0 -0
  101. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/exceptions.py +0 -0
  102. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/exporters/__init__.py +0 -0
  103. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/exporters/base.py +0 -0
  104. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/exporters/delimited.py +0 -0
  105. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/exporters/duckdb.py +0 -0
  106. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/exporters/feather.py +0 -0
  107. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/exporters/html.py +0 -0
  108. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/exporters/json.py +0 -0
  109. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/exporters/latex.py +0 -0
  110. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/exporters/markdown.py +0 -0
  111. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/exporters/ods.py +0 -0
  112. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/exporters/parquet.py +0 -0
  113. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/exporters/pretty.py +0 -0
  114. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/exporters/protocol.py +0 -0
  115. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/exporters/raw.py +0 -0
  116. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/exporters/sqlite.py +0 -0
  117. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/exporters/templates.py +0 -0
  118. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/exporters/values.py +0 -0
  119. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/exporters/xls.py +0 -0
  120. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/exporters/xlsx.py +0 -0
  121. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/exporters/xml.py +0 -0
  122. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/exporters/yaml.py +0 -0
  123. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/exporters/zip.py +0 -0
  124. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/format.py +0 -0
  125. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/gui/__init__.py +0 -0
  126. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/gui/base.py +0 -0
  127. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/gui/console.py +0 -0
  128. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/gui/desktop.py +0 -0
  129. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/gui/tui.py +0 -0
  130. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/importers/__init__.py +0 -0
  131. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/importers/base.py +0 -0
  132. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/importers/csv.py +0 -0
  133. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/importers/feather.py +0 -0
  134. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/importers/json.py +0 -0
  135. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/importers/ods.py +0 -0
  136. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/importers/xls.py +0 -0
  137. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/metacommands/__init__.py +0 -0
  138. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/metacommands/conditions.py +0 -0
  139. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/metacommands/connect.py +0 -0
  140. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/metacommands/control.py +0 -0
  141. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/metacommands/data.py +0 -0
  142. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/metacommands/debug.py +0 -0
  143. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/metacommands/dispatch.py +0 -0
  144. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/metacommands/io.py +0 -0
  145. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/metacommands/io_export.py +0 -0
  146. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/metacommands/io_fileops.py +0 -0
  147. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/metacommands/io_import.py +0 -0
  148. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/metacommands/io_write.py +0 -0
  149. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/metacommands/prompt.py +0 -0
  150. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/metacommands/script_ext.py +0 -0
  151. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/metacommands/system.py +0 -0
  152. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/models.py +0 -0
  153. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/parser.py +0 -0
  154. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/py.typed +0 -0
  155. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/script/__init__.py +0 -0
  156. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/script/control.py +0 -0
  157. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/script/engine.py +0 -0
  158. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/script/variables.py +0 -0
  159. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/state.py +0 -0
  160. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/types.py +0 -0
  161. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/utils/__init__.py +0 -0
  162. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/utils/auth.py +0 -0
  163. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/utils/crypto.py +0 -0
  164. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/utils/datetime.py +0 -0
  165. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/utils/errors.py +0 -0
  166. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/utils/fileio.py +0 -0
  167. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/utils/gui.py +0 -0
  168. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/utils/mail.py +0 -0
  169. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/utils/numeric.py +0 -0
  170. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/utils/regex.py +0 -0
  171. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/utils/strings.py +0 -0
  172. {execsql2-2.15.7 → execsql2-2.15.8}/src/execsql/utils/timer.py +0 -0
  173. {execsql2-2.15.7 → execsql2-2.15.8}/templates/README.md +0 -0
  174. {execsql2-2.15.7 → execsql2-2.15.8}/templates/config_settings.sqlite +0 -0
  175. {execsql2-2.15.7 → execsql2-2.15.8}/templates/example_config_prompt.sql +0 -0
  176. {execsql2-2.15.7 → execsql2-2.15.8}/templates/execsql.conf +0 -0
  177. {execsql2-2.15.7 → execsql2-2.15.8}/templates/make_config_db.sql +0 -0
  178. {execsql2-2.15.7 → execsql2-2.15.8}/templates/md_compare.sql +0 -0
  179. {execsql2-2.15.7 → execsql2-2.15.8}/templates/md_glossary.sql +0 -0
  180. {execsql2-2.15.7 → execsql2-2.15.8}/templates/md_upsert.sql +0 -0
  181. {execsql2-2.15.7 → execsql2-2.15.8}/templates/pg_compare.sql +0 -0
  182. {execsql2-2.15.7 → execsql2-2.15.8}/templates/pg_glossary.sql +0 -0
  183. {execsql2-2.15.7 → execsql2-2.15.8}/templates/pg_upsert.sql +0 -0
  184. {execsql2-2.15.7 → execsql2-2.15.8}/templates/script_template.sql +0 -0
  185. {execsql2-2.15.7 → execsql2-2.15.8}/templates/ss_compare.sql +0 -0
  186. {execsql2-2.15.7 → execsql2-2.15.8}/templates/ss_glossary.sql +0 -0
  187. {execsql2-2.15.7 → execsql2-2.15.8}/templates/ss_upsert.sql +0 -0
  188. {execsql2-2.15.7 → execsql2-2.15.8}/tests/__init__.py +0 -0
  189. {execsql2-2.15.7 → execsql2-2.15.8}/tests/cli/__init__.py +0 -0
  190. {execsql2-2.15.7 → execsql2-2.15.8}/tests/cli/test_cli.py +0 -0
  191. {execsql2-2.15.7 → execsql2-2.15.8}/tests/cli/test_cli_e2e.py +0 -0
  192. {execsql2-2.15.7 → execsql2-2.15.8}/tests/cli/test_cli_run.py +0 -0
  193. {execsql2-2.15.7 → execsql2-2.15.8}/tests/cli/test_lint.py +0 -0
  194. {execsql2-2.15.7 → execsql2-2.15.8}/tests/cli/test_ping.py +0 -0
  195. {execsql2-2.15.7 → execsql2-2.15.8}/tests/cli/test_profile.py +0 -0
  196. {execsql2-2.15.7 → execsql2-2.15.8}/tests/conftest.py +0 -0
  197. {execsql2-2.15.7 → execsql2-2.15.8}/tests/db/__init__.py +0 -0
  198. {execsql2-2.15.7 → execsql2-2.15.8}/tests/db/test_base.py +0 -0
  199. {execsql2-2.15.7 → execsql2-2.15.8}/tests/db/test_duckdb.py +0 -0
  200. {execsql2-2.15.7 → execsql2-2.15.8}/tests/db/test_factory.py +0 -0
  201. {execsql2-2.15.7 → execsql2-2.15.8}/tests/db/test_postgres.py +0 -0
  202. {execsql2-2.15.7 → execsql2-2.15.8}/tests/db/test_sqlite.py +0 -0
  203. {execsql2-2.15.7 → execsql2-2.15.8}/tests/db/test_sqlite_extra.py +0 -0
  204. {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/__init__.py +0 -0
  205. {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_base.py +0 -0
  206. {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_db.py +0 -0
  207. {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_delimited.py +0 -0
  208. {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_duckdb_exporter.py +0 -0
  209. {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_exporters.py +0 -0
  210. {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_feather.py +0 -0
  211. {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_html_extended.py +0 -0
  212. {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_html_latex.py +0 -0
  213. {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_json.py +0 -0
  214. {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_json_extended.py +0 -0
  215. {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_latex_extended.py +0 -0
  216. {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_markdown.py +0 -0
  217. {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_ods.py +0 -0
  218. {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_parquet.py +0 -0
  219. {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_pretty_extended.py +0 -0
  220. {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_raw_extended.py +0 -0
  221. {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_sqlite_exporter.py +0 -0
  222. {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_templates.py +0 -0
  223. {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_templates_extended.py +0 -0
  224. {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_values_extended.py +0 -0
  225. {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_xls_xlsx.py +0 -0
  226. {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_xlsx.py +0 -0
  227. {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_xml.py +0 -0
  228. {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_yaml.py +0 -0
  229. {execsql2-2.15.7 → execsql2-2.15.8}/tests/exporters/test_zip.py +0 -0
  230. {execsql2-2.15.7 → execsql2-2.15.8}/tests/gui/__init__.py +0 -0
  231. {execsql2-2.15.7 → execsql2-2.15.8}/tests/gui/test_backends.py +0 -0
  232. {execsql2-2.15.7 → execsql2-2.15.8}/tests/gui/test_compare_stats.py +0 -0
  233. {execsql2-2.15.7 → execsql2-2.15.8}/tests/gui/test_compute_row_diffs.py +0 -0
  234. {execsql2-2.15.7 → execsql2-2.15.8}/tests/importers/__init__.py +0 -0
  235. {execsql2-2.15.7 → execsql2-2.15.8}/tests/importers/test_base_extended.py +0 -0
  236. {execsql2-2.15.7 → execsql2-2.15.8}/tests/importers/test_csv_edge_cases.py +0 -0
  237. {execsql2-2.15.7 → execsql2-2.15.8}/tests/importers/test_csv_importer.py +0 -0
  238. {execsql2-2.15.7 → execsql2-2.15.8}/tests/importers/test_feather_importer.py +0 -0
  239. {execsql2-2.15.7 → execsql2-2.15.8}/tests/importers/test_json_importer.py +0 -0
  240. {execsql2-2.15.7 → execsql2-2.15.8}/tests/importers/test_ods_importer.py +0 -0
  241. {execsql2-2.15.7 → execsql2-2.15.8}/tests/importers/test_xls_importer.py +0 -0
  242. {execsql2-2.15.7 → execsql2-2.15.8}/tests/integration/__init__.py +0 -0
  243. {execsql2-2.15.7 → execsql2-2.15.8}/tests/integration/conftest.py +0 -0
  244. {execsql2-2.15.7 → execsql2-2.15.8}/tests/integration/test_dsn.py +0 -0
  245. {execsql2-2.15.7 → execsql2-2.15.8}/tests/integration/test_duckdb.py +0 -0
  246. {execsql2-2.15.7 → execsql2-2.15.8}/tests/integration/test_mysql.py +0 -0
  247. {execsql2-2.15.7 → execsql2-2.15.8}/tests/integration/test_postgres.py +0 -0
  248. {execsql2-2.15.7 → execsql2-2.15.8}/tests/integration/test_sqlite.py +0 -0
  249. {execsql2-2.15.7 → execsql2-2.15.8}/tests/metacommands/__init__.py +0 -0
  250. {execsql2-2.15.7 → execsql2-2.15.8}/tests/metacommands/test_assert.py +0 -0
  251. {execsql2-2.15.7 → execsql2-2.15.8}/tests/metacommands/test_breakpoint.py +0 -0
  252. {execsql2-2.15.7 → execsql2-2.15.8}/tests/metacommands/test_connect.py +0 -0
  253. {execsql2-2.15.7 → execsql2-2.15.8}/tests/metacommands/test_io_export.py +0 -0
  254. {execsql2-2.15.7 → execsql2-2.15.8}/tests/metacommands/test_io_import.py +0 -0
  255. {execsql2-2.15.7 → execsql2-2.15.8}/tests/metacommands/test_metacommands.py +0 -0
  256. {execsql2-2.15.7 → execsql2-2.15.8}/tests/metacommands/test_metacommands_connect.py +0 -0
  257. {execsql2-2.15.7 → execsql2-2.15.8}/tests/metacommands/test_metacommands_data.py +0 -0
  258. {execsql2-2.15.7 → execsql2-2.15.8}/tests/metacommands/test_metacommands_extended.py +0 -0
  259. {execsql2-2.15.7 → execsql2-2.15.8}/tests/metacommands/test_metacommands_fileops_extra.py +0 -0
  260. {execsql2-2.15.7 → execsql2-2.15.8}/tests/metacommands/test_metacommands_io.py +0 -0
  261. {execsql2-2.15.7 → execsql2-2.15.8}/tests/metacommands/test_metacommands_io_write_extra.py +0 -0
  262. {execsql2-2.15.7 → execsql2-2.15.8}/tests/metacommands/test_metacommands_script_ext.py +0 -0
  263. {execsql2-2.15.7 → execsql2-2.15.8}/tests/metacommands/test_metacommands_system.py +0 -0
  264. {execsql2-2.15.7 → execsql2-2.15.8}/tests/metacommands/test_metacommands_system_extra.py +0 -0
  265. {execsql2-2.15.7 → execsql2-2.15.8}/tests/metacommands/test_row_count.py +0 -0
  266. {execsql2-2.15.7 → execsql2-2.15.8}/tests/test_config.py +0 -0
  267. {execsql2-2.15.7 → execsql2-2.15.8}/tests/test_config_data.py +0 -0
  268. {execsql2-2.15.7 → execsql2-2.15.8}/tests/test_config_extended.py +0 -0
  269. {execsql2-2.15.7 → execsql2-2.15.8}/tests/test_debug_repl.py +0 -0
  270. {execsql2-2.15.7 → execsql2-2.15.8}/tests/test_engine.py +0 -0
  271. {execsql2-2.15.7 → execsql2-2.15.8}/tests/test_error_messages.py +0 -0
  272. {execsql2-2.15.7 → execsql2-2.15.8}/tests/test_exceptions.py +0 -0
  273. {execsql2-2.15.7 → execsql2-2.15.8}/tests/test_format.py +0 -0
  274. {execsql2-2.15.7 → execsql2-2.15.8}/tests/test_mail.py +0 -0
  275. {execsql2-2.15.7 → execsql2-2.15.8}/tests/test_models.py +0 -0
  276. {execsql2-2.15.7 → execsql2-2.15.8}/tests/test_package.py +0 -0
  277. {execsql2-2.15.7 → execsql2-2.15.8}/tests/test_parser.py +0 -0
  278. {execsql2-2.15.7 → execsql2-2.15.8}/tests/test_registry.py +0 -0
  279. {execsql2-2.15.7 → execsql2-2.15.8}/tests/test_script.py +0 -0
  280. {execsql2-2.15.7 → execsql2-2.15.8}/tests/test_state.py +0 -0
  281. {execsql2-2.15.7 → execsql2-2.15.8}/tests/test_types.py +0 -0
  282. {execsql2-2.15.7 → execsql2-2.15.8}/tests/utils/__init__.py +0 -0
  283. {execsql2-2.15.7 → execsql2-2.15.8}/tests/utils/test_auth.py +0 -0
  284. {execsql2-2.15.7 → execsql2-2.15.8}/tests/utils/test_auth_extra.py +0 -0
  285. {execsql2-2.15.7 → execsql2-2.15.8}/tests/utils/test_crypto.py +0 -0
  286. {execsql2-2.15.7 → execsql2-2.15.8}/tests/utils/test_datetime.py +0 -0
  287. {execsql2-2.15.7 → execsql2-2.15.8}/tests/utils/test_errors.py +0 -0
  288. {execsql2-2.15.7 → execsql2-2.15.8}/tests/utils/test_errors_extra.py +0 -0
  289. {execsql2-2.15.7 → execsql2-2.15.8}/tests/utils/test_fileio.py +0 -0
  290. {execsql2-2.15.7 → execsql2-2.15.8}/tests/utils/test_fileio_extra.py +0 -0
  291. {execsql2-2.15.7 → execsql2-2.15.8}/tests/utils/test_numeric.py +0 -0
  292. {execsql2-2.15.7 → execsql2-2.15.8}/tests/utils/test_regex.py +0 -0
  293. {execsql2-2.15.7 → execsql2-2.15.8}/tests/utils/test_strings.py +0 -0
  294. {execsql2-2.15.7 → execsql2-2.15.8}/tests/utils/test_timer.py +0 -0
  295. {execsql2-2.15.7 → execsql2-2.15.8}/tests/utils/test_timer_extra.py +0 -0
  296. {execsql2-2.15.7 → execsql2-2.15.8}/zensical.toml +0 -0
@@ -13,6 +13,25 @@ ______________________________________________________________________
13
13
 
14
14
  ______________________________________________________________________
15
15
 
16
+ ## [2.15.8] - 2026-04-20
17
+
18
+ ### Added
19
+
20
+ - `PG_UPSERT` / `PG_UPSERT QA` / `PG_UPSERT CHECK` now support the `STRICT_COLUMNS` keyword. When present, all missing columns in staging tables are treated as errors (not just PK and NOT NULL/no-default columns). Maps to pg-upsert's `strict_columns=True` parameter.
21
+ - New substitution variable `$PG_UPSERT_QA_WARNINGS` — a comma-separated list of table names that received WARNING-level QA findings. Scripts can use this to react to warnings without parsing `$PG_UPSERT_RESULT_JSON`.
22
+
23
+ ### Changed
24
+
25
+ - `$PG_UPSERT_RESULT_JSON` now includes a `qa_warnings` array per table (previously only `qa_errors` was present). This reflects pg-upsert v1.22's severity-aware QA model.
26
+ - Minimum pg-upsert version bumped from `>=1.21.0` to `>=1.22.0`.
27
+
28
+ ### Fixed
29
+
30
+ - `PG_UPSERT QA` and `PG_UPSERT CHECK` now capture all QA findings (errors + warnings) instead of only errors, so `$PG_UPSERT_RESULT_JSON` includes the full picture.
31
+ - Fixed compatibility with pg-upsert v1.22.0 where `TableResult.qa_errors` became a read-only property (now writes to `_qa_findings` field).
32
+
33
+ ______________________________________________________________________
34
+
16
35
  ## [2.15.7] - 2026-04-20
17
36
 
18
37
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: execsql2
3
- Version: 2.15.7
3
+ Version: 2.15.8
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
@@ -52,7 +52,7 @@ Requires-Dist: keyring; extra == 'all'
52
52
  Requires-Dist: odfpy; extra == 'all'
53
53
  Requires-Dist: openpyxl; extra == 'all'
54
54
  Requires-Dist: oracledb; extra == 'all'
55
- Requires-Dist: pg-upsert>=1.21.0; extra == 'all'
55
+ Requires-Dist: pg-upsert>=1.22.0; extra == 'all'
56
56
  Requires-Dist: polars; extra == 'all'
57
57
  Requires-Dist: psycopg2-binary; extra == 'all'
58
58
  Requires-Dist: pymysql; extra == 'all'
@@ -117,7 +117,7 @@ Requires-Dist: oracledb; extra == 'oracle'
117
117
  Provides-Extra: postgres
118
118
  Requires-Dist: psycopg2-binary; extra == 'postgres'
119
119
  Provides-Extra: upsert
120
- Requires-Dist: pg-upsert>=1.21.0; extra == 'upsert'
120
+ Requires-Dist: pg-upsert>=1.22.0; extra == 'upsert'
121
121
  Description-Content-Type: text/markdown
122
122
 
123
123
  > [!NOTE]
@@ -41,14 +41,14 @@ ______________________________________________________________________
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. |
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. Supports `EXPORT_FAILURES`, `EXPORT_FORMAT`, and `EXPORT_MAX_ROWS` keywords to write a "fix sheet" of failing QA rows to CSV/JSON/XLSX (requires `pg-upsert>=1.21.0`). |
51
- | `IMPORT … FROM JSON` | Import a JSON file (array of objects or NDJSON) into a database table. Nested objects are flattened with dot-separated column names; arrays are stored as JSON strings. |
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. Supports `EXPORT_FAILURES`, `EXPORT_FORMAT`, `EXPORT_MAX_ROWS`, and `STRICT_COLUMNS` keywords. `STRICT_COLUMNS` forces all missing columns to be errors (requires `pg-upsert>=1.22.0`). |
51
+ | `IMPORT … FROM JSON` | Import a JSON file (array of objects or NDJSON) into a database table. Nested objects are flattened with dot-separated column names; arrays are stored as JSON strings. |
52
52
 
53
53
  ### Conditional Tests
54
54
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "execsql2"
7
- version = "2.15.7"
7
+ version = "2.15.8"
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" }
@@ -61,7 +61,7 @@ formats = ["odfpy", "xlrd", "openpyxl", "Jinja2", "polars", "tables", "PyYAML"]
61
61
  auth = ["keyring"]
62
62
  auth-plaintext = ["keyring", "keyrings.alt"]
63
63
  auth-encrypted = ["keyring", "keyrings.alt", "pycryptodome"]
64
- upsert = ["pg-upsert>=1.21.0"]
64
+ upsert = ["pg-upsert>=1.22.0"]
65
65
  # Convenience groups
66
66
  all-db = [
67
67
  "psycopg2-binary",
@@ -164,7 +164,7 @@ skip-magic-trailing-comma = false
164
164
  line-ending = "auto"
165
165
 
166
166
  [tool.bumpversion]
167
- current_version = "2.15.7"
167
+ current_version = "2.15.8"
168
168
  commit = true
169
169
  commit_args = "--no-verify"
170
170
  tag = true
@@ -26,11 +26,11 @@ 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|CLEANUP|EXPORT_FAILURES|EXPORT_FORMAT|EXPORT_MAX_ROWS)\b|\s*$)",
29
+ r"\bEXCLUDE\s+([\w\s,]+?)(?=\s+(?:METHOD|COMMIT|INTERACTIVE|COMPACT|EXCLUDE_NULL|LOGFILE|CLEANUP|EXPORT_FAILURES|EXPORT_FORMAT|EXPORT_MAX_ROWS|STRICT_COLUMNS)\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|CLEANUP|EXPORT_FAILURES|EXPORT_FORMAT|EXPORT_MAX_ROWS)\b|\s*$)",
33
+ r"\bEXCLUDE_NULL\s+([\w\s,]+?)(?=\s+(?:METHOD|COMMIT|INTERACTIVE|COMPACT|EXCLUDE|LOGFILE|CLEANUP|EXPORT_FAILURES|EXPORT_FORMAT|EXPORT_MAX_ROWS|STRICT_COLUMNS)\b|\s*$)",
34
34
  re.IGNORECASE,
35
35
  )
36
36
  _KW_COMMIT = re.compile(r"\bCOMMIT\b", re.IGNORECASE)
@@ -44,10 +44,11 @@ _KW_EXPORT_FAILURES = re.compile(
44
44
  )
45
45
  _KW_EXPORT_FORMAT = re.compile(r"\bEXPORT_FORMAT\s+(\S+)", re.IGNORECASE)
46
46
  _KW_EXPORT_MAX_ROWS = re.compile(r"\bEXPORT_MAX_ROWS\s+(\S+)", re.IGNORECASE)
47
+ _KW_STRICT_COLUMNS = re.compile(r"\bSTRICT_COLUMNS\b", re.IGNORECASE)
47
48
 
48
49
  # All recognized keywords — used to split table names from options.
49
50
  _ALL_KEYWORDS = re.compile(
50
- r"\b(?:METHOD|COMMIT|INTERACTIVE|COMPACT|EXCLUDE_NULL|EXCLUDE|LOGFILE|CLEANUP|EXPORT_FAILURES|EXPORT_FORMAT|EXPORT_MAX_ROWS)\b",
51
+ r"\b(?:METHOD|COMMIT|INTERACTIVE|COMPACT|EXCLUDE_NULL|EXCLUDE|LOGFILE|CLEANUP|EXPORT_FAILURES|EXPORT_FORMAT|EXPORT_MAX_ROWS|STRICT_COLUMNS)\b",
51
52
  re.IGNORECASE,
52
53
  )
53
54
 
@@ -145,6 +146,7 @@ def _parse_tables_and_options(tail: str) -> dict[str, Any]:
145
146
  "export_failures": export_failures,
146
147
  "export_format": export_format,
147
148
  "export_max_rows": export_max_rows,
149
+ "strict_columns": bool(_KW_STRICT_COLUMNS.search(opts_part)),
148
150
  }
149
151
 
150
152
 
@@ -191,6 +193,9 @@ def _set_subvars(result: Any) -> None:
191
193
  sv("$PG_UPSERT_STARTED_AT", result.started_at)
192
194
  sv("$PG_UPSERT_FINISHED_AT", result.finished_at)
193
195
  sv("$PG_UPSERT_RESULT_JSON", json.dumps(result.to_dict(), separators=(",", ":")))
196
+ # Warnings: tables with WARNING-level findings (still qa_passed=True).
197
+ warned_tables = [t.table_name for t in result.tables if t.qa_warnings]
198
+ sv("$PG_UPSERT_QA_WARNINGS", ", ".join(warned_tables) if warned_tables else "")
194
199
  # Default export path subvar to empty; _export_failures_if_requested
195
200
  # will overwrite it with the actual path if an export was produced.
196
201
  sv("$PG_UPSERT_EXPORT_PATH", "")
@@ -230,16 +235,20 @@ def _require_postgres(db: Any, metacommandline: str | None) -> None:
230
235
  )
231
236
 
232
237
 
233
- def _build_result_from_qa_errors(ups: Any) -> Any:
234
- """Build an UpsertResult from ``ups.qa_errors`` after a QA/CHECK run."""
238
+ def _build_result_from_qa_findings(ups: Any) -> Any:
239
+ """Build an UpsertResult from ``ups.qa_findings`` after a QA/CHECK run.
240
+
241
+ Uses ``qa_findings`` (all findings: errors + warnings) so that
242
+ ``$PG_UPSERT_RESULT_JSON`` includes both severity levels.
243
+ """
235
244
  from pg_upsert.models import TableResult, UpsertResult
236
245
 
237
246
  table_results: dict[str, Any] = {}
238
247
  for table_name in ups.tables:
239
248
  table_results[table_name] = TableResult(table_name=table_name)
240
- for err in ups.qa_errors:
241
- if err.table in table_results:
242
- table_results[err.table].qa_errors.append(err)
249
+ for finding in ups.qa_findings:
250
+ if finding.table in table_results:
251
+ table_results[finding.table]._qa_findings.append(finding)
243
252
  return UpsertResult(
244
253
  tables=list(table_results.values()),
245
254
  committed=False,
@@ -289,6 +298,7 @@ def _create_pgupsert(
289
298
  "upsert_method": opts["method"],
290
299
  "exclude_cols": opts["exclude_cols"],
291
300
  "exclude_null_check_cols": opts["exclude_null_check_cols"],
301
+ "strict_columns": opts.get("strict_columns", False),
292
302
  "ui_mode": ui_mode,
293
303
  "callback": _make_callback(),
294
304
  }
@@ -508,7 +518,7 @@ def x_pg_upsert_qa(**kwargs: Any) -> None:
508
518
  finally:
509
519
  _detach_log_handlers(loggers, handlers, prev_levels)
510
520
 
511
- result = _build_result_from_qa_errors(ups)
521
+ result = _build_result_from_qa_findings(ups)
512
522
  _set_subvars(result)
513
523
  _export_failures_if_requested(result, opts, metacommandline)
514
524
  if opts.get("cleanup"):
@@ -551,7 +561,7 @@ def x_pg_upsert_check(**kwargs: Any) -> None:
551
561
  finally:
552
562
  _detach_log_handlers(loggers, handlers, prev_levels)
553
563
 
554
- result = _build_result_from_qa_errors(ups)
564
+ result = _build_result_from_qa_findings(ups)
555
565
  _set_subvars(result)
556
566
  _export_failures_if_requested(result, opts, metacommandline)
557
567
  if opts.get("cleanup"):
@@ -17,7 +17,7 @@ import pytest
17
17
  from execsql.exceptions import ErrInfo
18
18
  from execsql.metacommands.upsert import (
19
19
  _FileWriterHandler,
20
- _build_result_from_qa_errors,
20
+ _build_result_from_qa_findings,
21
21
  _parse_tables_and_options,
22
22
  _qa_failure_msg,
23
23
  x_pg_upsert,
@@ -46,12 +46,20 @@ class FakeTableResult:
46
46
  table_name: str = ""
47
47
  rows_updated: int = 0
48
48
  rows_inserted: int = 0
49
- qa_errors: list[Any] = field(default_factory=list)
49
+ _qa_findings: list[Any] = field(default_factory=list)
50
50
 
51
51
  @property
52
52
  def qa_passed(self) -> bool:
53
53
  return len(self.qa_errors) == 0
54
54
 
55
+ @property
56
+ def qa_errors(self) -> list[Any]:
57
+ return [f for f in self._qa_findings if getattr(f, "severity", "ERROR") == "ERROR"]
58
+
59
+ @property
60
+ def qa_warnings(self) -> list[Any]:
61
+ return [f for f in self._qa_findings if getattr(f, "severity", "ERROR") == "WARNING"]
62
+
55
63
  def to_dict(self) -> dict[str, Any]:
56
64
  return {
57
65
  "table_name": self.table_name,
@@ -59,6 +67,7 @@ class FakeTableResult:
59
67
  "rows_inserted": self.rows_inserted,
60
68
  "qa_passed": self.qa_passed,
61
69
  "qa_errors": [e.to_dict() for e in self.qa_errors],
70
+ "qa_warnings": [e.to_dict() for e in self.qa_warnings],
62
71
  }
63
72
 
64
73
 
@@ -231,6 +240,20 @@ class TestParseTablesAndOptions:
231
240
  result = _parse_tables_and_options("books COMPACT")
232
241
  assert result["compact"] is True
233
242
 
243
+ def test_strict_columns_keyword(self):
244
+ result = _parse_tables_and_options("books STRICT_COLUMNS")
245
+ assert result["strict_columns"] is True
246
+
247
+ def test_strict_columns_default_false(self):
248
+ result = _parse_tables_and_options("books")
249
+ assert result["strict_columns"] is False
250
+
251
+ def test_strict_columns_with_other_keywords(self):
252
+ result = _parse_tables_and_options("books STRICT_COLUMNS COMMIT METHOD update")
253
+ assert result["strict_columns"] is True
254
+ assert result["commit"] is True
255
+ assert result["method"] == "update"
256
+
234
257
  def test_exclude_cols(self):
235
258
  result = _parse_tables_and_options("books EXCLUDE rev_time, created_at COMMIT")
236
259
  assert result["tables"] == ["books"]
@@ -524,7 +547,7 @@ class TestFullMode:
524
547
  tables=[
525
548
  FakeTableResult(
526
549
  table_name="books",
527
- qa_errors=[FakeQAError(table="books", check_type="null", details="col1 (5)")],
550
+ _qa_findings=[FakeQAError(table="books", check_type="null", details="col1 (5)")],
528
551
  ),
529
552
  ],
530
553
  staging_schema="staging",
@@ -606,7 +629,7 @@ class TestQAMode:
606
629
  with (
607
630
  patch("execsql.metacommands.upsert._require_pg_upsert"),
608
631
  patch("execsql.metacommands.upsert._create_pgupsert") as mock_create,
609
- patch("execsql.metacommands.upsert._build_result_from_qa_errors") as mock_build,
632
+ patch("execsql.metacommands.upsert._build_result_from_qa_findings") as mock_build,
610
633
  ):
611
634
  mock_ups = mock_create.return_value
612
635
  mock_ups.qa_all.return_value = mock_ups
@@ -629,7 +652,7 @@ class TestQAMode:
629
652
  with (
630
653
  patch("execsql.metacommands.upsert._require_pg_upsert"),
631
654
  patch("execsql.metacommands.upsert._create_pgupsert") as mock_create,
632
- patch("execsql.metacommands.upsert._build_result_from_qa_errors") as mock_build,
655
+ patch("execsql.metacommands.upsert._build_result_from_qa_findings") as mock_build,
633
656
  ):
634
657
  mock_ups = mock_create.return_value
635
658
  mock_ups.qa_all.return_value = mock_ups
@@ -661,7 +684,7 @@ class TestCheckMode:
661
684
  with (
662
685
  patch("execsql.metacommands.upsert._require_pg_upsert"),
663
686
  patch("execsql.metacommands.upsert._create_pgupsert") as mock_create,
664
- patch("execsql.metacommands.upsert._build_result_from_qa_errors") as mock_build,
687
+ patch("execsql.metacommands.upsert._build_result_from_qa_findings") as mock_build,
665
688
  ):
666
689
  mock_ups = mock_create.return_value
667
690
  mock_ups.qa_column_existence.return_value = mock_ups
@@ -856,12 +879,12 @@ class TestQAFailureMessage:
856
879
  tables=[
857
880
  FakeTableResult(
858
881
  table_name="books",
859
- qa_errors=[FakeQAError(table="books", check_type="null", details="title (3)")],
882
+ _qa_findings=[FakeQAError(table="books", check_type="null", details="title (3)")],
860
883
  ),
861
884
  FakeTableResult(table_name="authors"), # passes
862
885
  FakeTableResult(
863
886
  table_name="genres",
864
- qa_errors=[FakeQAError(table="genres", check_type="fk", details="parent_id (5)")],
887
+ _qa_findings=[FakeQAError(table="genres", check_type="fk", details="parent_id (5)")],
865
888
  ),
866
889
  ],
867
890
  )
@@ -873,7 +896,7 @@ class TestQAFailureMessage:
873
896
  tables=[
874
897
  FakeTableResult(
875
898
  table_name="books",
876
- qa_errors=[FakeQAError(table="books", check_type="pk", details="id (2)")],
899
+ _qa_findings=[FakeQAError(table="books", check_type="pk", details="id (2)")],
877
900
  ),
878
901
  ],
879
902
  )
@@ -918,7 +941,7 @@ class TestQAModeFailure:
918
941
  tables=[
919
942
  FakeTableResult(
920
943
  table_name="books",
921
- qa_errors=[FakeQAError(table="books", check_type="null", details="col1 (5)")],
944
+ _qa_findings=[FakeQAError(table="books", check_type="null", details="col1 (5)")],
922
945
  ),
923
946
  ],
924
947
  staging_schema="staging",
@@ -928,7 +951,7 @@ class TestQAModeFailure:
928
951
  with (
929
952
  patch("execsql.metacommands.upsert._require_pg_upsert"),
930
953
  patch("execsql.metacommands.upsert._create_pgupsert") as mock_create,
931
- patch("execsql.metacommands.upsert._build_result_from_qa_errors") as mock_build,
954
+ patch("execsql.metacommands.upsert._build_result_from_qa_findings") as mock_build,
932
955
  ):
933
956
  mock_ups = mock_create.return_value
934
957
  mock_ups.qa_all.return_value = mock_ups
@@ -951,7 +974,7 @@ class TestCheckModeFailure:
951
974
  tables=[
952
975
  FakeTableResult(
953
976
  table_name="books",
954
- qa_errors=[FakeQAError(table="books", check_type="column", details="missing_col")],
977
+ _qa_findings=[FakeQAError(table="books", check_type="column", details="missing_col")],
955
978
  ),
956
979
  ],
957
980
  staging_schema="staging",
@@ -961,7 +984,7 @@ class TestCheckModeFailure:
961
984
  with (
962
985
  patch("execsql.metacommands.upsert._require_pg_upsert"),
963
986
  patch("execsql.metacommands.upsert._create_pgupsert") as mock_create,
964
- patch("execsql.metacommands.upsert._build_result_from_qa_errors") as mock_build,
987
+ patch("execsql.metacommands.upsert._build_result_from_qa_findings") as mock_build,
965
988
  ):
966
989
  mock_ups = mock_create.return_value
967
990
  mock_ups.qa_column_existence.return_value = mock_ups
@@ -978,7 +1001,7 @@ class TestCheckModeFailure:
978
1001
 
979
1002
 
980
1003
  # ---------------------------------------------------------------------------
981
- # _build_result_from_qa_errors direct test
1004
+ # _build_result_from_qa_findings direct test
982
1005
  # ---------------------------------------------------------------------------
983
1006
 
984
1007
 
@@ -986,7 +1009,7 @@ class TestBuildResultFromQAErrors:
986
1009
  def test_groups_errors_by_table(self):
987
1010
  mock_ups = MagicMock()
988
1011
  mock_ups.tables = ["books", "authors"]
989
- mock_ups.qa_errors = [
1012
+ mock_ups.qa_findings = [
990
1013
  FakeQAError(table="books", check_type="null", details="title (3)"),
991
1014
  FakeQAError(table="authors", check_type="pk", details="id (2)"),
992
1015
  FakeQAError(table="books", check_type="fk", details="pub_id (1)"),
@@ -995,7 +1018,7 @@ class TestBuildResultFromQAErrors:
995
1018
  mock_ups.base_schema = "public"
996
1019
  mock_ups.upsert_method = "upsert"
997
1020
 
998
- result = _build_result_from_qa_errors(mock_ups)
1021
+ result = _build_result_from_qa_findings(mock_ups)
999
1022
  assert result.staging_schema == "staging"
1000
1023
  assert result.base_schema == "public"
1001
1024
  assert result.upsert_method == "upsert"
@@ -1010,12 +1033,12 @@ class TestBuildResultFromQAErrors:
1010
1033
  def test_no_errors_means_passed(self):
1011
1034
  mock_ups = MagicMock()
1012
1035
  mock_ups.tables = ["books"]
1013
- mock_ups.qa_errors = []
1036
+ mock_ups.qa_findings = []
1014
1037
  mock_ups.staging_schema = "staging"
1015
1038
  mock_ups.base_schema = "public"
1016
1039
  mock_ups.upsert_method = "upsert"
1017
1040
 
1018
- result = _build_result_from_qa_errors(mock_ups)
1041
+ result = _build_result_from_qa_findings(mock_ups)
1019
1042
  assert result.qa_passed
1020
1043
 
1021
1044
 
@@ -1393,7 +1416,7 @@ class TestExportFailures:
1393
1416
  tables=[
1394
1417
  FakeTableResult(
1395
1418
  table_name="books",
1396
- qa_errors=[FakeQAError(table="books", check_type="null", details="col1")],
1419
+ _qa_findings=[FakeQAError(table="books", check_type="null", details="col1")],
1397
1420
  ),
1398
1421
  ],
1399
1422
  staging_schema="staging",
@@ -1500,7 +1523,7 @@ class TestExportFailures:
1500
1523
  ):
1501
1524
  mock_ups = mock_create.return_value
1502
1525
  mock_ups.tables = ["books"]
1503
- mock_ups.qa_errors = []
1526
+ mock_ups.qa_findings = []
1504
1527
  mock_ups.staging_schema = "staging"
1505
1528
  mock_ups.base_schema = "public"
1506
1529
  mock_ups.upsert_method = "upsert"
@@ -1516,7 +1539,7 @@ class TestExportFailures:
1516
1539
  return r
1517
1540
 
1518
1541
  with patch(
1519
- "execsql.metacommands.upsert._build_result_from_qa_errors",
1542
+ "execsql.metacommands.upsert._build_result_from_qa_findings",
1520
1543
  side_effect=_fake_build,
1521
1544
  ):
1522
1545
  x_pg_upsert_qa(
@@ -1536,7 +1559,7 @@ class TestExportFailures:
1536
1559
  ):
1537
1560
  mock_ups = mock_create.return_value
1538
1561
  mock_ups.tables = ["books"]
1539
- mock_ups.qa_errors = []
1562
+ mock_ups.qa_findings = []
1540
1563
  mock_ups.staging_schema = "staging"
1541
1564
  mock_ups.base_schema = "public"
1542
1565
  mock_ups.upsert_method = "upsert"
@@ -1555,7 +1578,7 @@ class TestExportFailures:
1555
1578
  return r
1556
1579
 
1557
1580
  with patch(
1558
- "execsql.metacommands.upsert._build_result_from_qa_errors",
1581
+ "execsql.metacommands.upsert._build_result_from_qa_findings",
1559
1582
  side_effect=_fake_build,
1560
1583
  ):
1561
1584
  x_pg_upsert_check(
@@ -648,7 +648,7 @@ wheels = [
648
648
 
649
649
  [[package]]
650
650
  name = "execsql2"
651
- version = "2.15.7"
651
+ version = "2.15.8"
652
652
  source = { editable = "." }
653
653
  dependencies = [
654
654
  { name = "python-dateutil" },
@@ -780,7 +780,7 @@ requires-dist = [
780
780
  { name = "openpyxl", marker = "extra == 'formats'" },
781
781
  { name = "oracledb", marker = "extra == 'all-db'" },
782
782
  { name = "oracledb", marker = "extra == 'oracle'" },
783
- { name = "pg-upsert", marker = "extra == 'upsert'", specifier = ">=1.21.0" },
783
+ { name = "pg-upsert", marker = "extra == 'upsert'", specifier = ">=1.22.0" },
784
784
  { name = "polars", marker = "extra == 'formats'" },
785
785
  { name = "pre-commit", marker = "extra == 'dev'", specifier = ">=3.5.0" },
786
786
  { name = "psycopg2-binary", marker = "extra == 'all-db'" },
@@ -1809,7 +1809,7 @@ wheels = [
1809
1809
 
1810
1810
  [[package]]
1811
1811
  name = "pg-upsert"
1812
- version = "1.21.0"
1812
+ version = "1.22.0"
1813
1813
  source = { registry = "https://pypi.org/simple" }
1814
1814
  dependencies = [
1815
1815
  { name = "psycopg2-binary" },
@@ -1817,9 +1817,9 @@ dependencies = [
1817
1817
  { name = "rich" },
1818
1818
  { name = "typer" },
1819
1819
  ]
1820
- sdist = { url = "https://files.pythonhosted.org/packages/6c/91/0ba3482070e3fd1a5fdd43e5132b43f2fbfd64df135b3988f8744964a34e/pg_upsert-1.21.0.tar.gz", hash = "sha256:8b70b83002287b8a47df2598bc69aa99e62be7b0c27d5efedb26075b334b9119", size = 1551989, upload-time = "2026-04-09T20:24:07.627Z" }
1820
+ sdist = { url = "https://files.pythonhosted.org/packages/1a/f8/68b0b02d2e76561cfa580bee320eaf1884666d0d8faa2f2873650e907eef/pg_upsert-1.22.0.tar.gz", hash = "sha256:6ba63d3990689685464695c897be081e9d774c375228cc0bf1ee44340e6a6a6f", size = 1558932, upload-time = "2026-04-20T15:54:46.946Z" }
1821
1821
  wheels = [
1822
- { url = "https://files.pythonhosted.org/packages/99/79/bfd849e7c99d405da687a74a9bf2afcbaace1bc76841c16bb0cea3683507/pg_upsert-1.21.0-py3-none-any.whl", hash = "sha256:4fca873166f3407f609c709cc1ef576bfd21824a9a059929e19c8c1fea919ad7", size = 95164, upload-time = "2026-04-09T20:24:05.701Z" },
1822
+ { url = "https://files.pythonhosted.org/packages/c3/f3/965c57af3bc89103d2f04435277c2c5e7e9b2bb9ce0653f434c55a570e4a/pg_upsert-1.22.0-py3-none-any.whl", hash = "sha256:c823af17c996ba5cb3ed8a2393882ea4a830a14d04965c090b73164b806f2309", size = 98581, upload-time = "2026-04-20T15:54:44.816Z" },
1823
1823
  ]
1824
1824
 
1825
1825
  [[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