execsql2 2.31.2__tar.gz → 2.33.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 (507) hide show
  1. {execsql2-2.31.2 → execsql2-2.33.0}/CHANGELOG.md +27 -0
  2. {execsql2-2.31.2 → execsql2-2.33.0}/PKG-INFO +2 -2
  3. {execsql2-2.31.2 → execsql2-2.33.0}/README.md +1 -1
  4. {execsql2-2.31.2 → execsql2-2.33.0}/docs/about/divergence.md +12 -10
  5. {execsql2-2.31.2 → execsql2-2.33.0}/docs/dev/adding_db_adapters.md +1 -0
  6. {execsql2-2.31.2 → execsql2-2.33.0}/docs/dev/architecture.md +1 -1
  7. {execsql2-2.31.2 → execsql2-2.33.0}/docs/guides/formatter.md +1 -1
  8. {execsql2-2.31.2 → execsql2-2.33.0}/docs/reference/lint.md +1 -1
  9. {execsql2-2.31.2 → execsql2-2.33.0}/docs/reference/metacommands.md +6 -4
  10. {execsql2-2.31.2 → execsql2-2.33.0}/extras/vscode-execsql/package-lock.json +2 -2
  11. {execsql2-2.31.2 → execsql2-2.33.0}/extras/vscode-execsql/package.json +1 -1
  12. {execsql2-2.31.2 → execsql2-2.33.0}/pyproject.toml +2 -2
  13. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/db/base.py +19 -1
  14. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/db/duckdb.py +60 -1
  15. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/exporters/duckdb.py +2 -10
  16. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/metacommands/reference.py +10 -0
  17. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/metacommands/upsert.py +13 -0
  18. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/models.py +73 -48
  19. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/script/engine.py +5 -7
  20. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/script/executor.py +3 -1
  21. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/types.py +14 -0
  22. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/utils/datetime.py +16 -2
  23. execsql2-2.33.0/tests/db/test_bulk_inserts.py +169 -0
  24. {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_postgres_inprocess.py +58 -0
  25. {execsql2-2.31.2 → execsql2-2.33.0}/tests/integration/test_postgres.py +49 -0
  26. {execsql2-2.31.2 → execsql2-2.33.0}/tests/metacommands/test_pg_upsert.py +34 -0
  27. {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_engine.py +22 -17
  28. execsql2-2.33.0/tests/test_metacommand_errors.py +98 -0
  29. {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_models.py +22 -0
  30. {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_script.py +3 -3
  31. {execsql2-2.31.2 → execsql2-2.33.0}/tests/utils/test_datetime.py +31 -0
  32. {execsql2-2.31.2 → execsql2-2.33.0}/uv.lock +1 -1
  33. {execsql2-2.31.2 → execsql2-2.33.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  34. {execsql2-2.31.2 → execsql2-2.33.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  35. {execsql2-2.31.2 → execsql2-2.33.0}/.github/dependabot.yml +0 -0
  36. {execsql2-2.31.2 → execsql2-2.33.0}/.github/workflows/ci-cd.yml +0 -0
  37. {execsql2-2.31.2 → execsql2-2.33.0}/.gitignore +0 -0
  38. {execsql2-2.31.2 → execsql2-2.33.0}/.pre-commit-config.yaml +0 -0
  39. {execsql2-2.31.2 → execsql2-2.33.0}/.pre-commit-hooks.yaml +0 -0
  40. {execsql2-2.31.2 → execsql2-2.33.0}/.python-version +0 -0
  41. {execsql2-2.31.2 → execsql2-2.33.0}/.readthedocs.yaml +0 -0
  42. {execsql2-2.31.2 → execsql2-2.33.0}/CONTRIBUTING.md +0 -0
  43. {execsql2-2.31.2 → execsql2-2.33.0}/LICENSE.txt +0 -0
  44. {execsql2-2.31.2 → execsql2-2.33.0}/NOTICE +0 -0
  45. {execsql2-2.31.2 → execsql2-2.33.0}/SECURITY.md +0 -0
  46. {execsql2-2.31.2 → execsql2-2.33.0}/docs/about/contributors.md +0 -0
  47. {execsql2-2.31.2 → execsql2-2.33.0}/docs/about/copyright.md +0 -0
  48. {execsql2-2.31.2 → execsql2-2.33.0}/docs/api/cli.md +0 -0
  49. {execsql2-2.31.2 → execsql2-2.33.0}/docs/api/db.md +0 -0
  50. {execsql2-2.31.2 → execsql2-2.33.0}/docs/api/exporters.md +0 -0
  51. {execsql2-2.31.2 → execsql2-2.33.0}/docs/api/importers.md +0 -0
  52. {execsql2-2.31.2 → execsql2-2.33.0}/docs/api/index.md +0 -0
  53. {execsql2-2.31.2 → execsql2-2.33.0}/docs/api/metacommands.md +0 -0
  54. {execsql2-2.31.2 → execsql2-2.33.0}/docs/dev/adding_exporters.md +0 -0
  55. {execsql2-2.31.2 → execsql2-2.33.0}/docs/dev/adding_importers.md +0 -0
  56. {execsql2-2.31.2 → execsql2-2.33.0}/docs/dev/adding_metacommands.md +0 -0
  57. {execsql2-2.31.2 → execsql2-2.33.0}/docs/dev/releasing.md +0 -0
  58. {execsql2-2.31.2 → execsql2-2.33.0}/docs/getting-started/installation.md +0 -0
  59. {execsql2-2.31.2 → execsql2-2.33.0}/docs/getting-started/requirements.md +0 -0
  60. {execsql2-2.31.2 → execsql2-2.33.0}/docs/getting-started/syntax.md +0 -0
  61. {execsql2-2.31.2 → execsql2-2.33.0}/docs/guides/debugging.md +0 -0
  62. {execsql2-2.31.2 → execsql2-2.33.0}/docs/guides/documentation.md +0 -0
  63. {execsql2-2.31.2 → execsql2-2.33.0}/docs/guides/editors.md +0 -0
  64. {execsql2-2.31.2 → execsql2-2.33.0}/docs/guides/encoding.md +0 -0
  65. {execsql2-2.31.2 → execsql2-2.33.0}/docs/guides/examples.md +0 -0
  66. {execsql2-2.31.2 → execsql2-2.33.0}/docs/guides/logging.md +0 -0
  67. {execsql2-2.31.2 → execsql2-2.33.0}/docs/guides/sql_syntax.md +0 -0
  68. {execsql2-2.31.2 → execsql2-2.33.0}/docs/guides/usage.md +0 -0
  69. {execsql2-2.31.2 → execsql2-2.33.0}/docs/guides/using_scripts.md +0 -0
  70. {execsql2-2.31.2 → execsql2-2.33.0}/docs/images/Compare_planets.png +0 -0
  71. {execsql2-2.31.2 → execsql2-2.33.0}/docs/images/actions.png +0 -0
  72. {execsql2-2.31.2 → execsql2-2.33.0}/docs/images/actions2.png +0 -0
  73. {execsql2-2.31.2 → execsql2-2.33.0}/docs/images/checkboxes.png +0 -0
  74. {execsql2-2.31.2 → execsql2-2.33.0}/docs/images/connect.png +0 -0
  75. {execsql2-2.31.2 → execsql2-2.33.0}/docs/images/data_error1_screenshot.jpg +0 -0
  76. {execsql2-2.31.2 → execsql2-2.33.0}/docs/images/execsql_logo_01.png +0 -0
  77. {execsql2-2.31.2 → execsql2-2.33.0}/docs/images/fatals.png +0 -0
  78. {execsql2-2.31.2 → execsql2-2.33.0}/docs/images/pause_terminal_sm.png +0 -0
  79. {execsql2-2.31.2 → execsql2-2.33.0}/docs/images/set_build_commands.jpg +0 -0
  80. {execsql2-2.31.2 → execsql2-2.33.0}/docs/images/unit_conversions_029.png +0 -0
  81. {execsql2-2.31.2 → execsql2-2.33.0}/docs/images/unmatched.png +0 -0
  82. {execsql2-2.31.2 → execsql2-2.33.0}/docs/images/vim_execsql_highlight.png +0 -0
  83. {execsql2-2.31.2 → execsql2-2.33.0}/docs/index.md +0 -0
  84. {execsql2-2.31.2 → execsql2-2.33.0}/docs/reference/configuration.md +0 -0
  85. {execsql2-2.31.2 → execsql2-2.33.0}/docs/reference/security.md +0 -0
  86. {execsql2-2.31.2 → execsql2-2.33.0}/docs/reference/substitution_vars.md +0 -0
  87. {execsql2-2.31.2 → execsql2-2.33.0}/docs/templates/compare.md +0 -0
  88. {execsql2-2.31.2 → execsql2-2.33.0}/docs/templates/glossary.md +0 -0
  89. {execsql2-2.31.2 → execsql2-2.33.0}/docs/templates/index.md +0 -0
  90. {execsql2-2.31.2 → execsql2-2.33.0}/docs/templates/upsert.md +0 -0
  91. {execsql2-2.31.2 → execsql2-2.33.0}/extras/plugin-template/README.md +0 -0
  92. {execsql2-2.31.2 → execsql2-2.33.0}/extras/plugin-template/pyproject.toml +0 -0
  93. {execsql2-2.31.2 → execsql2-2.33.0}/extras/plugin-template/src/execsql_plugin_YOURNAME/__init__.py +0 -0
  94. {execsql2-2.31.2 → execsql2-2.33.0}/extras/plugin-template/tests/test_plugin.py.example +0 -0
  95. {execsql2-2.31.2 → execsql2-2.33.0}/extras/vscode-execsql/.vscodeignore +0 -0
  96. {execsql2-2.31.2 → execsql2-2.33.0}/extras/vscode-execsql/LICENSE.txt +0 -0
  97. {execsql2-2.31.2 → execsql2-2.33.0}/extras/vscode-execsql/README.md +0 -0
  98. {execsql2-2.31.2 → execsql2-2.33.0}/extras/vscode-execsql/extension.js +0 -0
  99. {execsql2-2.31.2 → execsql2-2.33.0}/extras/vscode-execsql/icon.png +0 -0
  100. {execsql2-2.31.2 → execsql2-2.33.0}/extras/vscode-execsql/syntaxes/execsql.tmLanguage.json +0 -0
  101. {execsql2-2.31.2 → execsql2-2.33.0}/justfile +0 -0
  102. {execsql2-2.31.2 → execsql2-2.33.0}/scripts/benchmarks/README.md +0 -0
  103. {execsql2-2.31.2 → execsql2-2.33.0}/scripts/benchmarks/results/.gitignore +0 -0
  104. {execsql2-2.31.2 → execsql2-2.33.0}/scripts/benchmarks/run_benchmarks.py +0 -0
  105. {execsql2-2.31.2 → execsql2-2.33.0}/scripts/generate_vscode_grammar.py +0 -0
  106. {execsql2-2.31.2 → execsql2-2.33.0}/scripts/release_notes.py +0 -0
  107. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/__init__.py +0 -0
  108. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/__main__.py +0 -0
  109. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/api.py +0 -0
  110. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/cli/__init__.py +0 -0
  111. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/cli/application.py +0 -0
  112. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/cli/commands/__init__.py +0 -0
  113. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/cli/commands/config.py +0 -0
  114. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/cli/commands/format.py +0 -0
  115. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/cli/commands/init.py +0 -0
  116. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/cli/commands/lint.py +0 -0
  117. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/cli/commands/listing.py +0 -0
  118. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/cli/commands/lsp.py +0 -0
  119. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/cli/commands/run.py +0 -0
  120. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/cli/dispatch.py +0 -0
  121. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/cli/dsn.py +0 -0
  122. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/cli/help.py +0 -0
  123. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/cli/lint.py +0 -0
  124. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/cli/options.py +0 -0
  125. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/cli/run.py +0 -0
  126. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/config.py +0 -0
  127. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/config_validation.py +0 -0
  128. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/data/__init__.py +0 -0
  129. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/data/execsql.conf.template +0 -0
  130. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/data/script_header.sql +0 -0
  131. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/db/__init__.py +0 -0
  132. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/db/access.py +0 -0
  133. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/db/dsn.py +0 -0
  134. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/db/factory.py +0 -0
  135. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/db/firebird.py +0 -0
  136. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/db/mysql.py +0 -0
  137. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/db/odbc.py +0 -0
  138. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/db/oracle.py +0 -0
  139. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/db/postgres.py +0 -0
  140. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/db/sqlite.py +0 -0
  141. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/db/sqlserver.py +0 -0
  142. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/db/tiers.py +0 -0
  143. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/debug/__init__.py +0 -0
  144. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/debug/repl.py +0 -0
  145. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/exceptions.py +0 -0
  146. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/exporters/__init__.py +0 -0
  147. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/exporters/base.py +0 -0
  148. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/exporters/delimited.py +0 -0
  149. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/exporters/feather.py +0 -0
  150. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/exporters/html.py +0 -0
  151. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/exporters/json.py +0 -0
  152. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/exporters/latex.py +0 -0
  153. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/exporters/markdown.py +0 -0
  154. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/exporters/ods.py +0 -0
  155. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/exporters/parquet.py +0 -0
  156. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/exporters/pretty.py +0 -0
  157. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/exporters/protocol.py +0 -0
  158. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/exporters/raw.py +0 -0
  159. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/exporters/sqlite.py +0 -0
  160. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/exporters/templates.py +0 -0
  161. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/exporters/values.py +0 -0
  162. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/exporters/xls.py +0 -0
  163. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/exporters/xlsx.py +0 -0
  164. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/exporters/xml.py +0 -0
  165. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/exporters/yaml.py +0 -0
  166. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/exporters/zip.py +0 -0
  167. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/format.py +0 -0
  168. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/gui/__init__.py +0 -0
  169. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/gui/base.py +0 -0
  170. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/gui/console.py +0 -0
  171. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/gui/desktop.py +0 -0
  172. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/gui/tui.py +0 -0
  173. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/importers/__init__.py +0 -0
  174. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/importers/base.py +0 -0
  175. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/importers/csv.py +0 -0
  176. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/importers/feather.py +0 -0
  177. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/importers/json.py +0 -0
  178. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/importers/ods.py +0 -0
  179. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/importers/xls.py +0 -0
  180. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/interactive.py +0 -0
  181. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/lsp/__init__.py +0 -0
  182. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/lsp/_util.py +0 -0
  183. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/lsp/cache.py +0 -0
  184. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/lsp/code_actions.py +0 -0
  185. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/lsp/completion.py +0 -0
  186. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/lsp/diagnostics.py +0 -0
  187. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/lsp/document.py +0 -0
  188. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/lsp/folding.py +0 -0
  189. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/lsp/formatting.py +0 -0
  190. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/lsp/hover.py +0 -0
  191. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/lsp/navigation.py +0 -0
  192. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/lsp/rename.py +0 -0
  193. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/lsp/server.py +0 -0
  194. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/manifest.py +0 -0
  195. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/metacommands/__init__.py +0 -0
  196. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/metacommands/conditions.py +0 -0
  197. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/metacommands/connect.py +0 -0
  198. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/metacommands/control.py +0 -0
  199. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/metacommands/data.py +0 -0
  200. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/metacommands/debug.py +0 -0
  201. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/metacommands/dispatch.py +0 -0
  202. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/metacommands/effects.py +0 -0
  203. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/metacommands/io.py +0 -0
  204. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/metacommands/io_export.py +0 -0
  205. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/metacommands/io_fileops.py +0 -0
  206. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/metacommands/io_import.py +0 -0
  207. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/metacommands/io_write.py +0 -0
  208. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/metacommands/prompt.py +0 -0
  209. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/metacommands/script_ext.py +0 -0
  210. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/metacommands/system.py +0 -0
  211. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/parser.py +0 -0
  212. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/plugins.py +0 -0
  213. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/py.typed +0 -0
  214. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/script/__init__.py +0 -0
  215. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/script/ast.py +0 -0
  216. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/script/control.py +0 -0
  217. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/script/parser.py +0 -0
  218. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/script/variables.py +0 -0
  219. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/state.py +0 -0
  220. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/state.pyi +0 -0
  221. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/utils/__init__.py +0 -0
  222. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/utils/auth.py +0 -0
  223. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/utils/color.py +0 -0
  224. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/utils/crypto.py +0 -0
  225. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/utils/errors.py +0 -0
  226. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/utils/fileio.py +0 -0
  227. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/utils/gui.py +0 -0
  228. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/utils/mail.py +0 -0
  229. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/utils/numeric.py +0 -0
  230. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/utils/paths.py +0 -0
  231. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/utils/regex.py +0 -0
  232. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/utils/strings.py +0 -0
  233. {execsql2-2.31.2 → execsql2-2.33.0}/src/execsql/utils/timer.py +0 -0
  234. {execsql2-2.31.2 → execsql2-2.33.0}/templates/README.md +0 -0
  235. {execsql2-2.31.2 → execsql2-2.33.0}/templates/config_settings.sqlite +0 -0
  236. {execsql2-2.31.2 → execsql2-2.33.0}/templates/example_config_prompt.sql +0 -0
  237. {execsql2-2.31.2 → execsql2-2.33.0}/templates/make_config_db.sql +0 -0
  238. {execsql2-2.31.2 → execsql2-2.33.0}/templates/md_compare.sql +0 -0
  239. {execsql2-2.31.2 → execsql2-2.33.0}/templates/md_glossary.sql +0 -0
  240. {execsql2-2.31.2 → execsql2-2.33.0}/templates/md_upsert.sql +0 -0
  241. {execsql2-2.31.2 → execsql2-2.33.0}/templates/pg_compare.sql +0 -0
  242. {execsql2-2.31.2 → execsql2-2.33.0}/templates/pg_glossary.sql +0 -0
  243. {execsql2-2.31.2 → execsql2-2.33.0}/templates/script_template.sql +0 -0
  244. {execsql2-2.31.2 → execsql2-2.33.0}/templates/ss_compare.sql +0 -0
  245. {execsql2-2.31.2 → execsql2-2.33.0}/templates/ss_glossary.sql +0 -0
  246. {execsql2-2.31.2 → execsql2-2.33.0}/templates/ss_upsert.sql +0 -0
  247. {execsql2-2.31.2 → execsql2-2.33.0}/tests/__init__.py +0 -0
  248. {execsql2-2.31.2 → execsql2-2.33.0}/tests/cli/__init__.py +0 -0
  249. {execsql2-2.31.2 → execsql2-2.33.0}/tests/cli/test_cli.py +0 -0
  250. {execsql2-2.31.2 → execsql2-2.33.0}/tests/cli/test_cli_e2e.py +0 -0
  251. {execsql2-2.31.2 → execsql2-2.33.0}/tests/cli/test_cli_run.py +0 -0
  252. {execsql2-2.31.2 → execsql2-2.33.0}/tests/cli/test_cli_surface.py +0 -0
  253. {execsql2-2.31.2 → execsql2-2.33.0}/tests/cli/test_commands.py +0 -0
  254. {execsql2-2.31.2 → execsql2-2.33.0}/tests/cli/test_config_validate.py +0 -0
  255. {execsql2-2.31.2 → execsql2-2.33.0}/tests/cli/test_dispatch.py +0 -0
  256. {execsql2-2.31.2 → execsql2-2.33.0}/tests/cli/test_error_location.py +0 -0
  257. {execsql2-2.31.2 → execsql2-2.33.0}/tests/cli/test_init.py +0 -0
  258. {execsql2-2.31.2 → execsql2-2.33.0}/tests/cli/test_lint.py +0 -0
  259. {execsql2-2.31.2 → execsql2-2.33.0}/tests/cli/test_lint_fixtures.py +0 -0
  260. {execsql2-2.31.2 → execsql2-2.33.0}/tests/cli/test_log_exit_record.py +0 -0
  261. {execsql2-2.31.2 → execsql2-2.33.0}/tests/cli/test_manifest.py +0 -0
  262. {execsql2-2.31.2 → execsql2-2.33.0}/tests/cli/test_ping.py +0 -0
  263. {execsql2-2.31.2 → execsql2-2.33.0}/tests/cli/test_profile.py +0 -0
  264. {execsql2-2.31.2 → execsql2-2.33.0}/tests/cli/test_sigterm.py +0 -0
  265. {execsql2-2.31.2 → execsql2-2.33.0}/tests/cli/test_undeclared_imports.py +0 -0
  266. {execsql2-2.31.2 → execsql2-2.33.0}/tests/conftest.py +0 -0
  267. {execsql2-2.31.2 → execsql2-2.33.0}/tests/data/formatter_shapes/comment_anchoring.sql +0 -0
  268. {execsql2-2.31.2 → execsql2-2.33.0}/tests/data/formatter_shapes/escapes_and_literals.sql +0 -0
  269. {execsql2-2.31.2 → execsql2-2.33.0}/tests/data/formatter_shapes/substitution_and_metacommands.sql +0 -0
  270. {execsql2-2.31.2 → execsql2-2.33.0}/tests/data/formatter_shapes/unparsable_sql.sql +0 -0
  271. {execsql2-2.31.2 → execsql2-2.33.0}/tests/data/lint/conditions_and_blocks.sql +0 -0
  272. {execsql2-2.31.2 → execsql2-2.33.0}/tests/data/lint/does_not_parse.sql +0 -0
  273. {execsql2-2.31.2 → execsql2-2.33.0}/tests/data/lint/every_metacommand.sql +0 -0
  274. {execsql2-2.31.2 → execsql2-2.33.0}/tests/data/lint/invalid.sql +0 -0
  275. {execsql2-2.31.2 → execsql2-2.33.0}/tests/data/ods/libreoffice.fods +0 -0
  276. {execsql2-2.31.2 → execsql2-2.33.0}/tests/data/ods/libreoffice.ods +0 -0
  277. {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/__init__.py +0 -0
  278. {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/conftest.py +0 -0
  279. {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_access_windows.py +0 -0
  280. {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_base.py +0 -0
  281. {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_copy_new.py +0 -0
  282. {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_db_adapters_mocked.py +0 -0
  283. {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_db_adapters_mocked_extra.py +0 -0
  284. {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_dsn.py +0 -0
  285. {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_duckdb.py +0 -0
  286. {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_factory.py +0 -0
  287. {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_factory_postgres_encoding.py +0 -0
  288. {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_import_atomic.py +0 -0
  289. {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_import_replacement_quoted.py +0 -0
  290. {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_import_short_rows.py +0 -0
  291. {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_import_timezones.py +0 -0
  292. {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_mysql_case_folding.py +0 -0
  293. {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_mysql_inprocess.py +0 -0
  294. {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_mysql_trailing_blank_lines.py +0 -0
  295. {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_new_table_checks.py +0 -0
  296. {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_odbc_fresh_sessions.py +0 -0
  297. {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_oracle_driver.py +0 -0
  298. {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_postgres.py +0 -0
  299. {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_quoted_substitution_roundtrip.py +0 -0
  300. {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_replacement_missing_table.py +0 -0
  301. {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_roundtrip_adapters.py +0 -0
  302. {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_sqlite.py +0 -0
  303. {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_sqlite_extra.py +0 -0
  304. {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_sqlserver_driver18.py +0 -0
  305. {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_sqlserver_inprocess.py +0 -0
  306. {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_tiers.py +0 -0
  307. {execsql2-2.31.2 → execsql2-2.33.0}/tests/db/test_transactions.py +0 -0
  308. {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/__init__.py +0 -0
  309. {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/conftest.py +0 -0
  310. {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_base.py +0 -0
  311. {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_db.py +0 -0
  312. {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_delimited.py +0 -0
  313. {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_duckdb_exporter.py +0 -0
  314. {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_exporters.py +0 -0
  315. {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_feather.py +0 -0
  316. {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_html_extended.py +0 -0
  317. {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_html_latex.py +0 -0
  318. {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_json.py +0 -0
  319. {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_json_extended.py +0 -0
  320. {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_latex_extended.py +0 -0
  321. {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_markdown.py +0 -0
  322. {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_ods.py +0 -0
  323. {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_ods_export.py +0 -0
  324. {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_parquet.py +0 -0
  325. {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_pretty_extended.py +0 -0
  326. {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_raw_extended.py +0 -0
  327. {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_roundtrip_columnar.py +0 -0
  328. {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_roundtrip_dbfile.py +0 -0
  329. {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_roundtrip_delimited.py +0 -0
  330. {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_roundtrip_json.py +0 -0
  331. {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_roundtrip_latex_xml.py +0 -0
  332. {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_roundtrip_markup.py +0 -0
  333. {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_roundtrip_ods.py +0 -0
  334. {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_roundtrip_pretty_raw.py +0 -0
  335. {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_roundtrip_template.py +0 -0
  336. {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_roundtrip_values.py +0 -0
  337. {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_roundtrip_xlsx.py +0 -0
  338. {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_sqlite_exporter.py +0 -0
  339. {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_templates.py +0 -0
  340. {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_templates_extended.py +0 -0
  341. {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_values_extended.py +0 -0
  342. {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_values_roundtrip.py +0 -0
  343. {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_xls_xlsx.py +0 -0
  344. {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_xlsx.py +0 -0
  345. {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_xml.py +0 -0
  346. {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_yaml.py +0 -0
  347. {execsql2-2.31.2 → execsql2-2.33.0}/tests/exporters/test_zip.py +0 -0
  348. {execsql2-2.31.2 → execsql2-2.33.0}/tests/gui/__init__.py +0 -0
  349. {execsql2-2.31.2 → execsql2-2.33.0}/tests/gui/test_backends.py +0 -0
  350. {execsql2-2.31.2 → execsql2-2.33.0}/tests/gui/test_backends_extended.py +0 -0
  351. {execsql2-2.31.2 → execsql2-2.33.0}/tests/gui/test_compare_stats.py +0 -0
  352. {execsql2-2.31.2 → execsql2-2.33.0}/tests/gui/test_compute_row_diffs.py +0 -0
  353. {execsql2-2.31.2 → execsql2-2.33.0}/tests/gui/test_desktop_dialogs.py +0 -0
  354. {execsql2-2.31.2 → execsql2-2.33.0}/tests/gui/test_format_table.py +0 -0
  355. {execsql2-2.31.2 → execsql2-2.33.0}/tests/gui/test_tui_pilot.py +0 -0
  356. {execsql2-2.31.2 → execsql2-2.33.0}/tests/gui/test_tui_pilot_complex.py +0 -0
  357. {execsql2-2.31.2 → execsql2-2.33.0}/tests/gui/test_utils_gui_extended.py +0 -0
  358. {execsql2-2.31.2 → execsql2-2.33.0}/tests/importers/__init__.py +0 -0
  359. {execsql2-2.31.2 → execsql2-2.33.0}/tests/importers/conftest.py +0 -0
  360. {execsql2-2.31.2 → execsql2-2.33.0}/tests/importers/test_base_extended.py +0 -0
  361. {execsql2-2.31.2 → execsql2-2.33.0}/tests/importers/test_csv_blank_lines.py +0 -0
  362. {execsql2-2.31.2 → execsql2-2.33.0}/tests/importers/test_csv_edge_cases.py +0 -0
  363. {execsql2-2.31.2 → execsql2-2.33.0}/tests/importers/test_csv_importer.py +0 -0
  364. {execsql2-2.31.2 → execsql2-2.33.0}/tests/importers/test_feather_importer.py +0 -0
  365. {execsql2-2.31.2 → execsql2-2.33.0}/tests/importers/test_json_importer.py +0 -0
  366. {execsql2-2.31.2 → execsql2-2.33.0}/tests/importers/test_ods_importer.py +0 -0
  367. {execsql2-2.31.2 → execsql2-2.33.0}/tests/importers/test_ods_values.py +0 -0
  368. {execsql2-2.31.2 → execsql2-2.33.0}/tests/importers/test_roundtrip_csv.py +0 -0
  369. {execsql2-2.31.2 → execsql2-2.33.0}/tests/importers/test_xls_importer.py +0 -0
  370. {execsql2-2.31.2 → execsql2-2.33.0}/tests/integration/__init__.py +0 -0
  371. {execsql2-2.31.2 → execsql2-2.33.0}/tests/integration/conftest.py +0 -0
  372. {execsql2-2.31.2 → execsql2-2.33.0}/tests/integration/test_dsn.py +0 -0
  373. {execsql2-2.31.2 → execsql2-2.33.0}/tests/integration/test_duckdb.py +0 -0
  374. {execsql2-2.31.2 → execsql2-2.33.0}/tests/integration/test_mysql.py +0 -0
  375. {execsql2-2.31.2 → execsql2-2.33.0}/tests/integration/test_sqlite.py +0 -0
  376. {execsql2-2.31.2 → execsql2-2.33.0}/tests/live_db.py +0 -0
  377. {execsql2-2.31.2 → execsql2-2.33.0}/tests/lsp/__init__.py +0 -0
  378. {execsql2-2.31.2 → execsql2-2.33.0}/tests/lsp/test_cache.py +0 -0
  379. {execsql2-2.31.2 → execsql2-2.33.0}/tests/lsp/test_code_actions.py +0 -0
  380. {execsql2-2.31.2 → execsql2-2.33.0}/tests/lsp/test_completion.py +0 -0
  381. {execsql2-2.31.2 → execsql2-2.33.0}/tests/lsp/test_diagnostics.py +0 -0
  382. {execsql2-2.31.2 → execsql2-2.33.0}/tests/lsp/test_document.py +0 -0
  383. {execsql2-2.31.2 → execsql2-2.33.0}/tests/lsp/test_folding.py +0 -0
  384. {execsql2-2.31.2 → execsql2-2.33.0}/tests/lsp/test_formatting.py +0 -0
  385. {execsql2-2.31.2 → execsql2-2.33.0}/tests/lsp/test_hover.py +0 -0
  386. {execsql2-2.31.2 → execsql2-2.33.0}/tests/lsp/test_lsp_command.py +0 -0
  387. {execsql2-2.31.2 → execsql2-2.33.0}/tests/lsp/test_navigation.py +0 -0
  388. {execsql2-2.31.2 → execsql2-2.33.0}/tests/lsp/test_rename.py +0 -0
  389. {execsql2-2.31.2 → execsql2-2.33.0}/tests/lsp/test_server.py +0 -0
  390. {execsql2-2.31.2 → execsql2-2.33.0}/tests/metacommands/__init__.py +0 -0
  391. {execsql2-2.31.2 → execsql2-2.33.0}/tests/metacommands/conftest.py +0 -0
  392. {execsql2-2.31.2 → execsql2-2.33.0}/tests/metacommands/test_assert.py +0 -0
  393. {execsql2-2.31.2 → execsql2-2.33.0}/tests/metacommands/test_breakpoint.py +0 -0
  394. {execsql2-2.31.2 → execsql2-2.33.0}/tests/metacommands/test_conditions_extra.py +0 -0
  395. {execsql2-2.31.2 → execsql2-2.33.0}/tests/metacommands/test_connect.py +0 -0
  396. {execsql2-2.31.2 → execsql2-2.33.0}/tests/metacommands/test_io_export.py +0 -0
  397. {execsql2-2.31.2 → execsql2-2.33.0}/tests/metacommands/test_io_import.py +0 -0
  398. {execsql2-2.31.2 → execsql2-2.33.0}/tests/metacommands/test_metacommands.py +0 -0
  399. {execsql2-2.31.2 → execsql2-2.33.0}/tests/metacommands/test_metacommands_connect.py +0 -0
  400. {execsql2-2.31.2 → execsql2-2.33.0}/tests/metacommands/test_metacommands_data.py +0 -0
  401. {execsql2-2.31.2 → execsql2-2.33.0}/tests/metacommands/test_metacommands_extended.py +0 -0
  402. {execsql2-2.31.2 → execsql2-2.33.0}/tests/metacommands/test_metacommands_fileops_extra.py +0 -0
  403. {execsql2-2.31.2 → execsql2-2.33.0}/tests/metacommands/test_metacommands_io.py +0 -0
  404. {execsql2-2.31.2 → execsql2-2.33.0}/tests/metacommands/test_metacommands_io_write_extra.py +0 -0
  405. {execsql2-2.31.2 → execsql2-2.33.0}/tests/metacommands/test_metacommands_script_ext.py +0 -0
  406. {execsql2-2.31.2 → execsql2-2.33.0}/tests/metacommands/test_metacommands_system.py +0 -0
  407. {execsql2-2.31.2 → execsql2-2.33.0}/tests/metacommands/test_metacommands_system_extra.py +0 -0
  408. {execsql2-2.31.2 → execsql2-2.33.0}/tests/metacommands/test_prompt.py +0 -0
  409. {execsql2-2.31.2 → execsql2-2.33.0}/tests/metacommands/test_row_count.py +0 -0
  410. {execsql2-2.31.2 → execsql2-2.33.0}/tests/metacommands/test_serve_output.py +0 -0
  411. {execsql2-2.31.2 → execsql2-2.33.0}/tests/metacommands/test_show_scripts.py +0 -0
  412. {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/__init__.py +0 -0
  413. {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/audit_lint_bad.sql +0 -0
  414. {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/audit_smoke/sample.json +0 -0
  415. {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/audit_smoke/sample.jsonl +0 -0
  416. {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/audit_smoke.sql +0 -0
  417. {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/config_runtime.sql +0 -0
  418. {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/control_flow.sql +0 -0
  419. {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/counters_and_locals.sql +0 -0
  420. {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/error_handling.sql +0 -0
  421. {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/import_options/has_comments.csv +0 -0
  422. {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/import_options.sql +0 -0
  423. {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/includes/helper.sql +0 -0
  424. {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/includes.sql +0 -0
  425. {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/io_formats.sql +0 -0
  426. {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/io_roundtrip.sql +0 -0
  427. {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/multi_db.sql +0 -0
  428. {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/parquet_feather_roundtrip.sql +0 -0
  429. {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/parse_only/parse_tree.sql +0 -0
  430. {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/predicates.sql +0 -0
  431. {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/scripts.sql +0 -0
  432. {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/smoke.sql +0 -0
  433. {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/subroutine_loops.sql +0 -0
  434. {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/subvars_advanced.sql +0 -0
  435. {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/template_export/greeting.tmpl +0 -0
  436. {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/template_export.sql +0 -0
  437. {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/timer.sql +0 -0
  438. {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/transactions.sql +0 -0
  439. {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/write_create_table.sql +0 -0
  440. {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/fixtures/xlsx_ods_roundtrip.sql +0 -0
  441. {execsql2-2.31.2 → execsql2-2.33.0}/tests/scripts/test_sql_scripts.py +0 -0
  442. {execsql2-2.31.2 → execsql2-2.33.0}/tests/security/__init__.py +0 -0
  443. {execsql2-2.31.2 → execsql2-2.33.0}/tests/security/test_env_detection.py +0 -0
  444. {execsql2-2.31.2 → execsql2-2.33.0}/tests/security/test_expansion_bomb.py +0 -0
  445. {execsql2-2.31.2 → execsql2-2.33.0}/tests/security/test_import_regex_hardening.py +0 -0
  446. {execsql2-2.31.2 → execsql2-2.33.0}/tests/security/test_log_redaction.py +0 -0
  447. {execsql2-2.31.2 → execsql2-2.33.0}/tests/security/test_log_secrets.py +0 -0
  448. {execsql2-2.31.2 → execsql2-2.33.0}/tests/security/test_network_paths.py +0 -0
  449. {execsql2-2.31.2 → execsql2-2.33.0}/tests/security/test_odbc_connection_strings.py +0 -0
  450. {execsql2-2.31.2 → execsql2-2.33.0}/tests/security/test_odbc_injection.py +0 -0
  451. {execsql2-2.31.2 → execsql2-2.33.0}/tests/security/test_path_containment.py +0 -0
  452. {execsql2-2.31.2 → execsql2-2.33.0}/tests/security/test_substitution_injection.py +0 -0
  453. {execsql2-2.31.2 → execsql2-2.33.0}/tests/security/test_zip_bomb.py +0 -0
  454. {execsql2-2.31.2 → execsql2-2.33.0}/tests/templates/__init__.py +0 -0
  455. {execsql2-2.31.2 → execsql2-2.33.0}/tests/templates/conftest.py +0 -0
  456. {execsql2-2.31.2 → execsql2-2.33.0}/tests/templates/test_compare.py +0 -0
  457. {execsql2-2.31.2 → execsql2-2.33.0}/tests/templates/test_glossary.py +0 -0
  458. {execsql2-2.31.2 → execsql2-2.33.0}/tests/templates/test_static.py +0 -0
  459. {execsql2-2.31.2 → execsql2-2.33.0}/tests/templates/test_upsert.py +0 -0
  460. {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_api.py +0 -0
  461. {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_api_errors.py +0 -0
  462. {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_api_extended.py +0 -0
  463. {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_api_filewriter.py +0 -0
  464. {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_api_isolation.py +0 -0
  465. {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_api_subprocess.py +0 -0
  466. {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_ast.py +0 -0
  467. {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_ast_parser.py +0 -0
  468. {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_bundled_templates.py +0 -0
  469. {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_config.py +0 -0
  470. {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_config_data.py +0 -0
  471. {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_config_extended.py +0 -0
  472. {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_date_inference.py +0 -0
  473. {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_debug_repl.py +0 -0
  474. {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_error_messages.py +0 -0
  475. {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_exceptions.py +0 -0
  476. {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_executor.py +0 -0
  477. {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_executor_inprocess.py +0 -0
  478. {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_format.py +0 -0
  479. {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_interactive.py +0 -0
  480. {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_mail.py +0 -0
  481. {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_package.py +0 -0
  482. {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_parser.py +0 -0
  483. {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_parser_params.py +0 -0
  484. {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_plugins.py +0 -0
  485. {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_reference.py +0 -0
  486. {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_registry.py +0 -0
  487. {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_release_notes.py +0 -0
  488. {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_state.py +0 -0
  489. {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_tooling_versions.py +0 -0
  490. {execsql2-2.31.2 → execsql2-2.33.0}/tests/test_types.py +0 -0
  491. {execsql2-2.31.2 → execsql2-2.33.0}/tests/utils/__init__.py +0 -0
  492. {execsql2-2.31.2 → execsql2-2.33.0}/tests/utils/test_auth.py +0 -0
  493. {execsql2-2.31.2 → execsql2-2.33.0}/tests/utils/test_auth_extra.py +0 -0
  494. {execsql2-2.31.2 → execsql2-2.33.0}/tests/utils/test_crypto.py +0 -0
  495. {execsql2-2.31.2 → execsql2-2.33.0}/tests/utils/test_encodedfile_context.py +0 -0
  496. {execsql2-2.31.2 → execsql2-2.33.0}/tests/utils/test_errors.py +0 -0
  497. {execsql2-2.31.2 → execsql2-2.33.0}/tests/utils/test_errors_extra.py +0 -0
  498. {execsql2-2.31.2 → execsql2-2.33.0}/tests/utils/test_fileio.py +0 -0
  499. {execsql2-2.31.2 → execsql2-2.33.0}/tests/utils/test_fileio_extra.py +0 -0
  500. {execsql2-2.31.2 → execsql2-2.33.0}/tests/utils/test_keyring_rules.py +0 -0
  501. {execsql2-2.31.2 → execsql2-2.33.0}/tests/utils/test_mail_tls.py +0 -0
  502. {execsql2-2.31.2 → execsql2-2.33.0}/tests/utils/test_numeric.py +0 -0
  503. {execsql2-2.31.2 → execsql2-2.33.0}/tests/utils/test_regex.py +0 -0
  504. {execsql2-2.31.2 → execsql2-2.33.0}/tests/utils/test_strings.py +0 -0
  505. {execsql2-2.31.2 → execsql2-2.33.0}/tests/utils/test_timer.py +0 -0
  506. {execsql2-2.31.2 → execsql2-2.33.0}/tests/utils/test_timer_extra.py +0 -0
  507. {execsql2-2.31.2 → execsql2-2.33.0}/zensical.toml +0 -0
@@ -13,6 +13,33 @@ ______________________________________________________________________
13
13
 
14
14
  ______________________________________________________________________
15
15
 
16
+ ## [2.33.0] - 2026-10-16
17
+
18
+ ### Added
19
+
20
+ - **`$PG_UPSERT_STATUS`** says how a `PG_UPSERT` ended (`committed`, `dry_run`, `no_changes`, `qa_failed`, `cancelled`, `db_error`), and **`$PG_UPSERT_ERROR`** holds the database error message. A run with nothing to load and a cancelled run used to look the same in the other variables.
21
+
22
+ ### Fixed
23
+
24
+ - **A failed metacommand sets `$LAST_ERROR` and `$ERROR_MESSAGE`** with `METACOMMAND_ERROR_HALT OFF`, so a script can tell what failed, not only that `METACOMMAND_ERROR()` is true; an `ON ERROR_HALT` handler (including `ON ERROR_HALT WRITE "…!{$LAST_ERROR}!"`) now gets `$LAST_ERROR` too. The error is also written to `execsql.log`.
25
+ - **`execsql.run()` reports a metacommand that failed under `METACOMMAND_ERROR_HALT OFF`** in `result.errors`, with `success` False, as it already did for a SQL error under `ERROR_HALT OFF`.
26
+ - **`PG_UPSERT` cancelled from a prompt** no longer leaves an earlier run's `$PG_UPSERT_*` values (such as `$PG_UPSERT_COMMITTED` `TRUE`) in place.
27
+
28
+ ______________________________________________________________________
29
+
30
+ ## [2.32.0] - 2026-10-16
31
+
32
+ ### Changed
33
+
34
+ - **Working out column types is about twice as fast** for `IMPORT`, `COPY` and the exports that create a table (`EXPORT … AS SQLITE`, `DUCKDB`, `JSON`, `FEATHER`); a CSV whose dates repeat scans close to three times as fast. The types chosen are the same.
35
+ - **`IMPORT`, `COPY` and `EXPORT … AS DUCKDB` into DuckDB are about five times as fast**: they insert many rows per statement. Copying 200,000 rows took 124 s and now takes 22 s.
36
+
37
+ ### Fixed
38
+
39
+ - **A column of large integers**, such as epoch milliseconds, no longer makes `IMPORT`, `COPY` or those exports hang or run out of memory while execsql works out the column types.
40
+
41
+ ______________________________________________________________________
42
+
16
43
  ## [2.31.2] - 2026-10-16
17
44
 
18
45
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: execsql2
3
- Version: 2.31.2
3
+ Version: 2.33.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/en/stable/
6
6
  Project-URL: Repository, https://github.com/geocoug/execsql
@@ -441,7 +441,7 @@ execsql format --no-sql --in-place scripts/
441
441
  ```yaml
442
442
  repos:
443
443
  - repo: https://github.com/geocoug/execsql
444
- rev: v2.31.2
444
+ rev: v2.33.0
445
445
  hooks:
446
446
  - id: execsql-format
447
447
  - id: execsql-lint
@@ -321,7 +321,7 @@ execsql format --no-sql --in-place scripts/
321
321
  ```yaml
322
322
  repos:
323
323
  - repo: https://github.com/geocoug/execsql
324
- rev: v2.31.2
324
+ rev: v2.33.0
325
325
  hooks:
326
326
  - id: execsql-format
327
327
  - id: execsql-lint
@@ -48,15 +48,15 @@ ______________________________________________________________________
48
48
 
49
49
  ### Metacommands
50
50
 
51
- | Metacommand | Description |
52
- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
53
- | `ASSERT` | Evaluate a condition and raise an error (halting the script) if it is false. Supports all IF conditions. Optional quoted failure message. Skipped in false IF blocks. |
54
- | `BREAKPOINT` | Pause script execution and drop into an interactive debug REPL. See [Debugging](#debugging) below for full details. |
55
- | `CONFIG SHOW_PROGRESS` | Enable the Rich progress bar for IMPORT operations at runtime. |
56
- | `CONFIG LOG_SQL` | Enable SQL query audit logging — writes executed SQL to the log file. |
57
- | `PG_UPSERT` | QA-checked, FK-dependency-ordered upserts from staging to base schema on PostgreSQL via the optional [pg-upsert](https://pg-upsert.readthedocs.io/) dependency. Modes: `PG_UPSERT` (full pipeline), `PG_UPSERT QA` (checks only), `PG_UPSERT CHECK` (schema only). QA checks only the rows the given `METHOD` would write. QA failure does not raise — the result is reported via `$PG_UPSERT_QA_PASSED` so the caller's `IF` / `ASSERT` decides control flow. |
58
- | `IMPORT … FROM JSON` | Import a JSON file (array of objects or [JSON Lines](https://jsonlines.org/)) into a database table. Nested objects are flattened with dot-separated column names; arrays are stored as JSON strings. |
59
- | `SHOW SCRIPTS [<name>]` | Without a name, lists all registered SCRIPT definitions with parameter signatures and source locations. With a name, shows detail including parameters (with defaults), source file/line range, and docstring. |
51
+ | Metacommand | Description |
52
+ | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
53
+ | `ASSERT` | Evaluate a condition and raise an error (halting the script) if it is false. Supports all IF conditions. Optional quoted failure message. Skipped in false IF blocks. |
54
+ | `BREAKPOINT` | Pause script execution and drop into an interactive debug REPL. See [Debugging](#debugging) below for full details. |
55
+ | `CONFIG SHOW_PROGRESS` | Enable the Rich progress bar for IMPORT operations at runtime. |
56
+ | `CONFIG LOG_SQL` | Enable SQL query audit logging — writes executed SQL to the log file. |
57
+ | `PG_UPSERT` | QA-checked, FK-dependency-ordered upserts from staging to base schema on PostgreSQL via the optional [pg-upsert](https://pg-upsert.readthedocs.io/) dependency. Modes: `PG_UPSERT` (full pipeline), `PG_UPSERT QA` (checks only), `PG_UPSERT CHECK` (schema only). QA checks only the rows the given `METHOD` would write. QA failure does not raise — the result is reported via `$PG_UPSERT_QA_PASSED` so the caller's `IF` / `ASSERT` decides control flow; `$PG_UPSERT_STATUS` says how the run ended. |
58
+ | `IMPORT … FROM JSON` | Import a JSON file (array of objects or [JSON Lines](https://jsonlines.org/)) into a database table. Nested objects are flattened with dot-separated column names; arrays are stored as JSON strings. |
59
+ | `SHOW SCRIPTS [<name>]` | Without a name, lists all registered SCRIPT definitions with parameter signatures and source locations. With a name, shows detail including parameters (with defaults), source file/line range, and docstring. |
60
60
 
61
61
  ### SCRIPT Enhancements
62
62
 
@@ -264,6 +264,7 @@ All 33 mutable runtime globals in `state.py` have been consolidated into a `Runt
264
264
  - **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.26.0 dependency, which itself requires a psycopg3 connection. The connection sets `prepare_threshold=None` to disable psycopg3's automatic server-side prepared statements, which psycopg2 never used; without this, re-running a query (e.g. via `EXPORT`) against an object the script drops/recreates or alters raises `FeatureNotSupported: cached plan must not change result type`.
265
265
  - **Oracle driver is python-oracledb** — the Oracle adapter imports `oracledb`, the driver the `oracle` extra installs, instead of upstream's `cx_Oracle` (which ships no wheels for Python 3.12+). The `makedsn` / `connect` calls are the same. `cx_Oracle` is still used when it is the only Oracle driver installed.
266
266
  - **SQL Server tries ODBC Driver 18 first** — upstream's driver list stopped at Driver 17, so a machine with only Microsoft's current driver could not connect. Driver 18 encrypts the connection and verifies the server's certificate; the `trust_server_certificate` setting accepts a self-signed one.
267
+ - **DuckDB bulk inserts** — `IMPORT`, `COPY` and `EXPORT ... AS DUCKDB` insert many rows per `INSERT ... VALUES` statement instead of `executemany()`, which DuckDB runs one row at a time; the values stored are the same. While binding them, and only when pandas is not installed, `sys.modules["pandas"]` is `None`: the duckdb package looks pandas up twice per row, and without that entry each lookup searched `sys.path`.
267
268
  - **Each pyodbc connection is a new session** — the SQL Server, Access and ODBC DSN adapters set `pyodbc.pooling = False` before they connect. With pyodbc's default pooling, unixODBC (macOS, Linux) handed a closed connection's session unchanged to the next connection to the same server, so temp tables and `SET` options from one `CONNECT`, or one `execsql.run()`, were still there in the next. pyodbc applies the setting at the process's first pyodbc connection, so it also covers other pyodbc connections the process makes, and has no effect if the program connected with pyodbc before execsql did.
268
269
  - **`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.
269
270
  - **Connection timeouts** — PostgreSQL and SQLite adapters accept a connection timeout parameter (default 30 seconds).
@@ -348,7 +349,7 @@ These are behavioral changes driven by security or correctness issues in the ups
348
349
  | `WRITE` after `CD` | A relative `WRITE ... TO` path is resolved against the working directory at the time of the `WRITE`, so it follows an earlier `CD` the way `EXPORT` and `IMPORT` already did. Upstream wrote it relative to the directory execsql started in, because its writer subprocess never saw the `CD`. |
349
350
  | Substitution variable cycles | 100-iteration limit prevents infinite loops on cyclic variable references. |
350
351
  | Script location in error messages | `ErrInfo.script_file` and `script_line_no` are now populated via `stamp_errinfo()` so error output includes "Line N of script foo.sql" context — restoring behavior present in the monolith. |
351
- | `$ERROR_MESSAGE` not updated | `$ERROR_MESSAGE` is now set on every error path: `exit_now()`, non-halting SQL errors, and non-halting metacommand errors. Previously it was initialized to `""` and never changed. |
352
+ | `$ERROR_MESSAGE` not updated | `$ERROR_MESSAGE` is now set on every error path: `exit_now()`, non-halting SQL errors, and non-halting metacommand errors. Previously it was initialized to `""` and never changed. `$LAST_ERROR` also holds a failed metacommand, with `METACOMMAND_ERROR_HALT OFF` and in an `ON ERROR_HALT` handler, as the documentation describes; upstream set it only for SQL errors and unknown metacommands. |
352
353
  | Metacommand error message lost | When `halt_on_metacommand_err` is `ON`, the original handler `ErrInfo` is now re-raised; the generic "Unknown metacommand" message no longer replaces the specific error from the handler. |
353
354
  | Empty script name in error msg | `_execute_script_direct()` and `_execute_script_textual_console()` no longer append "in script , line 0" to uncaught-exception messages when `current_script_line()` returns an empty string. |
354
355
  | `PROMPT COMPARE` diff comparison | Diff engine uses native Python equality instead of string comparison — numeric types, Decimals, and booleans compare correctly. `None` is distinguished from empty string. Columns are matched by name (not position), key columns are excluded from comparison, and duplicate PKs keep the first row. |
@@ -434,6 +435,7 @@ These are behavioral changes driven by security or correctness issues in the ups
434
435
  | ODS export replaced newlines inside a value with spaces | String cells were written after `item.replace("\n", " ")`, so an exported value silently differed from the one in the database. An XML parser does normalise a newline inside an attribute, so `office:string-value` cannot carry one — but ODF represents a multi-line cell as one `<text:p>` per line, which is what LibreOffice writes and what execsql2 now writes. Single-line values still use the attribute. |
435
436
  | XLSX export wrote `numeric` columns as text | `_cell_value()` passed `int` and `float` straight to openpyxl but let every other type fall through to `str()`. PostgreSQL, MySQL, and DuckDB all return `Decimal` for `numeric`/`decimal` columns, so measurement values arrived in Excel as text cells — invisible in a screenshot, useless in a formula, a sort, or a chart. openpyxl lists `Decimal` in its own `NUMERIC_TYPES` and stores it as a real numeric cell, so the conversion was never needed. Found by exporting from a live PostgreSQL container and checking `cell.data_type` rather than `cell.value`. |
436
437
  | `EMAIL` SMTP login without a password | Upstream calls `smtplib.login()` with only a username when `[email] password` and `enc_password` are both unset, which raises `TypeError` from the stdlib — `login()` has no single-argument form. execsql2 reports a configuration error naming the missing password instead. |
438
+ | Type scan of large integers | Working out a column's type tested each integer against the binary type by building `bytearray(n)`, n zero bytes, so a column of large integers (epoch milliseconds: about a terabyte per value) hung or ran out of memory. The binary test now answers for an integer without building it; the inferred types are unchanged. |
437
439
 
438
440
  ______________________________________________________________________
439
441
 
@@ -148,6 +148,7 @@ These are the instance attributes and methods you must configure correctly:
148
148
  | `schema_exists()` | method | **Must override.** Return `False` if schemas are not supported. |
149
149
  | `drop_table()` | method | **Must override.** Drop a table (used by IMPORT when creating fresh). |
150
150
  | `populate_table()` | method | **Must override.** Bulk-load rows from a generator (used by IMPORT). |
151
+ | `row_inserter()` | method | Override for a faster bulk path that stores exactly what `executemany()` stores. |
151
152
  | `exec_cmd()` | method | Override if the DBMS can execute stored procedures or views as commands. |
152
153
 
153
154
  Methods inherited from `Database` that you get for free include `execute()`, `cursor()`, `close()`, `commit()`, `rollback()`, `select_rowsource()`, `select_data()`, `schema_qualified_table_name()`, `quote_identifier()`, and `paramsubs()`.
@@ -297,7 +297,7 @@ ______________________________________________________________________
297
297
  1. An `IMPORT` metacommand is dispatched to the appropriate handler.
298
298
  1. The handler instantiates the correct importer (e.g., `importers/csv.py`, `importers/ods.py`) based on the file type.
299
299
  1. The importer reads the file and yields rows.
300
- 1. `Database.populate_table()` bulk-inserts the rows using `executemany()`, with optional progress bars.
300
+ 1. `Database.populate_table()` converts the rows and inserts them in batches of `import_row_buffer` rows, in one transaction, with optional progress bars. Each batch goes through the adapter's `row_inserter()`: `executemany()` by default, and multi-row `INSERT ... VALUES` on DuckDB, whose `executemany()` binds one row at a time.
301
301
 
302
302
  ### Export metadata
303
303
 
@@ -272,7 +272,7 @@ select id,name,created_at from users where active = true order by name;
272
272
  ```yaml
273
273
  repos:
274
274
  - repo: https://github.com/geocoug/execsql
275
- rev: v2.31.2
275
+ rev: v2.33.0
276
276
  hooks:
277
277
  - id: execsql-format
278
278
  - id: execsql-lint # optional: fail the commit on lint errors
@@ -158,7 +158,7 @@ The `execsql-lint` [pre-commit](https://pre-commit.com/) hook runs `execsql lint
158
158
  ```yaml
159
159
  repos:
160
160
  - repo: https://github.com/geocoug/execsql
161
- rev: v2.31.2
161
+ rev: v2.33.0
162
162
  hooks:
163
163
  - id: execsql-lint
164
164
  args: [--ignore, V002] # optional: any execsql lint option
@@ -1766,7 +1766,7 @@ Evaluates whether or not the specified value is equal to zero. If the value is n
1766
1766
  METACOMMAND_ERROR()
1767
1767
  ```
1768
1768
 
1769
- Evaluates whether the previous metacommand generated an error. This test for SQL errors will only be effective if the [METACOMMAND_ERROR_HALT OFF](#metacommanderrorhalt) metacommand has previously been issued. This conditional must be used in the first metacommand after any metacommand that might have encountered an error.
1769
+ Evaluates whether the previous metacommand generated an error. This test for SQL errors will only be effective if the [METACOMMAND_ERROR_HALT OFF](#metacommanderrorhalt) metacommand has previously been issued. This conditional must be used in the first metacommand after any metacommand that might have encountered an error: any metacommand that succeeds, even a WRITE, clears the flag. The error itself stays available until the next error: [$LAST_ERROR](substitution_vars.md#system_vars) holds the metacommand that failed and [$ERROR_MESSAGE](substitution_vars.md#system_vars) its message.
1770
1770
 
1771
1771
 
1772
1772
  ### *NEWER_DATE*
@@ -2109,7 +2109,7 @@ The [BREAK](#break) metacommand can be used to exit from a loop at any time.
2109
2109
  METACOMMAND_ERROR_HALT ON|OFF
2110
2110
  ```
2111
2111
 
2112
- When METACOMMAND_ERROR_HALT is set to ON, which is the default, any errors that occur during execution of a metacommand will cause an error message to be displayed immediately, and *execsql* to halt. When METACOMMAND_ERROR_HALT is set to OFF, then metacommand errors will be ignored, but can be evaluated with the [IF METACOMMAND_ERROR](#metacommanderror) conditional.
2112
+ When METACOMMAND_ERROR_HALT is set to ON, which is the default, any errors that occur during execution of a metacommand will cause an error message to be displayed immediately, and *execsql* to halt. When METACOMMAND_ERROR_HALT is set to OFF, then metacommand errors will be ignored, but can be evaluated with the [IF METACOMMAND_ERROR](#metacommanderror) conditional; `$LAST_ERROR` and `$ERROR_MESSAGE` hold the failed metacommand and its message.
2113
2113
 
2114
2114
  The METACOMMAND_ERROR_HALT metacommand does not itself set or reset the internal flag that indicates whether a metacommand has encountered an error. This is so that a "METACOMMAND_ERROR_HALT Off" command can be used immediately after a potentially-failing metacommand and not alter the error flag that is set by the previous command. Specifically, constructions like this:
2115
2115
 
@@ -2356,6 +2356,8 @@ Set after every `PG_UPSERT` execution:
2356
2356
  | `$PG_UPSERT_ROWS_UPDATED` | integer | Total rows updated across all tables |
2357
2357
  | `$PG_UPSERT_ROWS_INSERTED` | integer | Total rows inserted across all tables |
2358
2358
  | `$PG_UPSERT_COMMITTED` | TRUE/FALSE | Were changes committed? |
2359
+ | `$PG_UPSERT_STATUS` | string | How the run ended: `committed`, `dry_run` (changes made, then rolled back because `COMMIT` was not given), `no_changes` (QA passed, nothing to insert or update), `qa_failed`, `cancelled` or `db_error`. Empty after `PG_UPSERT QA` and `PG_UPSERT CHECK`, which do not upsert. |
2360
+ | `$PG_UPSERT_ERROR` | string | The database error message when `$PG_UPSERT_STATUS` is `db_error`; otherwise empty |
2359
2361
  | `$PG_UPSERT_STAGING_SCHEMA` | string | Staging schema name used |
2360
2362
  | `$PG_UPSERT_BASE_SCHEMA` | string | Base schema name used |
2361
2363
  | `$PG_UPSERT_TABLES` | string | Comma-separated list of table names processed |
@@ -2380,7 +2382,7 @@ Set after every `PG_UPSERT` execution:
2380
2382
 
2381
2383
  ### QA failure behavior
2382
2384
 
2383
- `PG_UPSERT`, `PG_UPSERT QA`, and `PG_UPSERT CHECK` do **not** raise an error when QA checks fail. The outcome is reported via `$PG_UPSERT_QA_PASSED` (and the per-table `$PG_UPSERT_TABLE_QA_PASSED`), so the script controls flow with `IF` or with execsql's standard error-halt configuration. When QA fails, the metacommand still runs `EXPORT_FAILURES`, populates all `$PG_UPSERT_*` substitution variables, and skips the upsert (no commit). Pair with `ASSERT` to halt explicitly:
2385
+ `PG_UPSERT`, `PG_UPSERT QA`, and `PG_UPSERT CHECK` do **not** raise an error when QA checks fail. The outcome is reported via `$PG_UPSERT_QA_PASSED` (and the per-table `$PG_UPSERT_TABLE_QA_PASSED`), so the script controls flow with `IF` or with execsql's standard error-halt configuration. When QA fails, the metacommand still runs `EXPORT_FAILURES`, populates all `$PG_UPSERT_*` substitution variables, and skips the upsert (no commit). To tell the ways a run can end apart, branch on `$PG_UPSERT_STATUS` rather than combining `$PG_UPSERT_QA_PASSED` with `$PG_UPSERT_COMMITTED`: a run with nothing to load (`no_changes`) and one the user cancelled (`cancelled`) both leave QA passed and nothing committed. Pair with `ASSERT` to halt explicitly:
2384
2386
 
2385
2387
  ```sql
2386
2388
  -- Soft failure: branch on the result, keep running.
@@ -2398,7 +2400,7 @@ Set after every `PG_UPSERT` execution:
2398
2400
 
2399
2401
  ### Database errors
2400
2402
 
2401
- A database error during the run (a deadlock, a lock timeout, a constraint or trigger error the QA checks did not catch) is different from a QA failure. pg-upsert rolls the run back, and `PG_UPSERT` fails like any other metacommand: the driver's message is in `$ERROR_MESSAGE`, and the script halts unless `METACOMMAND_ERROR_HALT` is off. The `$PG_UPSERT_*` variables describe the failed run (`$PG_UPSERT_COMMITTED` is `FALSE` and the row counts are `0`), not an earlier `PG_UPSERT`.
2403
+ A database error during the run (a deadlock, a lock timeout, a constraint or trigger error the QA checks did not catch) is different from a QA failure. pg-upsert rolls the run back, and `PG_UPSERT` fails like any other metacommand: the driver's message is in `$ERROR_MESSAGE`, and the script halts unless `METACOMMAND_ERROR_HALT` is off. The `$PG_UPSERT_*` variables describe the failed run (`$PG_UPSERT_STATUS` is `db_error`, `$PG_UPSERT_ERROR` holds the message, `$PG_UPSERT_COMMITTED` is `FALSE` and the row counts are `0`), not an earlier `PG_UPSERT`. Cancelling at one of pg-upsert's own prompts is not an error: `PG_UPSERT` returns, with `$PG_UPSERT_STATUS` `cancelled`.
2402
2404
 
2403
2405
  The rollback ends the connection's transaction, so under `AUTOCOMMIT OFF` or inside `BEGIN BATCH`, statements run earlier on the same connection are rolled back with it.
2404
2406
 
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "execsql-syntax",
3
- "version": "2.31.2",
3
+ "version": "2.33.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "execsql-syntax",
9
- "version": "2.31.2",
9
+ "version": "2.33.0",
10
10
  "license": "GPL-3.0-or-later",
11
11
  "dependencies": {
12
12
  "vscode-languageclient": "10.1.2"
@@ -2,7 +2,7 @@
2
2
  "name": "execsql-syntax",
3
3
  "displayName": "execsql",
4
4
  "description": "Syntax highlighting, lint findings, completion, hover, navigation, quick fixes and formatting for execsql scripts.",
5
- "version": "2.31.2",
5
+ "version": "2.33.0",
6
6
  "publisher": "geocoug",
7
7
  "license": "GPL-3.0-or-later",
8
8
  "icon": "icon.png",
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "execsql2"
7
- version = "2.31.2"
7
+ version = "2.33.0"
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" }
@@ -188,7 +188,7 @@ line-ending = "auto"
188
188
  exclude = ["*.md"]
189
189
 
190
190
  [tool.bumpversion]
191
- current_version = "2.31.2"
191
+ current_version = "2.33.0"
192
192
  commit = true
193
193
  tag = true
194
194
  tag_name = "v{new_version}"
@@ -665,6 +665,23 @@ class Database(ABC):
665
665
  self.execute(f"drop table if exists {tablename} cascade;")
666
666
  self.commit()
667
667
 
668
+ def row_inserter(self, curs: Any, table: str, columns: list[str]) -> Callable[[list[list[Any]]], None]:
669
+ """Return a function that inserts one batch of rows into *table* through *curs*.
670
+
671
+ *table* is schema-qualified and quoted; *columns* are the quoted
672
+ column names, in the order of each row's values. IMPORT and COPY call
673
+ the function once per batch, all inside one transaction. This one
674
+ runs ``executemany``. An adapter may override it with a faster bulk
675
+ path only if that path stores exactly what ``executemany`` stores, for
676
+ every value type and column type: a faster load must not change data.
677
+ """
678
+ sql = f"insert into {table} ({','.join(columns)}) values ({self.paramsubs(len(columns))});"
679
+
680
+ def insert(rows: list[list[Any]]) -> None:
681
+ curs.executemany(sql, rows)
682
+
683
+ return insert
684
+
668
685
  def populate_table(
669
686
  self,
670
687
  schema_name: str | None,
@@ -740,6 +757,7 @@ class Database(ABC):
740
757
 
741
758
  def _import_loop(curs) -> int:
742
759
  nonlocal eof, total_rows, task_id
760
+ insert = self.row_inserter(curs, sq_name, columns)
743
761
  while True:
744
762
  b = []
745
763
  for _j in range(_state.conf.import_row_buffer):
@@ -809,7 +827,7 @@ class Database(ABC):
809
827
  b.append(row)
810
828
  if len(b) > 0:
811
829
  try:
812
- curs.executemany(sql, b)
830
+ insert(b)
813
831
  except ErrInfo:
814
832
  raise
815
833
  except Exception as e:
@@ -8,8 +8,12 @@ analytics databases via the ``duckdb`` package. Corresponds to ``-t k``
8
8
  on the CLI.
9
9
  """
10
10
 
11
+ import importlib.util
11
12
  import re
13
+ import sys
12
14
  from collections import deque
15
+ from collections.abc import Callable, Iterator
16
+ from contextlib import contextmanager
13
17
  from pathlib import Path
14
18
  from typing import Any
15
19
 
@@ -18,7 +22,7 @@ from execsql.exceptions import ErrInfo
18
22
  from execsql.utils.errors import exception_desc, fatal_error, install_hint
19
23
  import execsql.state as _state
20
24
 
21
- __all__ = ["DuckDBDatabase"]
25
+ __all__ = ["DuckDBDatabase", "insert_rows"]
22
26
 
23
27
  # Statements that return rows; any other statement DuckDB answers with a
24
28
  # one-column "Count" result (rows inserted, updated or deleted).
@@ -28,6 +32,53 @@ _QUERY_RX = re.compile(r"(?:SELECT|WITH|VALUES|FROM|TABLE|SHOW|DESCRIBE|SUMMARIZ
28
32
  _TRANSACTION_RX = re.compile(r"(?P<begin>BEGIN|START\s+TRANSACTION)\b|(?P<end>COMMIT|END|ROLLBACK|ABORT)\b", re.I)
29
33
 
30
34
 
35
+ # Parameters bound by one INSERT ... VALUES statement in insert_rows(); a
36
+ # wide table gets fewer rows per statement.
37
+ _PARAMS_PER_STATEMENT = 10_000
38
+
39
+
40
+ @contextmanager
41
+ def _pandas_lookup_skipped() -> Iterator[None]:
42
+ """Make DuckDB's check for pandas fail at once while it binds rows.
43
+
44
+ The duckdb package asks Python's import system for ``pandas`` twice for
45
+ every row it binds, and when pandas is not installed nothing remembers
46
+ the answer, so each row searches ``sys.path`` again: about three
47
+ quarters of an INSERT's time. A ``None`` in ``sys.modules`` makes that
48
+ import fail without the search. It is set only when pandas is not
49
+ installed, so no import that could have succeeded fails, and it is
50
+ removed afterwards.
51
+ """
52
+ if "pandas" in sys.modules or importlib.util.find_spec("pandas") is not None:
53
+ yield
54
+ return
55
+ sys.modules["pandas"] = None # type: ignore[assignment]
56
+ try:
57
+ yield
58
+ finally:
59
+ if "pandas" in sys.modules and sys.modules["pandas"] is None:
60
+ del sys.modules["pandas"]
61
+
62
+
63
+ def insert_rows(curs: Any, table: str, columns: list[str], rows: list[Any]) -> None:
64
+ """Insert *rows* into *table* with multi-row ``INSERT ... VALUES`` statements.
65
+
66
+ *table* is quoted (and schema-qualified as needed); *columns* are the
67
+ quoted column names, in the order of each row's values. DuckDB's
68
+ ``executemany`` runs one row at a time; binding many rows in one
69
+ statement is several times faster and stores the same values.
70
+ """
71
+ if not rows:
72
+ return
73
+ per_statement = max(1, _PARAMS_PER_STATEMENT // max(1, len(columns)))
74
+ head = f"insert into {table} ({','.join(columns)}) values "
75
+ row_marks = "(" + ",".join("?" * len(columns)) + ")"
76
+ with _pandas_lookup_skipped():
77
+ for start in range(0, len(rows), per_statement):
78
+ chunk = rows[start : start + per_statement]
79
+ curs.execute(head + ",".join([row_marks] * len(chunk)), [value for row in chunk for value in row])
80
+
81
+
31
82
  class _SharedCursor:
32
83
  """A cursor that runs on the database's one connection.
33
84
 
@@ -261,6 +312,14 @@ class DuckDBDatabase(Database):
261
312
  curs.execute(sql, params)
262
313
  return len(curs.fetchall()) > 0
263
314
 
315
+ def row_inserter(self, curs: Any, table: str, columns: list[str]) -> Callable[[list[list[Any]]], None]:
316
+ """Insert each batch with multi-row ``INSERT ... VALUES`` (:func:`insert_rows`)."""
317
+
318
+ def insert(rows: list[list[Any]]) -> None:
319
+ insert_rows(curs, table, columns, rows)
320
+
321
+ return insert
322
+
264
323
  def view_exists(self, view_name: str, schema_name: str | None = None) -> bool:
265
324
  """Return True if the named view exists in the DuckDB database."""
266
325
  # DuckDB information_schema has no 'views' table; views are listed in 'tables'
@@ -10,7 +10,6 @@ Requires the ``execsql2[duckdb]`` extra.
10
10
 
11
11
  import datetime as _datetime
12
12
  import decimal as _decimal
13
- import math
14
13
  from pathlib import Path
15
14
  from typing import Any
16
15
 
@@ -38,9 +37,9 @@ def export_duckdb(
38
37
  fatal_error(f"The duckdb module is required to export data in that format. {install_hint('duckdb')}")
39
38
  return
40
39
 
40
+ from execsql.db.duckdb import insert_rows
41
41
  from execsql.models import DataTable
42
42
 
43
- chunksize = 10000
44
43
  # B07a/F026: tablename and catalog are substituted from user input.
45
44
  # Identifier-quote the table-name interpolation site and use
46
45
  # placeholders for the information_schema literal-value query.
@@ -70,15 +69,8 @@ def export_duckdb(
70
69
  curs.execute(sql)
71
70
  # Export all rows of data
72
71
  columns = [dbt_duckdb.quoted(col) for col in hdrs]
73
- colspec = ",".join(columns)
74
- paramspec = ",".join(("?",) * len(columns))
75
- sql = f"insert into {qtable} ({colspec}) values ({paramspec});"
76
- n_chunks = math.ceil(len(rowdata) / chunksize)
77
72
  curs.execute("BEGIN TRANSACTION;")
78
- for i in range(n_chunks):
79
- start = i * chunksize
80
- end = start + chunksize
81
- curs.executemany(sql, rowdata[start:end])
73
+ insert_rows(curs, qtable, columns, rowdata)
82
74
  curs.execute("COMMIT;")
83
75
  curs.close()
84
76
  ddb.close()
@@ -1364,6 +1364,16 @@ _SYSTEM_VARIABLES: tuple[SystemVariable, ...] = (
1364
1364
  "TRUE or FALSE: whether the changes were committed.",
1365
1365
  "metacommands.md#pg_upsert",
1366
1366
  ),
1367
+ SystemVariable(
1368
+ "$PG_UPSERT_STATUS",
1369
+ "How the run ended: committed, dry_run, no_changes, qa_failed, cancelled or db_error.",
1370
+ "metacommands.md#pg_upsert",
1371
+ ),
1372
+ SystemVariable(
1373
+ "$PG_UPSERT_ERROR",
1374
+ "The database error message when $PG_UPSERT_STATUS is db_error; otherwise empty.",
1375
+ "metacommands.md#pg_upsert",
1376
+ ),
1367
1377
  SystemVariable("$PG_UPSERT_STAGING_SCHEMA", "Staging schema name used.", "metacommands.md#pg_upsert"),
1368
1378
  SystemVariable("$PG_UPSERT_BASE_SCHEMA", "Base schema name used.", "metacommands.md#pg_upsert"),
1369
1379
  SystemVariable("$PG_UPSERT_TABLES", "Comma-separated list of table names processed.", "metacommands.md#pg_upsert"),
@@ -185,6 +185,11 @@ def _set_subvars(result: Any) -> None:
185
185
  sv("$PG_UPSERT_ROWS_UPDATED", str(result.total_updated))
186
186
  sv("$PG_UPSERT_ROWS_INSERTED", str(result.total_inserted))
187
187
  sv("$PG_UPSERT_COMMITTED", str(result.committed).upper())
188
+ # How the run ended: committed, dry_run, no_changes, qa_failed, cancelled
189
+ # or db_error. Empty for QA and CHECK, which do not run an upsert.
190
+ status = getattr(result, "status", None)
191
+ sv("$PG_UPSERT_STATUS", status.value if status is not None else "")
192
+ sv("$PG_UPSERT_ERROR", getattr(result, "error", None) or "")
188
193
  sv("$PG_UPSERT_STAGING_SCHEMA", result.staging_schema)
189
194
  sv("$PG_UPSERT_BASE_SCHEMA", result.base_schema)
190
195
  sv("$PG_UPSERT_TABLES", ", ".join(t.table_name for t in result.tables))
@@ -426,6 +431,14 @@ def _handle_pg_upsert_errors(fn: Any, metacommandline: str | None) -> Any:
426
431
  try:
427
432
  return fn()
428
433
  except UserCancelledError as exc:
434
+ # No result describes a run cancelled outside pg-upsert's own run();
435
+ # say what is known rather than leave an earlier run's values.
436
+ sv = _state.subvars.add_substitution
437
+ sv("$PG_UPSERT_STATUS", "cancelled")
438
+ sv("$PG_UPSERT_ERROR", "")
439
+ sv("$PG_UPSERT_COMMITTED", "FALSE")
440
+ sv("$PG_UPSERT_ROWS_UPDATED", "0")
441
+ sv("$PG_UPSERT_ROWS_INSERTED", "0")
429
442
  raise ErrInfo(
430
443
  "cmd",
431
444
  command_text=metacommandline,