execsql2 2.19.2__tar.gz → 2.21.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (375) hide show
  1. {execsql2-2.19.2 → execsql2-2.21.0}/.github/workflows/ci-cd.yml +23 -11
  2. {execsql2-2.19.2 → execsql2-2.21.0}/.pre-commit-hooks.yaml +1 -0
  3. {execsql2-2.19.2 → execsql2-2.21.0}/.readthedocs.yaml +1 -1
  4. {execsql2-2.19.2 → execsql2-2.21.0}/CHANGELOG.md +35 -8
  5. {execsql2-2.19.2 → execsql2-2.21.0}/PKG-INFO +56 -58
  6. {execsql2-2.19.2 → execsql2-2.21.0}/README.md +2 -3
  7. {execsql2-2.19.2 → execsql2-2.21.0}/docs/about/divergence.md +17 -7
  8. {execsql2-2.19.2 → execsql2-2.21.0}/docs/dev/adding_db_adapters.md +2 -2
  9. {execsql2-2.19.2 → execsql2-2.21.0}/docs/dev/architecture.md +26 -0
  10. execsql2-2.21.0/docs/dev/releasing.md +124 -0
  11. {execsql2-2.19.2 → execsql2-2.21.0}/docs/getting-started/requirements.md +26 -10
  12. {execsql2-2.19.2 → execsql2-2.21.0}/docs/guides/formatter.md +11 -11
  13. {execsql2-2.19.2 → execsql2-2.21.0}/docs/guides/usage.md +1 -1
  14. {execsql2-2.19.2 → execsql2-2.21.0}/docs/reference/metacommands.md +1 -1
  15. {execsql2-2.19.2 → execsql2-2.21.0}/docs/reference/security.md +19 -0
  16. {execsql2-2.19.2 → execsql2-2.21.0}/pyproject.toml +41 -23
  17. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/cli/run.py +28 -11
  18. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/db/access.py +5 -1
  19. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/db/base.py +8 -2
  20. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/db/dsn.py +3 -1
  21. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/db/factory.py +1 -1
  22. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/db/firebird.py +10 -4
  23. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/db/mysql.py +6 -0
  24. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/db/postgres.py +20 -18
  25. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/db/sqlserver.py +4 -1
  26. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/format.py +172 -16
  27. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/utils/mail.py +7 -4
  28. {execsql2-2.19.2 → execsql2-2.21.0}/templates/README.md +1 -1
  29. {execsql2-2.19.2 → execsql2-2.21.0}/tests/cli/test_cli.py +10 -7
  30. {execsql2-2.19.2 → execsql2-2.21.0}/tests/db/test_db_adapters_mocked.py +13 -10
  31. {execsql2-2.19.2 → execsql2-2.21.0}/tests/db/test_db_adapters_mocked_extra.py +9 -3
  32. {execsql2-2.19.2 → execsql2-2.21.0}/tests/db/test_postgres.py +23 -16
  33. {execsql2-2.19.2 → execsql2-2.21.0}/tests/db/test_postgres_inprocess.py +5 -5
  34. {execsql2-2.19.2 → execsql2-2.21.0}/tests/integration/test_postgres.py +6 -6
  35. execsql2-2.21.0/tests/security/test_log_redaction.py +187 -0
  36. {execsql2-2.19.2 → execsql2-2.21.0}/tests/test_format.py +242 -0
  37. {execsql2-2.19.2 → execsql2-2.21.0}/tests/test_mail.py +3 -3
  38. {execsql2-2.19.2 → execsql2-2.21.0}/uv.lock +123 -111
  39. {execsql2-2.19.2 → execsql2-2.21.0}/zensical.toml +2 -1
  40. execsql2-2.19.2/templates/execsql.conf +0 -359
  41. execsql2-2.19.2/tests/security/test_log_redaction.py +0 -74
  42. {execsql2-2.19.2 → execsql2-2.21.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  43. {execsql2-2.19.2 → execsql2-2.21.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  44. {execsql2-2.19.2 → execsql2-2.21.0}/.github/dependabot.yml +0 -0
  45. {execsql2-2.19.2 → execsql2-2.21.0}/.gitignore +0 -0
  46. {execsql2-2.19.2 → execsql2-2.21.0}/.pre-commit-config.yaml +0 -0
  47. {execsql2-2.19.2 → execsql2-2.21.0}/.python-version +0 -0
  48. {execsql2-2.19.2 → execsql2-2.21.0}/CONTRIBUTING.md +0 -0
  49. {execsql2-2.19.2 → execsql2-2.21.0}/LICENSE.txt +0 -0
  50. {execsql2-2.19.2 → execsql2-2.21.0}/NOTICE +0 -0
  51. {execsql2-2.19.2 → execsql2-2.21.0}/SECURITY.md +0 -0
  52. {execsql2-2.19.2 → execsql2-2.21.0}/docs/about/contributors.md +0 -0
  53. {execsql2-2.19.2 → execsql2-2.21.0}/docs/about/copyright.md +0 -0
  54. {execsql2-2.19.2 → execsql2-2.21.0}/docs/api/cli.md +0 -0
  55. {execsql2-2.19.2 → execsql2-2.21.0}/docs/api/db.md +0 -0
  56. {execsql2-2.19.2 → execsql2-2.21.0}/docs/api/exporters.md +0 -0
  57. {execsql2-2.19.2 → execsql2-2.21.0}/docs/api/importers.md +0 -0
  58. {execsql2-2.19.2 → execsql2-2.21.0}/docs/api/index.md +0 -0
  59. {execsql2-2.19.2 → execsql2-2.21.0}/docs/api/metacommands.md +0 -0
  60. {execsql2-2.19.2 → execsql2-2.21.0}/docs/dev/adding_exporters.md +0 -0
  61. {execsql2-2.19.2 → execsql2-2.21.0}/docs/dev/adding_importers.md +0 -0
  62. {execsql2-2.19.2 → execsql2-2.21.0}/docs/dev/adding_metacommands.md +0 -0
  63. {execsql2-2.19.2 → execsql2-2.21.0}/docs/getting-started/installation.md +0 -0
  64. {execsql2-2.19.2 → execsql2-2.21.0}/docs/getting-started/syntax.md +0 -0
  65. {execsql2-2.19.2 → execsql2-2.21.0}/docs/guides/debugging.md +0 -0
  66. {execsql2-2.19.2 → execsql2-2.21.0}/docs/guides/documentation.md +0 -0
  67. {execsql2-2.19.2 → execsql2-2.21.0}/docs/guides/encoding.md +0 -0
  68. {execsql2-2.19.2 → execsql2-2.21.0}/docs/guides/examples.md +0 -0
  69. {execsql2-2.19.2 → execsql2-2.21.0}/docs/guides/logging.md +0 -0
  70. {execsql2-2.19.2 → execsql2-2.21.0}/docs/guides/sql_syntax.md +0 -0
  71. {execsql2-2.19.2 → execsql2-2.21.0}/docs/guides/using_scripts.md +0 -0
  72. {execsql2-2.19.2 → execsql2-2.21.0}/docs/images/Compare_planets.png +0 -0
  73. {execsql2-2.19.2 → execsql2-2.21.0}/docs/images/actions.png +0 -0
  74. {execsql2-2.19.2 → execsql2-2.21.0}/docs/images/actions2.png +0 -0
  75. {execsql2-2.19.2 → execsql2-2.21.0}/docs/images/checkboxes.png +0 -0
  76. {execsql2-2.19.2 → execsql2-2.21.0}/docs/images/connect.b64 +0 -0
  77. {execsql2-2.19.2 → execsql2-2.21.0}/docs/images/connect.png +0 -0
  78. {execsql2-2.19.2 → execsql2-2.21.0}/docs/images/create_conf.png +0 -0
  79. {execsql2-2.19.2 → execsql2-2.21.0}/docs/images/data_error1_screenshot.jpg +0 -0
  80. {execsql2-2.19.2 → execsql2-2.21.0}/docs/images/entry_form.png +0 -0
  81. {execsql2-2.19.2 → execsql2-2.21.0}/docs/images/execsql_console.png +0 -0
  82. {execsql2-2.19.2 → execsql2-2.21.0}/docs/images/execsql_logo_01.png +0 -0
  83. {execsql2-2.19.2 → execsql2-2.21.0}/docs/images/fatals.png +0 -0
  84. {execsql2-2.19.2 → execsql2-2.21.0}/docs/images/logo_small.png +0 -0
  85. {execsql2-2.19.2 → execsql2-2.21.0}/docs/images/pause_terminal.png +0 -0
  86. {execsql2-2.19.2 → execsql2-2.21.0}/docs/images/pause_terminal_sm.b64 +0 -0
  87. {execsql2-2.19.2 → execsql2-2.21.0}/docs/images/pause_terminal_sm.png +0 -0
  88. {execsql2-2.19.2 → execsql2-2.21.0}/docs/images/prompt_compare.png +0 -0
  89. {execsql2-2.19.2 → execsql2-2.21.0}/docs/images/set_build_commands.jpg +0 -0
  90. {execsql2-2.19.2 → execsql2-2.21.0}/docs/images/unit_conversions.b64 +0 -0
  91. {execsql2-2.19.2 → execsql2-2.21.0}/docs/images/unit_conversions_029.png +0 -0
  92. {execsql2-2.19.2 → execsql2-2.21.0}/docs/images/unmatched.png +0 -0
  93. {execsql2-2.19.2 → execsql2-2.21.0}/docs/images/vim_execsql_highlight.png +0 -0
  94. {execsql2-2.19.2 → execsql2-2.21.0}/docs/index.md +0 -0
  95. {execsql2-2.19.2 → execsql2-2.21.0}/docs/reference/configuration.md +0 -0
  96. {execsql2-2.19.2 → execsql2-2.21.0}/docs/reference/substitution_vars.md +0 -0
  97. {execsql2-2.19.2 → execsql2-2.21.0}/extras/plugin-template/README.md +0 -0
  98. {execsql2-2.19.2 → execsql2-2.21.0}/extras/plugin-template/pyproject.toml +0 -0
  99. {execsql2-2.19.2 → execsql2-2.21.0}/extras/plugin-template/src/execsql_plugin_YOURNAME/__init__.py +0 -0
  100. {execsql2-2.19.2 → execsql2-2.21.0}/extras/plugin-template/tests/test_plugin.py.example +0 -0
  101. {execsql2-2.19.2 → execsql2-2.21.0}/extras/vscode-execsql/README.md +0 -0
  102. {execsql2-2.19.2 → execsql2-2.21.0}/extras/vscode-execsql/package.json +0 -0
  103. {execsql2-2.19.2 → execsql2-2.21.0}/extras/vscode-execsql/syntaxes/execsql.tmLanguage.json +0 -0
  104. {execsql2-2.19.2 → execsql2-2.21.0}/justfile +0 -0
  105. {execsql2-2.19.2 → execsql2-2.21.0}/scripts/generate_vscode_grammar.py +0 -0
  106. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/__init__.py +0 -0
  107. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/__main__.py +0 -0
  108. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/api.py +0 -0
  109. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/cli/__init__.py +0 -0
  110. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/cli/dsn.py +0 -0
  111. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/cli/help.py +0 -0
  112. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/cli/lint.py +0 -0
  113. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/config.py +0 -0
  114. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/data/__init__.py +0 -0
  115. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/data/execsql.conf.template +0 -0
  116. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/db/__init__.py +0 -0
  117. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/db/duckdb.py +0 -0
  118. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/db/oracle.py +0 -0
  119. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/db/sqlite.py +0 -0
  120. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/debug/__init__.py +0 -0
  121. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/debug/repl.py +0 -0
  122. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/exceptions.py +0 -0
  123. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/exporters/__init__.py +0 -0
  124. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/exporters/base.py +0 -0
  125. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/exporters/delimited.py +0 -0
  126. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/exporters/duckdb.py +0 -0
  127. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/exporters/feather.py +0 -0
  128. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/exporters/html.py +0 -0
  129. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/exporters/json.py +0 -0
  130. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/exporters/latex.py +0 -0
  131. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/exporters/markdown.py +0 -0
  132. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/exporters/ods.py +0 -0
  133. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/exporters/parquet.py +0 -0
  134. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/exporters/pretty.py +0 -0
  135. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/exporters/protocol.py +0 -0
  136. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/exporters/raw.py +0 -0
  137. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/exporters/sqlite.py +0 -0
  138. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/exporters/templates.py +0 -0
  139. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/exporters/values.py +0 -0
  140. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/exporters/xls.py +0 -0
  141. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/exporters/xlsx.py +0 -0
  142. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/exporters/xml.py +0 -0
  143. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/exporters/yaml.py +0 -0
  144. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/exporters/zip.py +0 -0
  145. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/gui/__init__.py +0 -0
  146. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/gui/base.py +0 -0
  147. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/gui/console.py +0 -0
  148. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/gui/desktop.py +0 -0
  149. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/gui/tui.py +0 -0
  150. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/importers/__init__.py +0 -0
  151. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/importers/base.py +0 -0
  152. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/importers/csv.py +0 -0
  153. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/importers/feather.py +0 -0
  154. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/importers/json.py +0 -0
  155. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/importers/ods.py +0 -0
  156. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/importers/xls.py +0 -0
  157. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/metacommands/__init__.py +0 -0
  158. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/metacommands/conditions.py +0 -0
  159. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/metacommands/connect.py +0 -0
  160. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/metacommands/control.py +0 -0
  161. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/metacommands/data.py +0 -0
  162. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/metacommands/debug.py +0 -0
  163. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/metacommands/dispatch.py +0 -0
  164. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/metacommands/io.py +0 -0
  165. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/metacommands/io_export.py +0 -0
  166. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/metacommands/io_fileops.py +0 -0
  167. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/metacommands/io_import.py +0 -0
  168. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/metacommands/io_write.py +0 -0
  169. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/metacommands/prompt.py +0 -0
  170. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/metacommands/script_ext.py +0 -0
  171. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/metacommands/system.py +0 -0
  172. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/metacommands/upsert.py +0 -0
  173. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/models.py +0 -0
  174. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/parser.py +0 -0
  175. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/plugins.py +0 -0
  176. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/py.typed +0 -0
  177. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/script/__init__.py +0 -0
  178. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/script/ast.py +0 -0
  179. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/script/control.py +0 -0
  180. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/script/engine.py +0 -0
  181. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/script/executor.py +0 -0
  182. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/script/parser.py +0 -0
  183. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/script/variables.py +0 -0
  184. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/state.py +0 -0
  185. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/types.py +0 -0
  186. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/utils/__init__.py +0 -0
  187. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/utils/auth.py +0 -0
  188. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/utils/crypto.py +0 -0
  189. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/utils/datetime.py +0 -0
  190. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/utils/errors.py +0 -0
  191. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/utils/fileio.py +0 -0
  192. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/utils/gui.py +0 -0
  193. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/utils/numeric.py +0 -0
  194. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/utils/regex.py +0 -0
  195. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/utils/strings.py +0 -0
  196. {execsql2-2.19.2 → execsql2-2.21.0}/src/execsql/utils/timer.py +0 -0
  197. {execsql2-2.19.2 → execsql2-2.21.0}/templates/config_settings.sqlite +0 -0
  198. {execsql2-2.19.2 → execsql2-2.21.0}/templates/example_config_prompt.sql +0 -0
  199. {execsql2-2.19.2 → execsql2-2.21.0}/templates/make_config_db.sql +0 -0
  200. {execsql2-2.19.2 → execsql2-2.21.0}/templates/md_compare.sql +0 -0
  201. {execsql2-2.19.2 → execsql2-2.21.0}/templates/md_glossary.sql +0 -0
  202. {execsql2-2.19.2 → execsql2-2.21.0}/templates/md_upsert.sql +0 -0
  203. {execsql2-2.19.2 → execsql2-2.21.0}/templates/pg_compare.sql +0 -0
  204. {execsql2-2.19.2 → execsql2-2.21.0}/templates/pg_glossary.sql +0 -0
  205. {execsql2-2.19.2 → execsql2-2.21.0}/templates/pg_upsert.sql +0 -0
  206. {execsql2-2.19.2 → execsql2-2.21.0}/templates/script_template.sql +0 -0
  207. {execsql2-2.19.2 → execsql2-2.21.0}/templates/ss_compare.sql +0 -0
  208. {execsql2-2.19.2 → execsql2-2.21.0}/templates/ss_glossary.sql +0 -0
  209. {execsql2-2.19.2 → execsql2-2.21.0}/templates/ss_upsert.sql +0 -0
  210. {execsql2-2.19.2 → execsql2-2.21.0}/tests/__init__.py +0 -0
  211. {execsql2-2.19.2 → execsql2-2.21.0}/tests/cli/__init__.py +0 -0
  212. {execsql2-2.19.2 → execsql2-2.21.0}/tests/cli/test_cli_e2e.py +0 -0
  213. {execsql2-2.19.2 → execsql2-2.21.0}/tests/cli/test_cli_run.py +0 -0
  214. {execsql2-2.19.2 → execsql2-2.21.0}/tests/cli/test_lint.py +0 -0
  215. {execsql2-2.19.2 → execsql2-2.21.0}/tests/cli/test_ping.py +0 -0
  216. {execsql2-2.19.2 → execsql2-2.21.0}/tests/cli/test_profile.py +0 -0
  217. {execsql2-2.19.2 → execsql2-2.21.0}/tests/conftest.py +0 -0
  218. {execsql2-2.19.2 → execsql2-2.21.0}/tests/db/__init__.py +0 -0
  219. {execsql2-2.19.2 → execsql2-2.21.0}/tests/db/test_access_windows.py +0 -0
  220. {execsql2-2.19.2 → execsql2-2.21.0}/tests/db/test_base.py +0 -0
  221. {execsql2-2.19.2 → execsql2-2.21.0}/tests/db/test_dsn.py +0 -0
  222. {execsql2-2.19.2 → execsql2-2.21.0}/tests/db/test_duckdb.py +0 -0
  223. {execsql2-2.19.2 → execsql2-2.21.0}/tests/db/test_factory.py +0 -0
  224. {execsql2-2.19.2 → execsql2-2.21.0}/tests/db/test_mysql_case_folding.py +0 -0
  225. {execsql2-2.19.2 → execsql2-2.21.0}/tests/db/test_mysql_inprocess.py +0 -0
  226. {execsql2-2.19.2 → execsql2-2.21.0}/tests/db/test_sqlite.py +0 -0
  227. {execsql2-2.19.2 → execsql2-2.21.0}/tests/db/test_sqlite_extra.py +0 -0
  228. {execsql2-2.19.2 → execsql2-2.21.0}/tests/db/test_sqlserver_inprocess.py +0 -0
  229. {execsql2-2.19.2 → execsql2-2.21.0}/tests/exporters/__init__.py +0 -0
  230. {execsql2-2.19.2 → execsql2-2.21.0}/tests/exporters/test_base.py +0 -0
  231. {execsql2-2.19.2 → execsql2-2.21.0}/tests/exporters/test_db.py +0 -0
  232. {execsql2-2.19.2 → execsql2-2.21.0}/tests/exporters/test_delimited.py +0 -0
  233. {execsql2-2.19.2 → execsql2-2.21.0}/tests/exporters/test_duckdb_exporter.py +0 -0
  234. {execsql2-2.19.2 → execsql2-2.21.0}/tests/exporters/test_exporters.py +0 -0
  235. {execsql2-2.19.2 → execsql2-2.21.0}/tests/exporters/test_feather.py +0 -0
  236. {execsql2-2.19.2 → execsql2-2.21.0}/tests/exporters/test_html_extended.py +0 -0
  237. {execsql2-2.19.2 → execsql2-2.21.0}/tests/exporters/test_html_latex.py +0 -0
  238. {execsql2-2.19.2 → execsql2-2.21.0}/tests/exporters/test_json.py +0 -0
  239. {execsql2-2.19.2 → execsql2-2.21.0}/tests/exporters/test_json_extended.py +0 -0
  240. {execsql2-2.19.2 → execsql2-2.21.0}/tests/exporters/test_latex_extended.py +0 -0
  241. {execsql2-2.19.2 → execsql2-2.21.0}/tests/exporters/test_markdown.py +0 -0
  242. {execsql2-2.19.2 → execsql2-2.21.0}/tests/exporters/test_ods.py +0 -0
  243. {execsql2-2.19.2 → execsql2-2.21.0}/tests/exporters/test_ods_export.py +0 -0
  244. {execsql2-2.19.2 → execsql2-2.21.0}/tests/exporters/test_parquet.py +0 -0
  245. {execsql2-2.19.2 → execsql2-2.21.0}/tests/exporters/test_pretty_extended.py +0 -0
  246. {execsql2-2.19.2 → execsql2-2.21.0}/tests/exporters/test_raw_extended.py +0 -0
  247. {execsql2-2.19.2 → execsql2-2.21.0}/tests/exporters/test_sqlite_exporter.py +0 -0
  248. {execsql2-2.19.2 → execsql2-2.21.0}/tests/exporters/test_templates.py +0 -0
  249. {execsql2-2.19.2 → execsql2-2.21.0}/tests/exporters/test_templates_extended.py +0 -0
  250. {execsql2-2.19.2 → execsql2-2.21.0}/tests/exporters/test_values_extended.py +0 -0
  251. {execsql2-2.19.2 → execsql2-2.21.0}/tests/exporters/test_xls_xlsx.py +0 -0
  252. {execsql2-2.19.2 → execsql2-2.21.0}/tests/exporters/test_xlsx.py +0 -0
  253. {execsql2-2.19.2 → execsql2-2.21.0}/tests/exporters/test_xml.py +0 -0
  254. {execsql2-2.19.2 → execsql2-2.21.0}/tests/exporters/test_yaml.py +0 -0
  255. {execsql2-2.19.2 → execsql2-2.21.0}/tests/exporters/test_zip.py +0 -0
  256. {execsql2-2.19.2 → execsql2-2.21.0}/tests/gui/__init__.py +0 -0
  257. {execsql2-2.19.2 → execsql2-2.21.0}/tests/gui/test_backends.py +0 -0
  258. {execsql2-2.19.2 → execsql2-2.21.0}/tests/gui/test_backends_extended.py +0 -0
  259. {execsql2-2.19.2 → execsql2-2.21.0}/tests/gui/test_compare_stats.py +0 -0
  260. {execsql2-2.19.2 → execsql2-2.21.0}/tests/gui/test_compute_row_diffs.py +0 -0
  261. {execsql2-2.19.2 → execsql2-2.21.0}/tests/gui/test_desktop_dialogs.py +0 -0
  262. {execsql2-2.19.2 → execsql2-2.21.0}/tests/gui/test_tui_pilot.py +0 -0
  263. {execsql2-2.19.2 → execsql2-2.21.0}/tests/gui/test_tui_pilot_complex.py +0 -0
  264. {execsql2-2.19.2 → execsql2-2.21.0}/tests/gui/test_utils_gui_extended.py +0 -0
  265. {execsql2-2.19.2 → execsql2-2.21.0}/tests/importers/__init__.py +0 -0
  266. {execsql2-2.19.2 → execsql2-2.21.0}/tests/importers/test_base_extended.py +0 -0
  267. {execsql2-2.19.2 → execsql2-2.21.0}/tests/importers/test_csv_edge_cases.py +0 -0
  268. {execsql2-2.19.2 → execsql2-2.21.0}/tests/importers/test_csv_importer.py +0 -0
  269. {execsql2-2.19.2 → execsql2-2.21.0}/tests/importers/test_feather_importer.py +0 -0
  270. {execsql2-2.19.2 → execsql2-2.21.0}/tests/importers/test_json_importer.py +0 -0
  271. {execsql2-2.19.2 → execsql2-2.21.0}/tests/importers/test_ods_importer.py +0 -0
  272. {execsql2-2.19.2 → execsql2-2.21.0}/tests/importers/test_xls_importer.py +0 -0
  273. {execsql2-2.19.2 → execsql2-2.21.0}/tests/integration/__init__.py +0 -0
  274. {execsql2-2.19.2 → execsql2-2.21.0}/tests/integration/conftest.py +0 -0
  275. {execsql2-2.19.2 → execsql2-2.21.0}/tests/integration/test_dsn.py +0 -0
  276. {execsql2-2.19.2 → execsql2-2.21.0}/tests/integration/test_duckdb.py +0 -0
  277. {execsql2-2.19.2 → execsql2-2.21.0}/tests/integration/test_mysql.py +0 -0
  278. {execsql2-2.19.2 → execsql2-2.21.0}/tests/integration/test_sqlite.py +0 -0
  279. {execsql2-2.19.2 → execsql2-2.21.0}/tests/metacommands/__init__.py +0 -0
  280. {execsql2-2.19.2 → execsql2-2.21.0}/tests/metacommands/test_assert.py +0 -0
  281. {execsql2-2.19.2 → execsql2-2.21.0}/tests/metacommands/test_breakpoint.py +0 -0
  282. {execsql2-2.19.2 → execsql2-2.21.0}/tests/metacommands/test_conditions_extra.py +0 -0
  283. {execsql2-2.19.2 → execsql2-2.21.0}/tests/metacommands/test_connect.py +0 -0
  284. {execsql2-2.19.2 → execsql2-2.21.0}/tests/metacommands/test_io_export.py +0 -0
  285. {execsql2-2.19.2 → execsql2-2.21.0}/tests/metacommands/test_io_import.py +0 -0
  286. {execsql2-2.19.2 → execsql2-2.21.0}/tests/metacommands/test_metacommands.py +0 -0
  287. {execsql2-2.19.2 → execsql2-2.21.0}/tests/metacommands/test_metacommands_connect.py +0 -0
  288. {execsql2-2.19.2 → execsql2-2.21.0}/tests/metacommands/test_metacommands_data.py +0 -0
  289. {execsql2-2.19.2 → execsql2-2.21.0}/tests/metacommands/test_metacommands_extended.py +0 -0
  290. {execsql2-2.19.2 → execsql2-2.21.0}/tests/metacommands/test_metacommands_fileops_extra.py +0 -0
  291. {execsql2-2.19.2 → execsql2-2.21.0}/tests/metacommands/test_metacommands_io.py +0 -0
  292. {execsql2-2.19.2 → execsql2-2.21.0}/tests/metacommands/test_metacommands_io_write_extra.py +0 -0
  293. {execsql2-2.19.2 → execsql2-2.21.0}/tests/metacommands/test_metacommands_script_ext.py +0 -0
  294. {execsql2-2.19.2 → execsql2-2.21.0}/tests/metacommands/test_metacommands_system.py +0 -0
  295. {execsql2-2.19.2 → execsql2-2.21.0}/tests/metacommands/test_metacommands_system_extra.py +0 -0
  296. {execsql2-2.19.2 → execsql2-2.21.0}/tests/metacommands/test_pg_upsert.py +0 -0
  297. {execsql2-2.19.2 → execsql2-2.21.0}/tests/metacommands/test_prompt.py +0 -0
  298. {execsql2-2.19.2 → execsql2-2.21.0}/tests/metacommands/test_row_count.py +0 -0
  299. {execsql2-2.19.2 → execsql2-2.21.0}/tests/metacommands/test_show_scripts.py +0 -0
  300. {execsql2-2.19.2 → execsql2-2.21.0}/tests/scripts/__init__.py +0 -0
  301. {execsql2-2.19.2 → execsql2-2.21.0}/tests/scripts/fixtures/audit_lint_bad.sql +0 -0
  302. {execsql2-2.19.2 → execsql2-2.21.0}/tests/scripts/fixtures/audit_smoke/sample.json +0 -0
  303. {execsql2-2.19.2 → execsql2-2.21.0}/tests/scripts/fixtures/audit_smoke/sample.jsonl +0 -0
  304. {execsql2-2.19.2 → execsql2-2.21.0}/tests/scripts/fixtures/audit_smoke.sql +0 -0
  305. {execsql2-2.19.2 → execsql2-2.21.0}/tests/scripts/fixtures/config_runtime.sql +0 -0
  306. {execsql2-2.19.2 → execsql2-2.21.0}/tests/scripts/fixtures/control_flow.sql +0 -0
  307. {execsql2-2.19.2 → execsql2-2.21.0}/tests/scripts/fixtures/counters_and_locals.sql +0 -0
  308. {execsql2-2.19.2 → execsql2-2.21.0}/tests/scripts/fixtures/error_handling.sql +0 -0
  309. {execsql2-2.19.2 → execsql2-2.21.0}/tests/scripts/fixtures/import_options/has_comments.csv +0 -0
  310. {execsql2-2.19.2 → execsql2-2.21.0}/tests/scripts/fixtures/import_options.sql +0 -0
  311. {execsql2-2.19.2 → execsql2-2.21.0}/tests/scripts/fixtures/includes/helper.sql +0 -0
  312. {execsql2-2.19.2 → execsql2-2.21.0}/tests/scripts/fixtures/includes.sql +0 -0
  313. {execsql2-2.19.2 → execsql2-2.21.0}/tests/scripts/fixtures/io_formats.sql +0 -0
  314. {execsql2-2.19.2 → execsql2-2.21.0}/tests/scripts/fixtures/io_roundtrip.sql +0 -0
  315. {execsql2-2.19.2 → execsql2-2.21.0}/tests/scripts/fixtures/multi_db.sql +0 -0
  316. {execsql2-2.19.2 → execsql2-2.21.0}/tests/scripts/fixtures/parquet_feather_roundtrip.sql +0 -0
  317. {execsql2-2.19.2 → execsql2-2.21.0}/tests/scripts/fixtures/parse_only/parse_tree.sql +0 -0
  318. {execsql2-2.19.2 → execsql2-2.21.0}/tests/scripts/fixtures/predicates.sql +0 -0
  319. {execsql2-2.19.2 → execsql2-2.21.0}/tests/scripts/fixtures/scripts.sql +0 -0
  320. {execsql2-2.19.2 → execsql2-2.21.0}/tests/scripts/fixtures/smoke.sql +0 -0
  321. {execsql2-2.19.2 → execsql2-2.21.0}/tests/scripts/fixtures/subroutine_loops.sql +0 -0
  322. {execsql2-2.19.2 → execsql2-2.21.0}/tests/scripts/fixtures/subvars_advanced.sql +0 -0
  323. {execsql2-2.19.2 → execsql2-2.21.0}/tests/scripts/fixtures/template_export/greeting.tmpl +0 -0
  324. {execsql2-2.19.2 → execsql2-2.21.0}/tests/scripts/fixtures/template_export.sql +0 -0
  325. {execsql2-2.19.2 → execsql2-2.21.0}/tests/scripts/fixtures/timer.sql +0 -0
  326. {execsql2-2.19.2 → execsql2-2.21.0}/tests/scripts/fixtures/transactions.sql +0 -0
  327. {execsql2-2.19.2 → execsql2-2.21.0}/tests/scripts/fixtures/write_create_table.sql +0 -0
  328. {execsql2-2.19.2 → execsql2-2.21.0}/tests/scripts/fixtures/xlsx_ods_roundtrip.sql +0 -0
  329. {execsql2-2.19.2 → execsql2-2.21.0}/tests/scripts/test_sql_scripts.py +0 -0
  330. {execsql2-2.19.2 → execsql2-2.21.0}/tests/security/__init__.py +0 -0
  331. {execsql2-2.19.2 → execsql2-2.21.0}/tests/security/test_env_detection.py +0 -0
  332. {execsql2-2.19.2 → execsql2-2.21.0}/tests/security/test_expansion_bomb.py +0 -0
  333. {execsql2-2.19.2 → execsql2-2.21.0}/tests/security/test_import_regex_hardening.py +0 -0
  334. {execsql2-2.19.2 → execsql2-2.21.0}/tests/security/test_odbc_injection.py +0 -0
  335. {execsql2-2.19.2 → execsql2-2.21.0}/tests/security/test_path_containment.py +0 -0
  336. {execsql2-2.19.2 → execsql2-2.21.0}/tests/security/test_substitution_injection.py +0 -0
  337. {execsql2-2.19.2 → execsql2-2.21.0}/tests/security/test_zip_bomb.py +0 -0
  338. {execsql2-2.19.2 → execsql2-2.21.0}/tests/test_api.py +0 -0
  339. {execsql2-2.19.2 → execsql2-2.21.0}/tests/test_api_extended.py +0 -0
  340. {execsql2-2.19.2 → execsql2-2.21.0}/tests/test_ast.py +0 -0
  341. {execsql2-2.19.2 → execsql2-2.21.0}/tests/test_ast_parser.py +0 -0
  342. {execsql2-2.19.2 → execsql2-2.21.0}/tests/test_bundled_templates.py +0 -0
  343. {execsql2-2.19.2 → execsql2-2.21.0}/tests/test_config.py +0 -0
  344. {execsql2-2.19.2 → execsql2-2.21.0}/tests/test_config_data.py +0 -0
  345. {execsql2-2.19.2 → execsql2-2.21.0}/tests/test_config_extended.py +0 -0
  346. {execsql2-2.19.2 → execsql2-2.21.0}/tests/test_debug_repl.py +0 -0
  347. {execsql2-2.19.2 → execsql2-2.21.0}/tests/test_engine.py +0 -0
  348. {execsql2-2.19.2 → execsql2-2.21.0}/tests/test_error_messages.py +0 -0
  349. {execsql2-2.19.2 → execsql2-2.21.0}/tests/test_exceptions.py +0 -0
  350. {execsql2-2.19.2 → execsql2-2.21.0}/tests/test_executor.py +0 -0
  351. {execsql2-2.19.2 → execsql2-2.21.0}/tests/test_executor_inprocess.py +0 -0
  352. {execsql2-2.19.2 → execsql2-2.21.0}/tests/test_models.py +0 -0
  353. {execsql2-2.19.2 → execsql2-2.21.0}/tests/test_package.py +0 -0
  354. {execsql2-2.19.2 → execsql2-2.21.0}/tests/test_parser.py +0 -0
  355. {execsql2-2.19.2 → execsql2-2.21.0}/tests/test_parser_params.py +0 -0
  356. {execsql2-2.19.2 → execsql2-2.21.0}/tests/test_plugins.py +0 -0
  357. {execsql2-2.19.2 → execsql2-2.21.0}/tests/test_registry.py +0 -0
  358. {execsql2-2.19.2 → execsql2-2.21.0}/tests/test_script.py +0 -0
  359. {execsql2-2.19.2 → execsql2-2.21.0}/tests/test_state.py +0 -0
  360. {execsql2-2.19.2 → execsql2-2.21.0}/tests/test_types.py +0 -0
  361. {execsql2-2.19.2 → execsql2-2.21.0}/tests/utils/__init__.py +0 -0
  362. {execsql2-2.19.2 → execsql2-2.21.0}/tests/utils/test_auth.py +0 -0
  363. {execsql2-2.19.2 → execsql2-2.21.0}/tests/utils/test_auth_extra.py +0 -0
  364. {execsql2-2.19.2 → execsql2-2.21.0}/tests/utils/test_crypto.py +0 -0
  365. {execsql2-2.19.2 → execsql2-2.21.0}/tests/utils/test_datetime.py +0 -0
  366. {execsql2-2.19.2 → execsql2-2.21.0}/tests/utils/test_encodedfile_context.py +0 -0
  367. {execsql2-2.19.2 → execsql2-2.21.0}/tests/utils/test_errors.py +0 -0
  368. {execsql2-2.19.2 → execsql2-2.21.0}/tests/utils/test_errors_extra.py +0 -0
  369. {execsql2-2.19.2 → execsql2-2.21.0}/tests/utils/test_fileio.py +0 -0
  370. {execsql2-2.19.2 → execsql2-2.21.0}/tests/utils/test_fileio_extra.py +0 -0
  371. {execsql2-2.19.2 → execsql2-2.21.0}/tests/utils/test_numeric.py +0 -0
  372. {execsql2-2.19.2 → execsql2-2.21.0}/tests/utils/test_regex.py +0 -0
  373. {execsql2-2.19.2 → execsql2-2.21.0}/tests/utils/test_strings.py +0 -0
  374. {execsql2-2.19.2 → execsql2-2.21.0}/tests/utils/test_timer.py +0 -0
  375. {execsql2-2.19.2 → execsql2-2.21.0}/tests/utils/test_timer_extra.py +0 -0
@@ -20,7 +20,7 @@ jobs:
20
20
  permissions:
21
21
  contents: read
22
22
  steps:
23
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
23
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
24
24
  - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
25
25
  with:
26
26
  python-version: "3.13"
@@ -48,7 +48,7 @@ jobs:
48
48
  contents: read
49
49
  steps:
50
50
  - name: Check out repository code
51
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
51
+ uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
52
52
  - name: Setup Python ${{ matrix.python-version }}
53
53
  uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
54
54
  with:
@@ -86,7 +86,7 @@ jobs:
86
86
  tox -e py
87
87
  - name: Upload coverage to Codecov
88
88
  if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.13'
89
- uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
89
+ uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
90
90
  with:
91
91
  token: ${{ secrets.CODECOV_TOKEN }}
92
92
  files: coverage.xml
@@ -139,7 +139,7 @@ jobs:
139
139
  --health-interval 10s --health-timeout 5s --health-retries 20
140
140
  steps:
141
141
  - name: Check out repository code
142
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
142
+ uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
143
143
  - name: Setup Python ${{ matrix.python-version }}
144
144
  uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
145
145
  with:
@@ -167,7 +167,7 @@ jobs:
167
167
  run: |
168
168
  python -m pytest --cov=execsql --cov-branch --cov-report=xml --cov-fail-under=86
169
169
  - name: Upload coverage to Codecov
170
- uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
170
+ uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
171
171
  with:
172
172
  token: ${{ secrets.CODECOV_TOKEN }}
173
173
  files: coverage.xml
@@ -175,9 +175,10 @@ jobs:
175
175
  access-tests-windows:
176
176
  name: access-tests-windows
177
177
  runs-on: windows-latest
178
- # Best-effort: Access Database Engine install can be flaky on hosted
179
- # runners. Failures don't gate the rest of the pipeline.
180
- continue-on-error: true
178
+ # The Access engine install step is best-effort (the redistributable
179
+ # download from Microsoft can be flaky on hosted runners). The
180
+ # downstream test step IS gating — a real Access regression should
181
+ # block the merge.
181
182
  strategy:
182
183
  fail-fast: false
183
184
  matrix:
@@ -186,11 +187,13 @@ jobs:
186
187
  permissions:
187
188
  contents: read
188
189
  steps:
189
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
190
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
190
191
  - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
191
192
  with:
192
193
  python-version: ${{ matrix.python-version }}
193
194
  - name: Install Microsoft Access Database Engine 2016 (x64)
195
+ id: install_access
196
+ continue-on-error: true
194
197
  # The chocolatey `accessdatabaseengine-x64` package was removed
195
198
  # from the community feed; download the redistributable directly
196
199
  # from Microsoft and run it silently instead.
@@ -206,12 +209,21 @@ jobs:
206
209
  # 0 = success, 3010 = success but reboot pending. Anything else is a failure.
207
210
  if ($proc.ExitCode -ne 0 -and $proc.ExitCode -ne 3010) { exit $proc.ExitCode }
208
211
  shell: pwsh
212
+ - name: Annotate skipped Access tests
213
+ if: steps.install_access.outcome != 'success'
214
+ run: |
215
+ Write-Host "::warning::Access Database Engine install failed; Access real-driver tests skipped this run."
216
+ shell: pwsh
209
217
  - name: Install Python dependencies
218
+ if: steps.install_access.outcome == 'success'
210
219
  run: |
211
220
  python -m pip install --upgrade pip
212
221
  python -m pip install ".[dev,odbc]" pywin32
213
222
  shell: pwsh
214
223
  - name: Run Access real-driver tests
224
+ # Gating: a real regression in src/execsql/db/access.py blocks
225
+ # the merge. Only runs when the install above succeeded.
226
+ if: steps.install_access.outcome == 'success'
215
227
  env:
216
228
  DISPLAY: ""
217
229
  run: |
@@ -226,7 +238,7 @@ jobs:
226
238
  permissions:
227
239
  contents: read
228
240
  steps:
229
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
241
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
230
242
  - name: Set up Python
231
243
  uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
232
244
  with:
@@ -276,7 +288,7 @@ jobs:
276
288
  contents: write
277
289
  steps:
278
290
  - name: Checkout
279
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
291
+ uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
280
292
  - name: Download dist
281
293
  uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
282
294
  with:
@@ -5,4 +5,5 @@
5
5
  language: python
6
6
  types: [file]
7
7
  files: \.sql$
8
+ args: [--in-place]
8
9
  additional_dependencies: [sqlglot]
@@ -9,7 +9,7 @@ build:
9
9
  python: "3.13"
10
10
  jobs:
11
11
  install:
12
- - pip install zensical mkdocstrings-python
12
+ - pip install "zensical==0.0.28" mkdocstrings-python
13
13
  - pip install -e .
14
14
  pre_build:
15
15
  - cp CHANGELOG.md docs/about/change_log.md
@@ -13,11 +13,44 @@ ______________________________________________________________________
13
13
 
14
14
  ______________________________________________________________________
15
15
 
16
+ ## [2.21.0] - 2026-06-24
17
+
18
+ ### Changed
19
+
20
+ - PostgreSQL connections now use psycopg3 (install the `postgres` extra, which pulls `psycopg[binary]`) instead of psycopg2. The `PG_UPSERT` metacommand now requires `pg-upsert>=1.23.0`.
21
+
22
+ ______________________________________________________________________
23
+
24
+ ## [2.20.0] - 2026-06-11
25
+
26
+ ### Added
27
+
28
+ - `execsql-format --encoding NAME` flag controls the text encoding used to read and write SQL files (default `utf-8`). Files saved by editors that emit cp1252, latin-1, shift_jis, etc. can now be formatted directly. A `UnicodeDecodeError` reports the file path and suggests `--encoding` instead of dumping a traceback.
29
+
30
+ ### Changed
31
+
32
+ - Published `execsql-format` pre-commit hook now defaults to `args: [--in-place]`. Downstream configs that listed `- id: execsql-format` with no `args:` previously got a silent no-op (the CLI's default is stdout, which pre-commit doesn't capture); they will now reformat files in place. Override with `args: [--check]` for check-only mode.
33
+ - Pre-commit `rev:` snippets in `README.md` and `docs/guides/formatter.md` now point at `v2.19.2` and are rewritten automatically by `bump-my-version` on every version bump, so they cannot drift.
34
+ - Env-var redaction filter expanded to skip mainstream secret-naming conventions (`AWS_ACCESS_KEY_ID`, `STRIPE_KEY`, `OPENAI_API_KEY`, `SENTRY_DSN`, `SLACK_WEBHOOK`, etc.). `-a` assignment log lines now always show `{***}` instead of the raw value. See `docs/reference/security.md#env_redaction` for the full list and known gaps (`DATABASE_URL`, `GITHUB_PAT`).
35
+ - The duplicate `execsql.conf` reference file shipped at `<sys.prefix>/execsql2_extras/execsql.conf` has been removed. The canonical template still ships inside the package and is loaded via `importlib.resources`; bootstrap a project conf with `execsql --init-config > execsql.conf` (the documented path).
36
+
37
+ ### Fixed
38
+
39
+ - `[firebird]` extra now connects: the adapter imports `firebird.driver` (provided by `firebird-driver`) instead of the missing `fdb` module. Users who installed `pip install execsql2[firebird]` previously hit `ModuleNotFoundError: No module named 'fdb'` at connect time.
40
+ - MySQL, SQL Server (pyodbc), MS Access (pyodbc), DSN (pyodbc), Firebird, and SMTP connections now use a 30-second connect timeout, matching the existing Postgres default. A silently-dropped peer no longer hangs a script (or a CI run) indefinitely.
41
+ - `execsql-format` now recognizes tagged `$body$ … $body$` / `$func$ … $func$` PL/pgSQL dollar quotes and skips sqlglot inside them, matching the existing `$$` behaviour. Tagged function bodies previously had their `IF / END IF / LOOP / RETURN` rewritten or collapsed.
42
+ - `execsql-format` now uppercases the second word of multi-word metacommands like `PROMPT MAP`, `PROMPT SAVEFILE`, `PROMPT OPENFILE`, `PROMPT CREDENTIALS`, `PROMPT SELECT_ROWS`, `PROMPT ASK COMPARE`, `APPEND SCRIPT`, `PG_UPSERT CHECK`/`QA`, `RESET COUNTER`/`DIALOG_CANCELED`, `SET COUNTER`, `WRITE CREATE_TABLE`/`SCRIPT`. Previously only the first word was uppercased, leaving the rest as the user typed.
43
+ - `execsql-format` no longer short-circuits on the first unreadable file; remaining files are still checked or formatted, and the run exits 1 at the end if any read error or pending change was found.
44
+ - `execsql-format --leading-comma` is now idempotent on SQL with mid-statement comments. The flag is no longer threaded through to sqlglot (which migrates inline `/* marker */` comments between passes); instead the formatter normalizes any leading-comma input to trailing-comma before sqlglot sees it, and applies leading commas as a textual post-pass on the assembled output.
45
+
46
+ ______________________________________________________________________
47
+
16
48
  ## [2.19.2] - 2026-06-03
17
49
 
18
50
  ### Fixed
19
51
 
20
- - `pre-commit` hook `execsql-format` now installs `sqlglot` into its isolated env. Since 2.19.0 moved `sqlglot` to the `[formatter]` extra, the hook env (which installs the bare package) was missing it and crashed with `ModuleNotFoundError: No module named 'sqlglot'`. Re-run `pre-commit clean && pre-commit install --install-hooks` after upgrading to pick up the new dependency.
52
+ - `pre-commit` hook `execsql-format` now installs `sqlglot` into its isolated env (previously crashed with `ModuleNotFoundError: No module named 'sqlglot'`).
53
+ - After upgrading, re-run `pre-commit clean && pre-commit install --install-hooks` to rebuild the hook env.
21
54
 
22
55
  ______________________________________________________________________
23
56
 
@@ -49,13 +82,7 @@ ______________________________________________________________________
49
82
 
50
83
  ## [2.18.1] - 2026-05-28
51
84
 
52
- ### Changed
53
-
54
- - Internal: `execsql.cli.lint_ast` has been folded into `execsql.cli.lint`; the AST walker entry point is now `execsql.cli.lint.lint()`. Code that imported `execsql.cli.lint_ast.lint_ast` should import `execsql.cli.lint.lint` instead.
55
-
56
- ### Removed
57
-
58
- - Internal: the unused `run_when_false` and `run_in_batch` flags on `MetaCommand` (and the matching keyword arguments on `MetaCommandList.add()`) — neither has been consulted since v2.16.0. Code that still passes either kwarg to `mcl.add()` will raise `TypeError`.
85
+ - Internal: `execsql.cli.lint_ast` is now `execsql.cli.lint.lint`; deprecated `MetaCommand.run_when_false` / `run_in_batch` kwargs removed.
59
86
 
60
87
  ______________________________________________________________________
61
88
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: execsql2
3
- Version: 2.19.2
3
+ Version: 2.21.0
4
4
  Summary: Runs a SQL script against a PostgreSQL, SQLite, MariaDB/MySQL, DuckDB, Firebird, MS-Access, MS-SQL-Server, or Oracle database, or an ODBC DSN. Provides metacommands to import and export data, copy data between databases, conditionally execute SQL and metacommands, and dynamically alter SQL and metacommands with substitution variables.
5
5
  Project-URL: Homepage, https://execsql2.readthedocs.io
6
6
  Project-URL: Repository, https://github.com/geocoug/execsql
@@ -44,90 +44,89 @@ Requires-Dist: rich>=13.0
44
44
  Requires-Dist: textual>=1.0
45
45
  Requires-Dist: typer>=0.12
46
46
  Provides-Extra: all
47
- Requires-Dist: defusedxml; extra == 'all'
48
- Requires-Dist: duckdb; extra == 'all'
49
- Requires-Dist: firebird-driver; extra == 'all'
50
- Requires-Dist: jinja2; extra == 'all'
51
- Requires-Dist: keyring; extra == 'all'
52
- Requires-Dist: odfpy; extra == 'all'
53
- Requires-Dist: openpyxl; extra == 'all'
54
- Requires-Dist: oracledb; extra == 'all'
55
- Requires-Dist: pg-upsert>=1.22.1; extra == 'all'
56
- Requires-Dist: polars; extra == 'all'
57
- Requires-Dist: psycopg2-binary; extra == 'all'
58
- Requires-Dist: pymysql; extra == 'all'
59
- Requires-Dist: pyodbc; extra == 'all'
60
- Requires-Dist: pyyaml; extra == 'all'
47
+ Requires-Dist: defusedxml>=0.7; extra == 'all'
48
+ Requires-Dist: duckdb>=1.0; extra == 'all'
49
+ Requires-Dist: firebird-driver>=1.10; extra == 'all'
50
+ Requires-Dist: jinja2>=3.1; extra == 'all'
51
+ Requires-Dist: keyring>=25.0; extra == 'all'
52
+ Requires-Dist: odfpy>=1.4; extra == 'all'
53
+ Requires-Dist: openpyxl>=3.1; extra == 'all'
54
+ Requires-Dist: oracledb>=3.0; extra == 'all'
55
+ Requires-Dist: pg-upsert>=1.23.0; extra == 'all'
56
+ Requires-Dist: polars>=1.0; extra == 'all'
57
+ Requires-Dist: psycopg[binary]<4,>=3.1; extra == 'all'
58
+ Requires-Dist: pymysql>=1.1; extra == 'all'
59
+ Requires-Dist: pyodbc>=5.0; extra == 'all'
60
+ Requires-Dist: pyyaml>=6.0; extra == 'all'
61
61
  Requires-Dist: sqlglot>=25.0; extra == 'all'
62
- Requires-Dist: tables; extra == 'all'
62
+ Requires-Dist: tables>=3.10; extra == 'all'
63
63
  Requires-Dist: tkintermapview>=1.29; extra == 'all'
64
- Requires-Dist: xlrd; extra == 'all'
64
+ Requires-Dist: xlrd>=2.0; extra == 'all'
65
65
  Provides-Extra: all-db
66
- Requires-Dist: duckdb; extra == 'all-db'
67
- Requires-Dist: firebird-driver; extra == 'all-db'
68
- Requires-Dist: oracledb; extra == 'all-db'
69
- Requires-Dist: psycopg2-binary; extra == 'all-db'
70
- Requires-Dist: pymysql; extra == 'all-db'
71
- Requires-Dist: pyodbc; extra == 'all-db'
66
+ Requires-Dist: duckdb>=1.0; extra == 'all-db'
67
+ Requires-Dist: firebird-driver>=1.10; extra == 'all-db'
68
+ Requires-Dist: oracledb>=3.0; extra == 'all-db'
69
+ Requires-Dist: psycopg[binary]<4,>=3.1; extra == 'all-db'
70
+ Requires-Dist: pymysql>=1.1; extra == 'all-db'
71
+ Requires-Dist: pyodbc>=5.0; extra == 'all-db'
72
72
  Provides-Extra: auth
73
- Requires-Dist: keyring; extra == 'auth'
73
+ Requires-Dist: keyring>=25.0; extra == 'auth'
74
74
  Provides-Extra: auth-encrypted
75
- Requires-Dist: keyring; extra == 'auth-encrypted'
76
- Requires-Dist: keyrings-alt; extra == 'auth-encrypted'
77
- Requires-Dist: pycryptodome; extra == 'auth-encrypted'
75
+ Requires-Dist: keyring>=25.0; extra == 'auth-encrypted'
76
+ Requires-Dist: keyrings-alt>=5.0; extra == 'auth-encrypted'
77
+ Requires-Dist: pycryptodome>=3.20; extra == 'auth-encrypted'
78
78
  Provides-Extra: auth-plaintext
79
- Requires-Dist: keyring; extra == 'auth-plaintext'
80
- Requires-Dist: keyrings-alt; extra == 'auth-plaintext'
79
+ Requires-Dist: keyring>=25.0; extra == 'auth-plaintext'
80
+ Requires-Dist: keyrings-alt>=5.0; extra == 'auth-plaintext'
81
81
  Provides-Extra: dev
82
82
  Requires-Dist: build>=1.2.2.post1; extra == 'dev'
83
83
  Requires-Dist: bump-my-version>=1.2.7; extra == 'dev'
84
- Requires-Dist: defusedxml; extra == 'dev'
85
- Requires-Dist: jinja2; extra == 'dev'
86
- Requires-Dist: markdown-include>=0.8; extra == 'dev'
84
+ Requires-Dist: defusedxml>=0.7; extra == 'dev'
85
+ Requires-Dist: jinja2>=3.1; extra == 'dev'
87
86
  Requires-Dist: mkdocstrings-python>=2.0.3; extra == 'dev'
88
87
  Requires-Dist: mypy>=1.10; extra == 'dev'
89
- Requires-Dist: odfpy; extra == 'dev'
90
- Requires-Dist: openpyxl; extra == 'dev'
91
- Requires-Dist: polars; extra == 'dev'
88
+ Requires-Dist: odfpy>=1.4; extra == 'dev'
89
+ Requires-Dist: openpyxl>=3.1; extra == 'dev'
90
+ Requires-Dist: polars>=1.0; extra == 'dev'
92
91
  Requires-Dist: pre-commit>=3.5.0; extra == 'dev'
93
92
  Requires-Dist: pytest-cov>=5.0.0; extra == 'dev'
94
- Requires-Dist: pyyaml; extra == 'dev'
93
+ Requires-Dist: pyyaml>=6.0; extra == 'dev'
95
94
  Requires-Dist: ruff>=0.4; extra == 'dev'
96
95
  Requires-Dist: sqlglot>=25.0; extra == 'dev'
97
- Requires-Dist: tables; extra == 'dev'
96
+ Requires-Dist: tables>=3.10; extra == 'dev'
98
97
  Requires-Dist: tox-uv>=1.13.1; extra == 'dev'
99
98
  Requires-Dist: twine>=6.1.0; extra == 'dev'
100
- Requires-Dist: xlrd; extra == 'dev'
101
- Requires-Dist: zensical>=0.0.28; extra == 'dev'
99
+ Requires-Dist: xlrd>=2.0; extra == 'dev'
100
+ Requires-Dist: zensical==0.0.28; extra == 'dev'
102
101
  Provides-Extra: duckdb
103
- Requires-Dist: duckdb; extra == 'duckdb'
102
+ Requires-Dist: duckdb>=1.0; extra == 'duckdb'
104
103
  Provides-Extra: firebird
105
- Requires-Dist: firebird-driver; extra == 'firebird'
104
+ Requires-Dist: firebird-driver>=1.10; extra == 'firebird'
106
105
  Provides-Extra: formats
107
- Requires-Dist: defusedxml; extra == 'formats'
108
- Requires-Dist: jinja2; extra == 'formats'
109
- Requires-Dist: odfpy; extra == 'formats'
110
- Requires-Dist: openpyxl; extra == 'formats'
111
- Requires-Dist: polars; extra == 'formats'
112
- Requires-Dist: pyyaml; extra == 'formats'
113
- Requires-Dist: tables; extra == 'formats'
114
- Requires-Dist: xlrd; extra == 'formats'
106
+ Requires-Dist: defusedxml>=0.7; extra == 'formats'
107
+ Requires-Dist: jinja2>=3.1; extra == 'formats'
108
+ Requires-Dist: odfpy>=1.4; extra == 'formats'
109
+ Requires-Dist: openpyxl>=3.1; extra == 'formats'
110
+ Requires-Dist: polars>=1.0; extra == 'formats'
111
+ Requires-Dist: pyyaml>=6.0; extra == 'formats'
112
+ Requires-Dist: tables>=3.10; extra == 'formats'
113
+ Requires-Dist: xlrd>=2.0; extra == 'formats'
115
114
  Provides-Extra: formatter
116
115
  Requires-Dist: sqlglot>=25.0; extra == 'formatter'
117
116
  Provides-Extra: map
118
117
  Requires-Dist: tkintermapview>=1.29; extra == 'map'
119
118
  Provides-Extra: mssql
120
- Requires-Dist: pyodbc; extra == 'mssql'
119
+ Requires-Dist: pyodbc>=5.0; extra == 'mssql'
121
120
  Provides-Extra: mysql
122
- Requires-Dist: pymysql; extra == 'mysql'
121
+ Requires-Dist: pymysql>=1.1; extra == 'mysql'
123
122
  Provides-Extra: odbc
124
- Requires-Dist: pyodbc; extra == 'odbc'
123
+ Requires-Dist: pyodbc>=5.0; extra == 'odbc'
125
124
  Provides-Extra: oracle
126
- Requires-Dist: oracledb; extra == 'oracle'
125
+ Requires-Dist: oracledb>=3.0; extra == 'oracle'
127
126
  Provides-Extra: postgres
128
- Requires-Dist: psycopg2-binary; extra == 'postgres'
127
+ Requires-Dist: psycopg[binary]<4,>=3.1; extra == 'postgres'
129
128
  Provides-Extra: upsert
130
- Requires-Dist: pg-upsert>=1.22.1; extra == 'upsert'
129
+ Requires-Dist: pg-upsert>=1.23.0; extra == 'upsert'
131
130
  Description-Content-Type: text/markdown
132
131
 
133
132
  > [!NOTE]
@@ -404,13 +403,12 @@ execsql-format --no-sql --in-place scripts/
404
403
  ```yaml
405
404
  repos:
406
405
  - repo: https://github.com/geocoug/execsql
407
- rev: v2.18.0
406
+ rev: v2.21.0
408
407
  hooks:
409
408
  - id: execsql-format
410
- args: [--in-place]
411
409
  ```
412
410
 
413
- See the [formatter documentation](https://execsql2.readthedocs.io/en/latest/guides/formatter/) for all options.
411
+ The hook rewrites `*.sql` files in place by default. See the [formatter documentation](https://execsql2.readthedocs.io/en/latest/guides/formatter/) for `--check`, `--indent`, and other options.
414
412
 
415
413
  # VS Code Syntax Highlighting
416
414
 
@@ -272,13 +272,12 @@ execsql-format --no-sql --in-place scripts/
272
272
  ```yaml
273
273
  repos:
274
274
  - repo: https://github.com/geocoug/execsql
275
- rev: v2.18.0
275
+ rev: v2.21.0
276
276
  hooks:
277
277
  - id: execsql-format
278
- args: [--in-place]
279
278
  ```
280
279
 
281
- See the [formatter documentation](https://execsql2.readthedocs.io/en/latest/guides/formatter/) for all options.
280
+ The hook rewrites `*.sql` files in place by default. See the [formatter documentation](https://execsql2.readthedocs.io/en/latest/guides/formatter/) for `--check`, `--indent`, and other options.
282
281
 
283
282
  # VS Code Syntax Highlighting
284
283
 
@@ -102,9 +102,15 @@ New options in `execsql.conf`:
102
102
 
103
103
  ### Tools
104
104
 
105
- | Tool | Description |
106
- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
107
- | `execsql-format` | Standalone CLI for normalizing metacommand indentation and uppercasing SQL keywords. Supports `--check`, `--in-place`, `--indent N` (controls both metacommand and SQL indentation), and `--leading-comma` (commas at start of lines) modes. Also available as a [pre-commit hook](../guides/formatter.md). SQL reformatting (the optional sqlglot pass) requires the `[formatter]` extra as of 2.19.0; `--no-sql` works without it. |
105
+ | Tool | Description |
106
+ | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
107
+ | `execsql-format` | Standalone CLI for normalizing metacommand indentation and uppercasing SQL keywords. Supports `--check`, `--in-place`, `--indent N` (controls both metacommand and SQL indentation), and `--leading-comma` (commas at start of lines) modes. Also available as a [pre-commit hook](../guides/formatter.md) — the published hook defaults to `args: [--in-place]` so downstream consumers get formatting on commit without extra wiring. SQL reformatting (the optional sqlglot pass) requires the `[formatter]` extra as of 2.19.0; `--no-sql` works without it. |
108
+
109
+ Formatter correctness notes added in the 2.19.x line:
110
+
111
+ - **2.19.1** — All four substitution-variable forms (`!!var!!`, `!'!var!'!`, `!"!var!"!`, and the deferred form `!{var}!`) are hidden from the sqlglot pass. Previously, `!'!var!'!` and `!"!var!"!` were parsed as `NOT` operators and rewritten — e.g. `!'!myvar!'!` → `NOT NOT '!myvar!'`. Scripts using quoted substitution variables are now safe to run through `execsql-format`.
112
+ - **2.19.2** — The published pre-commit hook declares `sqlglot` as an isolated-env dependency. The 2.19.0 move of `sqlglot` to the `[formatter]` extra meant the hook env (which installs the bare package) crashed with `ModuleNotFoundError: No module named 'sqlglot'` on first run. After upgrading, downstream users should run `pre-commit clean && pre-commit install --install-hooks` once to rebuild the env.
113
+ - **Tagged dollar quotes** — Tagged PL/pgSQL function bodies (`$body$ … $body$`, `$func$ … $func$`, any `$tag$`) are now recognized and skipped over by the formatter. Previously the tracker only saw untagged `$$`, so tagged function bodies were sent to sqlglot which mangled `IF / END IF / LOOP / RETURN`.
108
114
 
109
115
  ### GUI
110
116
 
@@ -204,6 +210,7 @@ All 33 mutable runtime globals in `state.py` have been consolidated into a `Runt
204
210
 
205
211
  ### Database Adapters
206
212
 
213
+ - **PostgreSQL driver is psycopg3** — the PostgreSQL adapter uses `psycopg` (psycopg3, installed via the `psycopg[binary]` extra) instead of upstream's `psycopg2`. The connection now passes the libpq `dbname` keyword (psycopg3 rejects psycopg2's `database`), `VACUUM` toggles `conn.autocommit` instead of the removed `set_session()`, server-side `COPY` import uses psycopg3's `cursor.copy()` context manager instead of `copy_expert()`, and binary `IMPORT` sends raw `bytes` instead of `psycopg2.Binary()`. This also makes `db.conn` directly usable by the `PG_UPSERT` metacommand's pg-upsert ≥1.23.0 dependency, which itself requires a psycopg3 connection.
207
214
  - **`Database` is an ABC** — `open_db()` and `exec_cmd()` are abstract methods. Subclasses that omit them raise `TypeError` at instantiation instead of at call time.
208
215
  - **Connection timeouts** — PostgreSQL and SQLite adapters accept a connection timeout parameter (default 30 seconds).
209
216
  - **DuckDB temporal types** — `TIMESTAMPTZ`, `TIMESTAMP`, `DATE`, `TIME` now map to native DuckDB types instead of `TEXT`.
@@ -256,10 +263,11 @@ These are behavioral changes driven by security or correctness issues in the ups
256
263
 
257
264
  ### Credential and Logging Safety
258
265
 
259
- | Area | Fix |
260
- | ---------------------------- | ------------------------------------------------------------------------------------------ |
261
- | ODBC password redaction | Connection strings in log output have `Pwd=***` substituted before logging. |
262
- | `enc_password` documentation | Prominent warnings that XOR encryption is obfuscation only — keys are hardcoded in source. |
266
+ | Area | Fix |
267
+ | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
268
+ | ODBC password redaction | Connection strings in log output have `Pwd=***` substituted before logging. |
269
+ | `enc_password` documentation | Prominent warnings that XOR encryption is obfuscation only — keys are hardcoded in source. |
270
+ | Env-var secret denylist | Extended `_SENSITIVE_SUBSTRINGS` to cover mainstream cloud / payment / observability / VCS conventions (`*_KEY`, `APIKEY`, `API_KEY`, `DSN`, `WEBHOOK`). `-a` positional assignment log lines now redact unconditionally (`{***}` instead of the raw value). |
263
271
 
264
272
  ### Bug Fixes
265
273
 
@@ -297,6 +305,8 @@ These are behavioral changes driven by security or correctness issues in the ups
297
305
  | Temp file creation TOCTOU race | `TempFileMgr.new_temp_fn()` discarded the `NamedTemporaryFile` handle, creating a race window. Now uses `tempfile.mkstemp()` for secure creation. |
298
306
  | `shlex.split` on Windows incorrect mode | Called without `posix=False` on Windows, mishandling backslash-heavy paths in SHELL commands. |
299
307
  | AST executor `~`/`+` variable scoping broken | The AST executor passed `localvars` through function parameters but never pushed `CommandList` frames onto `commandliststack`. Legacy metacommand handlers (`x_sub`, `x_rm_sub`, `xf_sub_defined`, `SUB_LOCAL`, prompt handlers, REPL) access `commandliststack[-1]` for `~` local and `+` outer-scope variables. This caused `~` vars to be invisible to SQL, `SUB_DEFINED(~var)` to always return false, and the REPL `.vars`/`.stack` to show empty state. Fixed by pushing/popping `CommandList` frames in `execute()` and `_execute_script_native()`. |
308
+ | Firebird adapter import | Adapter imported `fdb` (the unmaintained legacy driver) while the `[firebird]` extra declared `firebird-driver`. `pip install execsql2[firebird]` followed by a Firebird connect raised `ModuleNotFoundError: No module named 'fdb'`. Adapter now imports `firebird.driver`, matching the declared dependency. |
309
+ | Connect timeouts on network adapters | MySQL, SQL Server / Access / DSN (pyodbc), Firebird, and SMTP all now apply a 30 s connect timeout matching the existing PostgreSQL default. A silently-dropped network peer (firewall rule, dead VM) used to hang scripts indefinitely; now the connect fails fast. |
300
310
  | AST parser INCLUDE quoted paths broken | The AST parser captured the full INCLUDE target including surrounding quotes (`"path"`), but the legacy dispatch regex stripped them. Quoted INCLUDE paths failed with "File does not exist" even when the file was present. Fixed by stripping matched quote pairs in the parser. |
301
311
  | AST parser `BEGIN SCRIPT name(params)` rejected | The regex required whitespace between the script name and parameter list. `BEGIN SCRIPT foo(a,b)` (no space before `(`) silently failed to match, causing the matching `END SCRIPT` to raise "Unmatched END SCRIPT metacommand." Fixed by allowing optional whitespace before the parameter expression. |
302
312
  | AST executor forward SCRIPT references broken | The legacy engine registered all `BEGIN SCRIPT` blocks at parse time (two-pass), so `EXECUTE SCRIPT foo` could appear before the `BEGIN SCRIPT foo` definition. The AST executor walked the tree in a single pass, so forward references failed with "There is no SCRIPT named foo." Fixed by adding a pre-registration scan of all SCRIPT blocks before execution begins. |
@@ -59,7 +59,7 @@ class MyDBDatabase(Database):
59
59
  self.need_passwd = False
60
60
  self.encoding = "UTF-8"
61
61
  self.encode_commands = False
62
- self.paramstr = "?" # placeholder style: "?" for most drivers, "%s" for psycopg2
62
+ self.paramstr = "?" # placeholder style: "?" for most drivers, "%s" for psycopg
63
63
  self.conn = None
64
64
  self.autocommit = True
65
65
  self.open_db()
@@ -137,7 +137,7 @@ These are the instance attributes and methods you must configure correctly:
137
137
  | Attribute / Method | Type | Purpose |
138
138
  | ---------------------- | ----------------- | --------------------------------------------------------------------------------- |
139
139
  | `self.type` | `DbType` | DBMS type token (e.g., `dbt_sqlite`). Controls quoting and type-mapping. |
140
- | `self.paramstr` | `str` | SQL parameter placeholder: `"?"` (most drivers) or `"%s"` (psycopg2, PyMySQL). |
140
+ | `self.paramstr` | `str` | SQL parameter placeholder: `"?"` (most drivers) or `"%s"` (psycopg, PyMySQL). |
141
141
  | `self.encoding` | `str` | Database character encoding. Detect from the database on connect if possible. |
142
142
  | `self.encode_commands` | `bool` | `True` if SQL strings should be encoded before passing to the driver. |
143
143
  | `self.autocommit` | `bool` | `True` means the driver commits automatically; `False` requires explicit commits. |
@@ -118,6 +118,32 @@ Use `--parse-tree` to print the AST without executing.
118
118
 
119
119
  `RuntimeContext` (in `state.py`) holds the per-run mutable state. `execute()` accepts an explicit `ctx`; the `active_context()` context manager installs one as the active thread-local so metacommand handlers and database adapters resolve against it automatically. Each thread gets its own context via `threading.local()`, enabling concurrent `execsql.run()` calls. The context carries the AST script registry (`ast_scripts`), the include cycle detector (`include_chain`), and the unified execution stack (`ast_exec_stack`) — a list of `ExecFrame` records describing every active scope, IF/LOOP/BATCH block, and INCLUDE'd file. Scope frames (`kind="main"` / `kind="script"`) hold the active `localvars` and `paramvals`; block frames cache a `scope_ref` to the enclosing scope for O(1) variable lookup. `current_script_line()` reads `ctx.last_command`, which the executor updates per statement.
120
120
 
121
+ ### Dispatch vs. AST executor — the stub seam
122
+
123
+ A handful of keywords appear in **both** the metacommand dispatch table and the AST grammar. The dispatch entries are intentional stubs that raise `ErrInfo` at runtime if reached, and exist only so the keyword tables and tooling stay complete.
124
+
125
+ | Keyword | Source |
126
+ | ----------------------------------------------------- | ------------------------------------------------------------ |
127
+ | `IF` / `ANDIF` / `ORIF` / `ELSEIF` / `ELSE` / `ENDIF` | `metacommands/control.py` raises `_ast_only_stub("IF")` etc. |
128
+ | `LOOP` / `BREAK` | same |
129
+ | `BEGIN BATCH` / `END BATCH` | same |
130
+
131
+ What the stubs are for:
132
+
133
+ - **`--dump-keywords`** walks the dispatch table to emit the canonical keyword list. The VS Code grammar in `extras/vscode-execsql/syntaxes/execsql.tmLanguage.json` is regenerated from `--dump-keywords` output (see `scripts/generate_vscode_grammar.py`). Removing the dispatch entries would silently shrink that grammar and lose highlighting for the affected keywords.
134
+ - **Reachability is impossible at runtime.** The AST parser owns these constructs and they never bottom out in `_exec_metacommand()`. If a stub *does* raise, that means the parser missed a structural case — file a bug rather than implementing the dispatch path.
135
+
136
+ A new contributor who greps for `ErrInfo: AST-only` lands in `metacommands/control.py:_ast_only_stub`; the same logic applies to any future keyword that the AST parser owns.
137
+
138
+ ### Legacy `commandliststack` residue
139
+
140
+ `state.py` still exposes `commandliststack` (and `if_stack`, `savedscripts`) as `RuntimeContext` attributes for backwards compatibility with two surfaces:
141
+
142
+ - **`metacommands/debug.py`** — the `x_debug_commandliststack` REPL helper and a few diagnostic prints read `_state.commandliststack[-1]` to surface the active local-variable frame.
143
+ - **`state.py` proxy** — kept as a slot on `RuntimeContext` so external code that imported `from execsql.state import commandliststack` keeps importing.
144
+
145
+ These are **read-only diagnostic surfaces.** New control-flow or scope code must go through `ctx.ast_exec_stack` / `ExecFrame`; the legacy stack is no longer the source of truth and is not pushed/popped by the AST executor in the way the old flat-command-list engine did. Treat `commandliststack` as a debug-only window onto `ctx.localvars` / `ctx.paramvals`.
146
+
121
147
  ______________________________________________________________________
122
148
 
123
149
  ## Plugin System
@@ -0,0 +1,124 @@
1
+ # Release Process
2
+
3
+ This page documents the release workflow for `execsql2`. The release is
4
+ fully automated — the version-bump commit (plus its companion tag) is
5
+ the trigger; `.github/workflows/ci-cd.yml` does the rest.
6
+
7
+ ## Prerequisites
8
+
9
+ - Working tree clean on `main` (or whatever branch you are releasing
10
+ from).
11
+ - Local tests green: `just check`.
12
+ - `CHANGELOG.md` `[Unreleased]` section reviewed and tightened to the
13
+ Keep-a-Changelog voice rule (one idea per bullet, user-facing tone,
14
+ no internal-only refactor notes). See `CLAUDE.md` for the full rule.
15
+ - `docs/about/divergence.md` updated for any new feature, changed
16
+ behavior, security fix, or removed functionality that diverges from
17
+ upstream `execsql` v1.130.1.
18
+ - You have `gh` authenticated against the `geocoug/execsql` repository
19
+ (`gh auth status` shows you're logged in).
20
+
21
+ ## Choose the bump level
22
+
23
+ | Recipe | Use when |
24
+ | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- |
25
+ | `just bump-patch` | Bug fixes, doc fixes, dependency hygiene, anything user-invisible. Most releases. |
26
+ | `just bump-minor` | New features, new metacommands / flags / formats, backwards-compatible behavior changes. |
27
+ | `just bump-major` | Breaking changes. Public-API signature changes, removed metacommands, dropped Python versions. |
28
+ | `just bump-pre 2.20.0a1` | Cut an explicit pre-release (alpha / beta / rc). Doesn't fire the publish workflow unless `--allow-dirty` and a tag-style argument trigger it. |
29
+
30
+ What each one does (per `[tool.bumpversion]` in `pyproject.toml`):
31
+
32
+ 1. Rewrites the version in `pyproject.toml`, `CHANGELOG.md` (turns
33
+ `[Unreleased]` into the new dated section), `README.md`'s pre-commit
34
+ `rev:` snippet, and `docs/guides/formatter.md`'s pre-commit `rev:`
35
+ snippet.
36
+ 1. Runs `uv lock` (refreshes the lockfile against the new version) and
37
+ `git add uv.lock`.
38
+ 1. Creates a commit `Bump version: 2.X.Y → 2.X.Y+1`.
39
+ 1. Creates an annotated tag `v2.X.Y+1` on that commit.
40
+
41
+ The working tree is now clean.
42
+
43
+ ## Push and watch CI
44
+
45
+ ```bash
46
+ git push && git push --tags # commit AND tag, both required
47
+ gh run list --limit 1 # confirm the workflow fired
48
+ gh run watch <RUN_ID> --exit-status # block until it finishes (red on failure)
49
+ ```
50
+
51
+ `gh run watch --exit-status` returns non-zero if any job in the workflow
52
+ fails. Stay on the command until it exits.
53
+
54
+ ### What runs on a tag push
55
+
56
+ | Job | Gating? | Purpose |
57
+ | ---------------------- | ---------------------------------- | ---------------------------------------------------------------- |
58
+ | `lint` | yes | ruff check + ruff format check |
59
+ | `tests` (matrix) | yes | py3.10–3.14 × {ubuntu, macos, windows} |
60
+ | `integration-tests` | yes | PostgreSQL, MySQL, MSSQL service containers |
61
+ | `access-tests-windows` | yes (when Access install succeeds) | Real Access driver on `windows-latest` |
62
+ | `build` | yes | `python -m build` produces sdist + wheel |
63
+ | `publish` | yes (tag-gated) | OIDC trusted-publisher PyPI upload |
64
+ | `generate-release` | yes (tag-gated) | Creates the GitHub release with auto-extracted CHANGELOG section |
65
+
66
+ Build / publish / generate-release run only on tag refs
67
+ (`if: startsWith(github.ref, 'refs/tags/v')`), so a non-bump push to
68
+ `main` doesn't publish.
69
+
70
+ ## When something goes wrong
71
+
72
+ ### A test failed after the tag push (PyPI not yet published)
73
+
74
+ The `build` job won't run if any earlier job is red, so `publish` won't
75
+ fire. Fix:
76
+
77
+ 1. Identify the failure: `gh run view <RUN_ID> --log-failed`.
78
+ 1. Fix it on `main`.
79
+ 1. Re-bump: `just bump-patch` (this creates a *new* tag at the next patch
80
+ level; do NOT delete the old tag and retag the new commit — once a
81
+ tag has been visible publicly, treat it as immutable).
82
+ 1. Push and re-watch.
83
+
84
+ ### `publish` failed but the tag is live
85
+
86
+ This is the worst case: PyPI is unaware, GitHub thinks the release
87
+ happened. Fix:
88
+
89
+ 1. `gh run view <RUN_ID> --log-failed` and read the publish job log.
90
+ 1. Common cause: OIDC trust-policy drift, transient PyPI 5xx, or a name
91
+ collision with the pre-release tag.
92
+ 1. If transient: `gh run rerun --failed <RUN_ID>` and watch again.
93
+ 1. If structural (trust policy changed, package name conflict): delete
94
+ the GitHub release **but not the tag**, fix the cause, and re-trigger
95
+ the workflow manually via the Actions UI. The tag is the source of
96
+ truth for the version; don't reassign it.
97
+
98
+ ### `generate-release` succeeded but the CHANGELOG section is wrong
99
+
100
+ The release-notes step in the workflow extracts the CHANGELOG section
101
+ matching `## [<version>]`. Edit the GitHub release body directly through
102
+ the UI or `gh release edit v2.X.Y --notes-file <path>`. Then fix the
103
+ voice in `CHANGELOG.md` on `main` so future releases don't repeat the
104
+ mistake.
105
+
106
+ ### `uv lock` produced an unrelated diff
107
+
108
+ `bump-my-version` runs `uv lock` as a pre-commit hook to refresh the
109
+ lockfile. If unrelated packages also moved (because they updated since
110
+ your last sync), that's expected — `uv` is doing the right thing. If the
111
+ diff is large enough to be worrying, abort the bump (`git restore .`),
112
+ run `uv lock` manually, review, commit the lock update on its own, then
113
+ re-bump.
114
+
115
+ ## Post-release sanity check
116
+
117
+ After `gh run watch` exits green:
118
+
119
+ - `pip install execsql2==2.X.Y` in a throwaway venv — confirms the
120
+ wheel landed on PyPI.
121
+ - Browse the new GitHub release page; check the CHANGELOG section
122
+ matches what's on `main`.
123
+ - `git pull` locally to retrieve the bump commit (you already had it
124
+ locally if you bumped yourself, but other contributors will sync).