execsql2 2.22.2__tar.gz → 2.22.3__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 (377) hide show
  1. {execsql2-2.22.2 → execsql2-2.22.3}/.gitignore +3 -1
  2. {execsql2-2.22.2 → execsql2-2.22.3}/CHANGELOG.md +8 -0
  3. {execsql2-2.22.2 → execsql2-2.22.3}/PKG-INFO +2 -2
  4. {execsql2-2.22.2 → execsql2-2.22.3}/README.md +1 -1
  5. {execsql2-2.22.2 → execsql2-2.22.3}/docs/about/divergence.md +1 -0
  6. {execsql2-2.22.2 → execsql2-2.22.3}/docs/guides/formatter.md +1 -1
  7. {execsql2-2.22.2 → execsql2-2.22.3}/pyproject.toml +2 -2
  8. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/format.py +12 -1
  9. {execsql2-2.22.2 → execsql2-2.22.3}/tests/test_format.py +96 -0
  10. {execsql2-2.22.2 → execsql2-2.22.3}/uv.lock +1 -1
  11. {execsql2-2.22.2 → execsql2-2.22.3}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  12. {execsql2-2.22.2 → execsql2-2.22.3}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  13. {execsql2-2.22.2 → execsql2-2.22.3}/.github/dependabot.yml +0 -0
  14. {execsql2-2.22.2 → execsql2-2.22.3}/.github/workflows/ci-cd.yml +0 -0
  15. {execsql2-2.22.2 → execsql2-2.22.3}/.pre-commit-config.yaml +0 -0
  16. {execsql2-2.22.2 → execsql2-2.22.3}/.pre-commit-hooks.yaml +0 -0
  17. {execsql2-2.22.2 → execsql2-2.22.3}/.python-version +0 -0
  18. {execsql2-2.22.2 → execsql2-2.22.3}/.readthedocs.yaml +0 -0
  19. {execsql2-2.22.2 → execsql2-2.22.3}/CONTRIBUTING.md +0 -0
  20. {execsql2-2.22.2 → execsql2-2.22.3}/LICENSE.txt +0 -0
  21. {execsql2-2.22.2 → execsql2-2.22.3}/NOTICE +0 -0
  22. {execsql2-2.22.2 → execsql2-2.22.3}/SECURITY.md +0 -0
  23. {execsql2-2.22.2 → execsql2-2.22.3}/docs/about/contributors.md +0 -0
  24. {execsql2-2.22.2 → execsql2-2.22.3}/docs/about/copyright.md +0 -0
  25. {execsql2-2.22.2 → execsql2-2.22.3}/docs/api/cli.md +0 -0
  26. {execsql2-2.22.2 → execsql2-2.22.3}/docs/api/db.md +0 -0
  27. {execsql2-2.22.2 → execsql2-2.22.3}/docs/api/exporters.md +0 -0
  28. {execsql2-2.22.2 → execsql2-2.22.3}/docs/api/importers.md +0 -0
  29. {execsql2-2.22.2 → execsql2-2.22.3}/docs/api/index.md +0 -0
  30. {execsql2-2.22.2 → execsql2-2.22.3}/docs/api/metacommands.md +0 -0
  31. {execsql2-2.22.2 → execsql2-2.22.3}/docs/dev/adding_db_adapters.md +0 -0
  32. {execsql2-2.22.2 → execsql2-2.22.3}/docs/dev/adding_exporters.md +0 -0
  33. {execsql2-2.22.2 → execsql2-2.22.3}/docs/dev/adding_importers.md +0 -0
  34. {execsql2-2.22.2 → execsql2-2.22.3}/docs/dev/adding_metacommands.md +0 -0
  35. {execsql2-2.22.2 → execsql2-2.22.3}/docs/dev/architecture.md +0 -0
  36. {execsql2-2.22.2 → execsql2-2.22.3}/docs/dev/releasing.md +0 -0
  37. {execsql2-2.22.2 → execsql2-2.22.3}/docs/getting-started/installation.md +0 -0
  38. {execsql2-2.22.2 → execsql2-2.22.3}/docs/getting-started/requirements.md +0 -0
  39. {execsql2-2.22.2 → execsql2-2.22.3}/docs/getting-started/syntax.md +0 -0
  40. {execsql2-2.22.2 → execsql2-2.22.3}/docs/guides/debugging.md +0 -0
  41. {execsql2-2.22.2 → execsql2-2.22.3}/docs/guides/documentation.md +0 -0
  42. {execsql2-2.22.2 → execsql2-2.22.3}/docs/guides/encoding.md +0 -0
  43. {execsql2-2.22.2 → execsql2-2.22.3}/docs/guides/examples.md +0 -0
  44. {execsql2-2.22.2 → execsql2-2.22.3}/docs/guides/logging.md +0 -0
  45. {execsql2-2.22.2 → execsql2-2.22.3}/docs/guides/sql_syntax.md +0 -0
  46. {execsql2-2.22.2 → execsql2-2.22.3}/docs/guides/usage.md +0 -0
  47. {execsql2-2.22.2 → execsql2-2.22.3}/docs/guides/using_scripts.md +0 -0
  48. {execsql2-2.22.2 → execsql2-2.22.3}/docs/images/Compare_planets.png +0 -0
  49. {execsql2-2.22.2 → execsql2-2.22.3}/docs/images/actions.png +0 -0
  50. {execsql2-2.22.2 → execsql2-2.22.3}/docs/images/actions2.png +0 -0
  51. {execsql2-2.22.2 → execsql2-2.22.3}/docs/images/checkboxes.png +0 -0
  52. {execsql2-2.22.2 → execsql2-2.22.3}/docs/images/connect.b64 +0 -0
  53. {execsql2-2.22.2 → execsql2-2.22.3}/docs/images/connect.png +0 -0
  54. {execsql2-2.22.2 → execsql2-2.22.3}/docs/images/create_conf.png +0 -0
  55. {execsql2-2.22.2 → execsql2-2.22.3}/docs/images/data_error1_screenshot.jpg +0 -0
  56. {execsql2-2.22.2 → execsql2-2.22.3}/docs/images/entry_form.png +0 -0
  57. {execsql2-2.22.2 → execsql2-2.22.3}/docs/images/execsql_console.png +0 -0
  58. {execsql2-2.22.2 → execsql2-2.22.3}/docs/images/execsql_logo_01.png +0 -0
  59. {execsql2-2.22.2 → execsql2-2.22.3}/docs/images/fatals.png +0 -0
  60. {execsql2-2.22.2 → execsql2-2.22.3}/docs/images/logo_small.png +0 -0
  61. {execsql2-2.22.2 → execsql2-2.22.3}/docs/images/pause_terminal.png +0 -0
  62. {execsql2-2.22.2 → execsql2-2.22.3}/docs/images/pause_terminal_sm.b64 +0 -0
  63. {execsql2-2.22.2 → execsql2-2.22.3}/docs/images/pause_terminal_sm.png +0 -0
  64. {execsql2-2.22.2 → execsql2-2.22.3}/docs/images/prompt_compare.png +0 -0
  65. {execsql2-2.22.2 → execsql2-2.22.3}/docs/images/set_build_commands.jpg +0 -0
  66. {execsql2-2.22.2 → execsql2-2.22.3}/docs/images/unit_conversions.b64 +0 -0
  67. {execsql2-2.22.2 → execsql2-2.22.3}/docs/images/unit_conversions_029.png +0 -0
  68. {execsql2-2.22.2 → execsql2-2.22.3}/docs/images/unmatched.png +0 -0
  69. {execsql2-2.22.2 → execsql2-2.22.3}/docs/images/vim_execsql_highlight.png +0 -0
  70. {execsql2-2.22.2 → execsql2-2.22.3}/docs/index.md +0 -0
  71. {execsql2-2.22.2 → execsql2-2.22.3}/docs/reference/configuration.md +0 -0
  72. {execsql2-2.22.2 → execsql2-2.22.3}/docs/reference/metacommands.md +0 -0
  73. {execsql2-2.22.2 → execsql2-2.22.3}/docs/reference/security.md +0 -0
  74. {execsql2-2.22.2 → execsql2-2.22.3}/docs/reference/substitution_vars.md +0 -0
  75. {execsql2-2.22.2 → execsql2-2.22.3}/extras/plugin-template/README.md +0 -0
  76. {execsql2-2.22.2 → execsql2-2.22.3}/extras/plugin-template/pyproject.toml +0 -0
  77. {execsql2-2.22.2 → execsql2-2.22.3}/extras/plugin-template/src/execsql_plugin_YOURNAME/__init__.py +0 -0
  78. {execsql2-2.22.2 → execsql2-2.22.3}/extras/plugin-template/tests/test_plugin.py.example +0 -0
  79. {execsql2-2.22.2 → execsql2-2.22.3}/extras/vscode-execsql/README.md +0 -0
  80. {execsql2-2.22.2 → execsql2-2.22.3}/extras/vscode-execsql/package.json +0 -0
  81. {execsql2-2.22.2 → execsql2-2.22.3}/extras/vscode-execsql/syntaxes/execsql.tmLanguage.json +0 -0
  82. {execsql2-2.22.2 → execsql2-2.22.3}/justfile +0 -0
  83. {execsql2-2.22.2 → execsql2-2.22.3}/scripts/benchmarks/README.md +0 -0
  84. {execsql2-2.22.2 → execsql2-2.22.3}/scripts/benchmarks/results/.gitignore +0 -0
  85. {execsql2-2.22.2 → execsql2-2.22.3}/scripts/benchmarks/run_benchmarks.py +0 -0
  86. {execsql2-2.22.2 → execsql2-2.22.3}/scripts/generate_vscode_grammar.py +0 -0
  87. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/__init__.py +0 -0
  88. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/__main__.py +0 -0
  89. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/api.py +0 -0
  90. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/cli/__init__.py +0 -0
  91. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/cli/dsn.py +0 -0
  92. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/cli/help.py +0 -0
  93. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/cli/lint.py +0 -0
  94. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/cli/run.py +0 -0
  95. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/config.py +0 -0
  96. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/data/__init__.py +0 -0
  97. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/data/execsql.conf.template +0 -0
  98. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/db/__init__.py +0 -0
  99. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/db/access.py +0 -0
  100. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/db/base.py +0 -0
  101. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/db/dsn.py +0 -0
  102. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/db/duckdb.py +0 -0
  103. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/db/factory.py +0 -0
  104. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/db/firebird.py +0 -0
  105. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/db/mysql.py +0 -0
  106. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/db/oracle.py +0 -0
  107. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/db/postgres.py +0 -0
  108. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/db/sqlite.py +0 -0
  109. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/db/sqlserver.py +0 -0
  110. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/debug/__init__.py +0 -0
  111. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/debug/repl.py +0 -0
  112. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/exceptions.py +0 -0
  113. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/exporters/__init__.py +0 -0
  114. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/exporters/base.py +0 -0
  115. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/exporters/delimited.py +0 -0
  116. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/exporters/duckdb.py +0 -0
  117. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/exporters/feather.py +0 -0
  118. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/exporters/html.py +0 -0
  119. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/exporters/json.py +0 -0
  120. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/exporters/latex.py +0 -0
  121. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/exporters/markdown.py +0 -0
  122. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/exporters/ods.py +0 -0
  123. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/exporters/parquet.py +0 -0
  124. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/exporters/pretty.py +0 -0
  125. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/exporters/protocol.py +0 -0
  126. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/exporters/raw.py +0 -0
  127. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/exporters/sqlite.py +0 -0
  128. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/exporters/templates.py +0 -0
  129. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/exporters/values.py +0 -0
  130. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/exporters/xls.py +0 -0
  131. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/exporters/xlsx.py +0 -0
  132. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/exporters/xml.py +0 -0
  133. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/exporters/yaml.py +0 -0
  134. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/exporters/zip.py +0 -0
  135. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/gui/__init__.py +0 -0
  136. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/gui/base.py +0 -0
  137. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/gui/console.py +0 -0
  138. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/gui/desktop.py +0 -0
  139. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/gui/tui.py +0 -0
  140. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/importers/__init__.py +0 -0
  141. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/importers/base.py +0 -0
  142. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/importers/csv.py +0 -0
  143. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/importers/feather.py +0 -0
  144. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/importers/json.py +0 -0
  145. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/importers/ods.py +0 -0
  146. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/importers/xls.py +0 -0
  147. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/metacommands/__init__.py +0 -0
  148. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/metacommands/conditions.py +0 -0
  149. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/metacommands/connect.py +0 -0
  150. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/metacommands/control.py +0 -0
  151. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/metacommands/data.py +0 -0
  152. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/metacommands/debug.py +0 -0
  153. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/metacommands/dispatch.py +0 -0
  154. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/metacommands/io.py +0 -0
  155. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/metacommands/io_export.py +0 -0
  156. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/metacommands/io_fileops.py +0 -0
  157. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/metacommands/io_import.py +0 -0
  158. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/metacommands/io_write.py +0 -0
  159. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/metacommands/prompt.py +0 -0
  160. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/metacommands/script_ext.py +0 -0
  161. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/metacommands/system.py +0 -0
  162. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/metacommands/upsert.py +0 -0
  163. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/models.py +0 -0
  164. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/parser.py +0 -0
  165. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/plugins.py +0 -0
  166. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/py.typed +0 -0
  167. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/script/__init__.py +0 -0
  168. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/script/ast.py +0 -0
  169. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/script/control.py +0 -0
  170. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/script/engine.py +0 -0
  171. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/script/executor.py +0 -0
  172. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/script/parser.py +0 -0
  173. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/script/variables.py +0 -0
  174. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/state.py +0 -0
  175. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/state.pyi +0 -0
  176. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/types.py +0 -0
  177. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/utils/__init__.py +0 -0
  178. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/utils/auth.py +0 -0
  179. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/utils/crypto.py +0 -0
  180. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/utils/datetime.py +0 -0
  181. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/utils/errors.py +0 -0
  182. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/utils/fileio.py +0 -0
  183. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/utils/gui.py +0 -0
  184. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/utils/mail.py +0 -0
  185. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/utils/numeric.py +0 -0
  186. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/utils/regex.py +0 -0
  187. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/utils/strings.py +0 -0
  188. {execsql2-2.22.2 → execsql2-2.22.3}/src/execsql/utils/timer.py +0 -0
  189. {execsql2-2.22.2 → execsql2-2.22.3}/templates/README.md +0 -0
  190. {execsql2-2.22.2 → execsql2-2.22.3}/templates/config_settings.sqlite +0 -0
  191. {execsql2-2.22.2 → execsql2-2.22.3}/templates/example_config_prompt.sql +0 -0
  192. {execsql2-2.22.2 → execsql2-2.22.3}/templates/make_config_db.sql +0 -0
  193. {execsql2-2.22.2 → execsql2-2.22.3}/templates/md_compare.sql +0 -0
  194. {execsql2-2.22.2 → execsql2-2.22.3}/templates/md_glossary.sql +0 -0
  195. {execsql2-2.22.2 → execsql2-2.22.3}/templates/md_upsert.sql +0 -0
  196. {execsql2-2.22.2 → execsql2-2.22.3}/templates/pg_compare.sql +0 -0
  197. {execsql2-2.22.2 → execsql2-2.22.3}/templates/pg_glossary.sql +0 -0
  198. {execsql2-2.22.2 → execsql2-2.22.3}/templates/pg_upsert.sql +0 -0
  199. {execsql2-2.22.2 → execsql2-2.22.3}/templates/script_template.sql +0 -0
  200. {execsql2-2.22.2 → execsql2-2.22.3}/templates/ss_compare.sql +0 -0
  201. {execsql2-2.22.2 → execsql2-2.22.3}/templates/ss_glossary.sql +0 -0
  202. {execsql2-2.22.2 → execsql2-2.22.3}/templates/ss_upsert.sql +0 -0
  203. {execsql2-2.22.2 → execsql2-2.22.3}/tests/__init__.py +0 -0
  204. {execsql2-2.22.2 → execsql2-2.22.3}/tests/cli/__init__.py +0 -0
  205. {execsql2-2.22.2 → execsql2-2.22.3}/tests/cli/test_cli.py +0 -0
  206. {execsql2-2.22.2 → execsql2-2.22.3}/tests/cli/test_cli_e2e.py +0 -0
  207. {execsql2-2.22.2 → execsql2-2.22.3}/tests/cli/test_cli_run.py +0 -0
  208. {execsql2-2.22.2 → execsql2-2.22.3}/tests/cli/test_lint.py +0 -0
  209. {execsql2-2.22.2 → execsql2-2.22.3}/tests/cli/test_ping.py +0 -0
  210. {execsql2-2.22.2 → execsql2-2.22.3}/tests/cli/test_profile.py +0 -0
  211. {execsql2-2.22.2 → execsql2-2.22.3}/tests/conftest.py +0 -0
  212. {execsql2-2.22.2 → execsql2-2.22.3}/tests/db/__init__.py +0 -0
  213. {execsql2-2.22.2 → execsql2-2.22.3}/tests/db/test_access_windows.py +0 -0
  214. {execsql2-2.22.2 → execsql2-2.22.3}/tests/db/test_base.py +0 -0
  215. {execsql2-2.22.2 → execsql2-2.22.3}/tests/db/test_db_adapters_mocked.py +0 -0
  216. {execsql2-2.22.2 → execsql2-2.22.3}/tests/db/test_db_adapters_mocked_extra.py +0 -0
  217. {execsql2-2.22.2 → execsql2-2.22.3}/tests/db/test_dsn.py +0 -0
  218. {execsql2-2.22.2 → execsql2-2.22.3}/tests/db/test_duckdb.py +0 -0
  219. {execsql2-2.22.2 → execsql2-2.22.3}/tests/db/test_factory.py +0 -0
  220. {execsql2-2.22.2 → execsql2-2.22.3}/tests/db/test_mysql_case_folding.py +0 -0
  221. {execsql2-2.22.2 → execsql2-2.22.3}/tests/db/test_mysql_inprocess.py +0 -0
  222. {execsql2-2.22.2 → execsql2-2.22.3}/tests/db/test_postgres.py +0 -0
  223. {execsql2-2.22.2 → execsql2-2.22.3}/tests/db/test_postgres_inprocess.py +0 -0
  224. {execsql2-2.22.2 → execsql2-2.22.3}/tests/db/test_sqlite.py +0 -0
  225. {execsql2-2.22.2 → execsql2-2.22.3}/tests/db/test_sqlite_extra.py +0 -0
  226. {execsql2-2.22.2 → execsql2-2.22.3}/tests/db/test_sqlserver_inprocess.py +0 -0
  227. {execsql2-2.22.2 → execsql2-2.22.3}/tests/exporters/__init__.py +0 -0
  228. {execsql2-2.22.2 → execsql2-2.22.3}/tests/exporters/test_base.py +0 -0
  229. {execsql2-2.22.2 → execsql2-2.22.3}/tests/exporters/test_db.py +0 -0
  230. {execsql2-2.22.2 → execsql2-2.22.3}/tests/exporters/test_delimited.py +0 -0
  231. {execsql2-2.22.2 → execsql2-2.22.3}/tests/exporters/test_duckdb_exporter.py +0 -0
  232. {execsql2-2.22.2 → execsql2-2.22.3}/tests/exporters/test_exporters.py +0 -0
  233. {execsql2-2.22.2 → execsql2-2.22.3}/tests/exporters/test_feather.py +0 -0
  234. {execsql2-2.22.2 → execsql2-2.22.3}/tests/exporters/test_html_extended.py +0 -0
  235. {execsql2-2.22.2 → execsql2-2.22.3}/tests/exporters/test_html_latex.py +0 -0
  236. {execsql2-2.22.2 → execsql2-2.22.3}/tests/exporters/test_json.py +0 -0
  237. {execsql2-2.22.2 → execsql2-2.22.3}/tests/exporters/test_json_extended.py +0 -0
  238. {execsql2-2.22.2 → execsql2-2.22.3}/tests/exporters/test_latex_extended.py +0 -0
  239. {execsql2-2.22.2 → execsql2-2.22.3}/tests/exporters/test_markdown.py +0 -0
  240. {execsql2-2.22.2 → execsql2-2.22.3}/tests/exporters/test_ods.py +0 -0
  241. {execsql2-2.22.2 → execsql2-2.22.3}/tests/exporters/test_ods_export.py +0 -0
  242. {execsql2-2.22.2 → execsql2-2.22.3}/tests/exporters/test_parquet.py +0 -0
  243. {execsql2-2.22.2 → execsql2-2.22.3}/tests/exporters/test_pretty_extended.py +0 -0
  244. {execsql2-2.22.2 → execsql2-2.22.3}/tests/exporters/test_raw_extended.py +0 -0
  245. {execsql2-2.22.2 → execsql2-2.22.3}/tests/exporters/test_sqlite_exporter.py +0 -0
  246. {execsql2-2.22.2 → execsql2-2.22.3}/tests/exporters/test_templates.py +0 -0
  247. {execsql2-2.22.2 → execsql2-2.22.3}/tests/exporters/test_templates_extended.py +0 -0
  248. {execsql2-2.22.2 → execsql2-2.22.3}/tests/exporters/test_values_extended.py +0 -0
  249. {execsql2-2.22.2 → execsql2-2.22.3}/tests/exporters/test_xls_xlsx.py +0 -0
  250. {execsql2-2.22.2 → execsql2-2.22.3}/tests/exporters/test_xlsx.py +0 -0
  251. {execsql2-2.22.2 → execsql2-2.22.3}/tests/exporters/test_xml.py +0 -0
  252. {execsql2-2.22.2 → execsql2-2.22.3}/tests/exporters/test_yaml.py +0 -0
  253. {execsql2-2.22.2 → execsql2-2.22.3}/tests/exporters/test_zip.py +0 -0
  254. {execsql2-2.22.2 → execsql2-2.22.3}/tests/gui/__init__.py +0 -0
  255. {execsql2-2.22.2 → execsql2-2.22.3}/tests/gui/test_backends.py +0 -0
  256. {execsql2-2.22.2 → execsql2-2.22.3}/tests/gui/test_backends_extended.py +0 -0
  257. {execsql2-2.22.2 → execsql2-2.22.3}/tests/gui/test_compare_stats.py +0 -0
  258. {execsql2-2.22.2 → execsql2-2.22.3}/tests/gui/test_compute_row_diffs.py +0 -0
  259. {execsql2-2.22.2 → execsql2-2.22.3}/tests/gui/test_desktop_dialogs.py +0 -0
  260. {execsql2-2.22.2 → execsql2-2.22.3}/tests/gui/test_tui_pilot.py +0 -0
  261. {execsql2-2.22.2 → execsql2-2.22.3}/tests/gui/test_tui_pilot_complex.py +0 -0
  262. {execsql2-2.22.2 → execsql2-2.22.3}/tests/gui/test_utils_gui_extended.py +0 -0
  263. {execsql2-2.22.2 → execsql2-2.22.3}/tests/importers/__init__.py +0 -0
  264. {execsql2-2.22.2 → execsql2-2.22.3}/tests/importers/test_base_extended.py +0 -0
  265. {execsql2-2.22.2 → execsql2-2.22.3}/tests/importers/test_csv_edge_cases.py +0 -0
  266. {execsql2-2.22.2 → execsql2-2.22.3}/tests/importers/test_csv_importer.py +0 -0
  267. {execsql2-2.22.2 → execsql2-2.22.3}/tests/importers/test_feather_importer.py +0 -0
  268. {execsql2-2.22.2 → execsql2-2.22.3}/tests/importers/test_json_importer.py +0 -0
  269. {execsql2-2.22.2 → execsql2-2.22.3}/tests/importers/test_ods_importer.py +0 -0
  270. {execsql2-2.22.2 → execsql2-2.22.3}/tests/importers/test_xls_importer.py +0 -0
  271. {execsql2-2.22.2 → execsql2-2.22.3}/tests/integration/__init__.py +0 -0
  272. {execsql2-2.22.2 → execsql2-2.22.3}/tests/integration/conftest.py +0 -0
  273. {execsql2-2.22.2 → execsql2-2.22.3}/tests/integration/test_dsn.py +0 -0
  274. {execsql2-2.22.2 → execsql2-2.22.3}/tests/integration/test_duckdb.py +0 -0
  275. {execsql2-2.22.2 → execsql2-2.22.3}/tests/integration/test_mysql.py +0 -0
  276. {execsql2-2.22.2 → execsql2-2.22.3}/tests/integration/test_postgres.py +0 -0
  277. {execsql2-2.22.2 → execsql2-2.22.3}/tests/integration/test_sqlite.py +0 -0
  278. {execsql2-2.22.2 → execsql2-2.22.3}/tests/metacommands/__init__.py +0 -0
  279. {execsql2-2.22.2 → execsql2-2.22.3}/tests/metacommands/test_assert.py +0 -0
  280. {execsql2-2.22.2 → execsql2-2.22.3}/tests/metacommands/test_breakpoint.py +0 -0
  281. {execsql2-2.22.2 → execsql2-2.22.3}/tests/metacommands/test_conditions_extra.py +0 -0
  282. {execsql2-2.22.2 → execsql2-2.22.3}/tests/metacommands/test_connect.py +0 -0
  283. {execsql2-2.22.2 → execsql2-2.22.3}/tests/metacommands/test_io_export.py +0 -0
  284. {execsql2-2.22.2 → execsql2-2.22.3}/tests/metacommands/test_io_import.py +0 -0
  285. {execsql2-2.22.2 → execsql2-2.22.3}/tests/metacommands/test_metacommands.py +0 -0
  286. {execsql2-2.22.2 → execsql2-2.22.3}/tests/metacommands/test_metacommands_connect.py +0 -0
  287. {execsql2-2.22.2 → execsql2-2.22.3}/tests/metacommands/test_metacommands_data.py +0 -0
  288. {execsql2-2.22.2 → execsql2-2.22.3}/tests/metacommands/test_metacommands_extended.py +0 -0
  289. {execsql2-2.22.2 → execsql2-2.22.3}/tests/metacommands/test_metacommands_fileops_extra.py +0 -0
  290. {execsql2-2.22.2 → execsql2-2.22.3}/tests/metacommands/test_metacommands_io.py +0 -0
  291. {execsql2-2.22.2 → execsql2-2.22.3}/tests/metacommands/test_metacommands_io_write_extra.py +0 -0
  292. {execsql2-2.22.2 → execsql2-2.22.3}/tests/metacommands/test_metacommands_script_ext.py +0 -0
  293. {execsql2-2.22.2 → execsql2-2.22.3}/tests/metacommands/test_metacommands_system.py +0 -0
  294. {execsql2-2.22.2 → execsql2-2.22.3}/tests/metacommands/test_metacommands_system_extra.py +0 -0
  295. {execsql2-2.22.2 → execsql2-2.22.3}/tests/metacommands/test_pg_upsert.py +0 -0
  296. {execsql2-2.22.2 → execsql2-2.22.3}/tests/metacommands/test_prompt.py +0 -0
  297. {execsql2-2.22.2 → execsql2-2.22.3}/tests/metacommands/test_row_count.py +0 -0
  298. {execsql2-2.22.2 → execsql2-2.22.3}/tests/metacommands/test_show_scripts.py +0 -0
  299. {execsql2-2.22.2 → execsql2-2.22.3}/tests/scripts/__init__.py +0 -0
  300. {execsql2-2.22.2 → execsql2-2.22.3}/tests/scripts/fixtures/audit_lint_bad.sql +0 -0
  301. {execsql2-2.22.2 → execsql2-2.22.3}/tests/scripts/fixtures/audit_smoke/sample.json +0 -0
  302. {execsql2-2.22.2 → execsql2-2.22.3}/tests/scripts/fixtures/audit_smoke/sample.jsonl +0 -0
  303. {execsql2-2.22.2 → execsql2-2.22.3}/tests/scripts/fixtures/audit_smoke.sql +0 -0
  304. {execsql2-2.22.2 → execsql2-2.22.3}/tests/scripts/fixtures/config_runtime.sql +0 -0
  305. {execsql2-2.22.2 → execsql2-2.22.3}/tests/scripts/fixtures/control_flow.sql +0 -0
  306. {execsql2-2.22.2 → execsql2-2.22.3}/tests/scripts/fixtures/counters_and_locals.sql +0 -0
  307. {execsql2-2.22.2 → execsql2-2.22.3}/tests/scripts/fixtures/error_handling.sql +0 -0
  308. {execsql2-2.22.2 → execsql2-2.22.3}/tests/scripts/fixtures/import_options/has_comments.csv +0 -0
  309. {execsql2-2.22.2 → execsql2-2.22.3}/tests/scripts/fixtures/import_options.sql +0 -0
  310. {execsql2-2.22.2 → execsql2-2.22.3}/tests/scripts/fixtures/includes/helper.sql +0 -0
  311. {execsql2-2.22.2 → execsql2-2.22.3}/tests/scripts/fixtures/includes.sql +0 -0
  312. {execsql2-2.22.2 → execsql2-2.22.3}/tests/scripts/fixtures/io_formats.sql +0 -0
  313. {execsql2-2.22.2 → execsql2-2.22.3}/tests/scripts/fixtures/io_roundtrip.sql +0 -0
  314. {execsql2-2.22.2 → execsql2-2.22.3}/tests/scripts/fixtures/multi_db.sql +0 -0
  315. {execsql2-2.22.2 → execsql2-2.22.3}/tests/scripts/fixtures/parquet_feather_roundtrip.sql +0 -0
  316. {execsql2-2.22.2 → execsql2-2.22.3}/tests/scripts/fixtures/parse_only/parse_tree.sql +0 -0
  317. {execsql2-2.22.2 → execsql2-2.22.3}/tests/scripts/fixtures/predicates.sql +0 -0
  318. {execsql2-2.22.2 → execsql2-2.22.3}/tests/scripts/fixtures/scripts.sql +0 -0
  319. {execsql2-2.22.2 → execsql2-2.22.3}/tests/scripts/fixtures/smoke.sql +0 -0
  320. {execsql2-2.22.2 → execsql2-2.22.3}/tests/scripts/fixtures/subroutine_loops.sql +0 -0
  321. {execsql2-2.22.2 → execsql2-2.22.3}/tests/scripts/fixtures/subvars_advanced.sql +0 -0
  322. {execsql2-2.22.2 → execsql2-2.22.3}/tests/scripts/fixtures/template_export/greeting.tmpl +0 -0
  323. {execsql2-2.22.2 → execsql2-2.22.3}/tests/scripts/fixtures/template_export.sql +0 -0
  324. {execsql2-2.22.2 → execsql2-2.22.3}/tests/scripts/fixtures/timer.sql +0 -0
  325. {execsql2-2.22.2 → execsql2-2.22.3}/tests/scripts/fixtures/transactions.sql +0 -0
  326. {execsql2-2.22.2 → execsql2-2.22.3}/tests/scripts/fixtures/write_create_table.sql +0 -0
  327. {execsql2-2.22.2 → execsql2-2.22.3}/tests/scripts/fixtures/xlsx_ods_roundtrip.sql +0 -0
  328. {execsql2-2.22.2 → execsql2-2.22.3}/tests/scripts/test_sql_scripts.py +0 -0
  329. {execsql2-2.22.2 → execsql2-2.22.3}/tests/security/__init__.py +0 -0
  330. {execsql2-2.22.2 → execsql2-2.22.3}/tests/security/test_env_detection.py +0 -0
  331. {execsql2-2.22.2 → execsql2-2.22.3}/tests/security/test_expansion_bomb.py +0 -0
  332. {execsql2-2.22.2 → execsql2-2.22.3}/tests/security/test_import_regex_hardening.py +0 -0
  333. {execsql2-2.22.2 → execsql2-2.22.3}/tests/security/test_log_redaction.py +0 -0
  334. {execsql2-2.22.2 → execsql2-2.22.3}/tests/security/test_odbc_injection.py +0 -0
  335. {execsql2-2.22.2 → execsql2-2.22.3}/tests/security/test_path_containment.py +0 -0
  336. {execsql2-2.22.2 → execsql2-2.22.3}/tests/security/test_substitution_injection.py +0 -0
  337. {execsql2-2.22.2 → execsql2-2.22.3}/tests/security/test_zip_bomb.py +0 -0
  338. {execsql2-2.22.2 → execsql2-2.22.3}/tests/test_api.py +0 -0
  339. {execsql2-2.22.2 → execsql2-2.22.3}/tests/test_api_extended.py +0 -0
  340. {execsql2-2.22.2 → execsql2-2.22.3}/tests/test_ast.py +0 -0
  341. {execsql2-2.22.2 → execsql2-2.22.3}/tests/test_ast_parser.py +0 -0
  342. {execsql2-2.22.2 → execsql2-2.22.3}/tests/test_bundled_templates.py +0 -0
  343. {execsql2-2.22.2 → execsql2-2.22.3}/tests/test_config.py +0 -0
  344. {execsql2-2.22.2 → execsql2-2.22.3}/tests/test_config_data.py +0 -0
  345. {execsql2-2.22.2 → execsql2-2.22.3}/tests/test_config_extended.py +0 -0
  346. {execsql2-2.22.2 → execsql2-2.22.3}/tests/test_debug_repl.py +0 -0
  347. {execsql2-2.22.2 → execsql2-2.22.3}/tests/test_engine.py +0 -0
  348. {execsql2-2.22.2 → execsql2-2.22.3}/tests/test_error_messages.py +0 -0
  349. {execsql2-2.22.2 → execsql2-2.22.3}/tests/test_exceptions.py +0 -0
  350. {execsql2-2.22.2 → execsql2-2.22.3}/tests/test_executor.py +0 -0
  351. {execsql2-2.22.2 → execsql2-2.22.3}/tests/test_executor_inprocess.py +0 -0
  352. {execsql2-2.22.2 → execsql2-2.22.3}/tests/test_mail.py +0 -0
  353. {execsql2-2.22.2 → execsql2-2.22.3}/tests/test_models.py +0 -0
  354. {execsql2-2.22.2 → execsql2-2.22.3}/tests/test_package.py +0 -0
  355. {execsql2-2.22.2 → execsql2-2.22.3}/tests/test_parser.py +0 -0
  356. {execsql2-2.22.2 → execsql2-2.22.3}/tests/test_parser_params.py +0 -0
  357. {execsql2-2.22.2 → execsql2-2.22.3}/tests/test_plugins.py +0 -0
  358. {execsql2-2.22.2 → execsql2-2.22.3}/tests/test_registry.py +0 -0
  359. {execsql2-2.22.2 → execsql2-2.22.3}/tests/test_script.py +0 -0
  360. {execsql2-2.22.2 → execsql2-2.22.3}/tests/test_state.py +0 -0
  361. {execsql2-2.22.2 → execsql2-2.22.3}/tests/test_types.py +0 -0
  362. {execsql2-2.22.2 → execsql2-2.22.3}/tests/utils/__init__.py +0 -0
  363. {execsql2-2.22.2 → execsql2-2.22.3}/tests/utils/test_auth.py +0 -0
  364. {execsql2-2.22.2 → execsql2-2.22.3}/tests/utils/test_auth_extra.py +0 -0
  365. {execsql2-2.22.2 → execsql2-2.22.3}/tests/utils/test_crypto.py +0 -0
  366. {execsql2-2.22.2 → execsql2-2.22.3}/tests/utils/test_datetime.py +0 -0
  367. {execsql2-2.22.2 → execsql2-2.22.3}/tests/utils/test_encodedfile_context.py +0 -0
  368. {execsql2-2.22.2 → execsql2-2.22.3}/tests/utils/test_errors.py +0 -0
  369. {execsql2-2.22.2 → execsql2-2.22.3}/tests/utils/test_errors_extra.py +0 -0
  370. {execsql2-2.22.2 → execsql2-2.22.3}/tests/utils/test_fileio.py +0 -0
  371. {execsql2-2.22.2 → execsql2-2.22.3}/tests/utils/test_fileio_extra.py +0 -0
  372. {execsql2-2.22.2 → execsql2-2.22.3}/tests/utils/test_numeric.py +0 -0
  373. {execsql2-2.22.2 → execsql2-2.22.3}/tests/utils/test_regex.py +0 -0
  374. {execsql2-2.22.2 → execsql2-2.22.3}/tests/utils/test_strings.py +0 -0
  375. {execsql2-2.22.2 → execsql2-2.22.3}/tests/utils/test_timer.py +0 -0
  376. {execsql2-2.22.2 → execsql2-2.22.3}/tests/utils/test_timer_extra.py +0 -0
  377. {execsql2-2.22.2 → execsql2-2.22.3}/zensical.toml +0 -0
@@ -46,10 +46,12 @@ scripts/*
46
46
  # Superseded monolith
47
47
  _execsql/
48
48
 
49
- # Claude
49
+ # AI
50
50
  .claude/
51
51
  CLAUDE.md
52
52
  AUDIT.md
53
53
  DOC_AUDIT.md
54
54
  PROMPTS.md
55
55
  audit/
56
+ AGENTS.md
57
+ .codex/
@@ -13,6 +13,14 @@ ______________________________________________________________________
13
13
 
14
14
  ______________________________________________________________________
15
15
 
16
+ ## [2.22.3] - 2026-07-23
17
+
18
+ ### Fixed
19
+
20
+ - `execsql-format` no longer sends a completed dollar-quoted PL/pgSQL body to sqlglot. A `CREATE FUNCTION … $$ … $$;` or `DO $$ … $$;` whose quote closed before the block was flushed was reformatted (signature exploded, `LANGUAGE … AS` reordered, body types uppercased) and drifted — gaining one space of indentation on every run, so `--check` never converged. Such statements are now kept verbatim.
21
+
22
+ ______________________________________________________________________
23
+
16
24
  ## [2.22.2] - 2026-07-13
17
25
 
18
26
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: execsql2
3
- Version: 2.22.2
3
+ Version: 2.22.3
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
@@ -384,7 +384,7 @@ execsql-format --no-sql --in-place scripts/
384
384
  ```yaml
385
385
  repos:
386
386
  - repo: https://github.com/geocoug/execsql
387
- rev: v2.22.2
387
+ rev: v2.22.3
388
388
  hooks:
389
389
  - id: execsql-format
390
390
  ```
@@ -253,7 +253,7 @@ execsql-format --no-sql --in-place scripts/
253
253
  ```yaml
254
254
  repos:
255
255
  - repo: https://github.com/geocoug/execsql
256
- rev: v2.22.2
256
+ rev: v2.22.3
257
257
  hooks:
258
258
  - id: execsql-format
259
259
  ```
@@ -111,6 +111,7 @@ Formatter correctness notes added in the 2.19.x line:
111
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
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
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`.
114
+ - **Closed dollar quotes** — Any statement that *contains* a dollar-quoted region is now opaque to sqlglot, tracked with a sticky flag rather than the parser's live `in_dollar_quote` state. A complete `CREATE FUNCTION … $$ … $$;` or `DO $$ … $$;` had its quote already closed by the time the block was flushed, so the guard passed and the whole body was reformatted — and, because dropped comments were re-indented onto the nearest output line each pass, the body drifted (gaining a space of indentation) on every run, so `--check` never converged. A side effect is that a single-line `SELECT $$literal$$;` also skips the sqlglot pass (no keyword uppercasing), which is the safe, conservative behavior.
114
115
 
115
116
  ### GUI
116
117
 
@@ -160,7 +160,7 @@ select id,name,created_at from users where active = true order by name;
160
160
  ```yaml
161
161
  repos:
162
162
  - repo: https://github.com/geocoug/execsql
163
- rev: v2.22.2
163
+ rev: v2.22.3
164
164
  hooks:
165
165
  - id: execsql-format
166
166
  ```
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "execsql2"
7
- version = "2.22.2"
7
+ version = "2.22.3"
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" }
@@ -178,7 +178,7 @@ skip-magic-trailing-comma = false
178
178
  line-ending = "auto"
179
179
 
180
180
  [tool.bumpversion]
181
- current_version = "2.22.2"
181
+ current_version = "2.22.3"
182
182
  commit = true
183
183
  tag = true
184
184
  tag_name = "v{new_version}"
@@ -642,6 +642,12 @@ def format_file(source: str, indent: int = 4, use_sql: bool = True, leading_comm
642
642
  # "body" for `$body$`, etc.). Nested markers with a different tag
643
643
  # are ignored — only a matching close marker re-opens us.
644
644
  current_dq_tag: str | None = None
645
+ # Sticky flag: True once the current accumulator has seen ANY dollar-quote
646
+ # marker. `in_dollar_quote` alone is False by the time a complete
647
+ # `CREATE FUNCTION ... $$ ... $$;` is flushed (the closing `$$` reset it),
648
+ # which would wrongly hand the PL/pgSQL body to sqlglot. This flag keeps the
649
+ # whole containing statement opaque to sqlglot. Reset in flush_sql().
650
+ sql_acc_contains_dollar_quote = False
645
651
  in_block_comment = False
646
652
  # Track whether we are inside an open SQL statement (last SQL line
647
653
  # did not end with ';'). Blank lines mid-statement should NOT flush
@@ -655,15 +661,19 @@ def format_file(source: str, indent: int = 4, use_sql: bool = True, leading_comm
655
661
 
656
662
  def flush_sql() -> None:
657
663
  nonlocal in_dollar_quote, current_dq_tag, in_sql_statement
664
+ nonlocal sql_acc_contains_dollar_quote
658
665
  if sql_acc:
659
666
  # If any line in the accumulated block is inside a $$-delimited
660
667
  # region, skip sqlglot formatting entirely. PL/pgSQL function
661
668
  # bodies contain IF/END IF, LOOP, RETURN, etc. that sqlglot does
662
669
  # not understand and will corrupt (e.g., rewriting to COMMIT).
663
- safe_for_sqlglot = use_sql and not in_dollar_quote
670
+ # `sql_acc_contains_dollar_quote` catches the case where the region
671
+ # already closed (`... $$;`) so `in_dollar_quote` is False at flush.
672
+ safe_for_sqlglot = use_sql and not in_dollar_quote and not sql_acc_contains_dollar_quote
664
673
  output.extend(format_sql_block(sql_acc, depth, indent, safe_for_sqlglot, leading_comma=leading_comma))
665
674
  sql_acc.clear()
666
675
  in_sql_statement = False
676
+ sql_acc_contains_dollar_quote = False
667
677
 
668
678
  for raw_line in source.expandtabs(4).splitlines():
669
679
  stripped_line = raw_line.strip()
@@ -727,6 +737,7 @@ def format_file(source: str, indent: int = 4, use_sql: bool = True, leading_comm
727
737
  # PL/pgSQL. Walk every dollar-quote marker on the line; toggle
728
738
  # state only when we hit the matching open or close.
729
739
  for m in _DOLLAR_QUOTE_RE.finditer(raw_line):
740
+ sql_acc_contains_dollar_quote = True
730
741
  tag = m.group(1) or ""
731
742
  if not in_dollar_quote:
732
743
  in_dollar_quote = True
@@ -6,6 +6,7 @@ import subprocess
6
6
  import sys
7
7
  from unittest.mock import patch
8
8
 
9
+ import pytest
9
10
 
10
11
  from execsql.format import (
11
12
  _is_comment_line,
@@ -1825,6 +1826,101 @@ class TestDollarQuotedStrings:
1825
1826
  assert "EXIT WHEN n >= 3" in result
1826
1827
  assert "END LOOP" in result
1827
1828
 
1829
+ def test_closed_dollar_quote_body_not_sent_to_sqlglot(self):
1830
+ """A *complete* `CREATE FUNCTION ... $$ ... $$;` must stay opaque.
1831
+
1832
+ The closing `$$` resets `in_dollar_quote` to False before the block is
1833
+ flushed, so a guard that only checked `in_dollar_quote` handed the whole
1834
+ procedural body to sqlglot — exploding the arg list, reordering
1835
+ `LANGUAGE ... AS`, and uppercasing the body's types. The sticky
1836
+ `sql_acc_contains_dollar_quote` flag keeps the statement verbatim.
1837
+ """
1838
+ source = (
1839
+ "CREATE FUNCTION check_case(val text) RETURNS boolean AS $$\n"
1840
+ "BEGIN\n"
1841
+ " -- compare lowercased value\n"
1842
+ " IF lower(val) = val THEN\n"
1843
+ " RETURN true;\n"
1844
+ " END IF;\n"
1845
+ " RETURN false;\n"
1846
+ "END;\n"
1847
+ "$$ LANGUAGE plpgsql;\n"
1848
+ )
1849
+ result = format_file(source, use_sql=True)
1850
+ # Body preserved byte-for-byte: signature not exploded, types not
1851
+ # uppercased, LANGUAGE clause not reordered, indentation intact.
1852
+ assert "CREATE FUNCTION check_case(val text) RETURNS boolean AS $$" in result
1853
+ assert " -- compare lowercased value" in result
1854
+ assert " RETURN true;" in result
1855
+ assert "$$ LANGUAGE plpgsql;" in result
1856
+
1857
+ @pytest.mark.parametrize(
1858
+ "source",
1859
+ [
1860
+ # Untagged $$ with a comment inside the body.
1861
+ (
1862
+ "CREATE FUNCTION check_case(val text) RETURNS boolean AS $$\n"
1863
+ "BEGIN\n"
1864
+ " -- compare lowercased value\n"
1865
+ " IF lower(val) = val THEN\n"
1866
+ " RETURN true;\n"
1867
+ " END IF;\n"
1868
+ " RETURN false;\n"
1869
+ "END;\n"
1870
+ "$$ LANGUAGE plpgsql;\n"
1871
+ ),
1872
+ # Tagged body ($body$ ... $body$).
1873
+ (
1874
+ "CREATE FUNCTION add_one(x int) RETURNS int AS $body$\n"
1875
+ "BEGIN\n"
1876
+ " -- add one\n"
1877
+ " IF x IS NULL THEN\n"
1878
+ " RETURN 0;\n"
1879
+ " END IF;\n"
1880
+ " RETURN x + 1;\n"
1881
+ "END\n"
1882
+ "$body$ LANGUAGE plpgsql;\n"
1883
+ ),
1884
+ # DO $$ ... $$;
1885
+ (
1886
+ "DO $$\n"
1887
+ "DECLARE\n"
1888
+ " n int := 0;\n"
1889
+ "BEGIN\n"
1890
+ " -- loop three times\n"
1891
+ " LOOP\n"
1892
+ " EXIT WHEN n >= 3;\n"
1893
+ " n := n + 1;\n"
1894
+ " END LOOP;\n"
1895
+ "END\n"
1896
+ "$$;\n"
1897
+ ),
1898
+ # Dollar-quoted body wrapped in an explicit BEGIN SQL / END SQL block.
1899
+ (
1900
+ "-- !x! BEGIN SQL\n"
1901
+ "CREATE FUNCTION add_one(x int) RETURNS int AS $$\n"
1902
+ "BEGIN\n"
1903
+ " -- add one\n"
1904
+ " RETURN x + 1;\n"
1905
+ "END;\n"
1906
+ "$$ LANGUAGE plpgsql;\n"
1907
+ "-- !x! END SQL\n"
1908
+ ),
1909
+ ],
1910
+ ids=["untagged", "tagged-body", "do-block", "in-begin-sql"],
1911
+ )
1912
+ def test_dollar_quoted_body_idempotent(self, source):
1913
+ """Formatting a dollar-quoted body must be idempotent (no drift).
1914
+
1915
+ Prior to the fix, each SQL-enabled pass re-indented the body's comment
1916
+ and control-flow lines by one more space, so `execsql-format --check`
1917
+ never converged. Assert `first == second == third`.
1918
+ """
1919
+ first = format_file(source, use_sql=True)
1920
+ second = format_file(first, use_sql=True)
1921
+ third = format_file(second, use_sql=True)
1922
+ assert first == second == third
1923
+
1828
1924
  def test_if_inline_regex_agrees_with_parser(self):
1829
1925
  """`_IF_INLINE_RE` (formatter) and `_IF_INLINE_RX` (AST parser) must
1830
1926
  recognise the same inline-IF payloads. They are intentionally kept
@@ -722,7 +722,7 @@ wheels = [
722
722
 
723
723
  [[package]]
724
724
  name = "execsql2"
725
- version = "2.22.2"
725
+ version = "2.22.3"
726
726
  source = { editable = "." }
727
727
  dependencies = [
728
728
  { name = "python-dateutil" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes