execsql2 2.21.1__tar.gz → 2.22.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 (374) hide show
  1. {execsql2-2.21.1 → execsql2-2.22.0}/.github/workflows/ci-cd.yml +63 -14
  2. {execsql2-2.21.1 → execsql2-2.22.0}/.pre-commit-config.yaml +12 -0
  3. {execsql2-2.21.1 → execsql2-2.22.0}/CHANGELOG.md +23 -0
  4. {execsql2-2.21.1 → execsql2-2.22.0}/CONTRIBUTING.md +20 -8
  5. {execsql2-2.21.1 → execsql2-2.22.0}/PKG-INFO +3 -3
  6. {execsql2-2.21.1 → execsql2-2.22.0}/README.md +2 -2
  7. {execsql2-2.21.1 → execsql2-2.22.0}/docs/about/divergence.md +44 -40
  8. {execsql2-2.21.1 → execsql2-2.22.0}/docs/dev/architecture.md +1 -1
  9. {execsql2-2.21.1 → execsql2-2.22.0}/docs/getting-started/installation.md +2 -1
  10. {execsql2-2.21.1 → execsql2-2.22.0}/docs/guides/formatter.md +1 -1
  11. {execsql2-2.21.1 → execsql2-2.22.0}/docs/guides/sql_syntax.md +1 -1
  12. {execsql2-2.21.1 → execsql2-2.22.0}/docs/reference/configuration.md +3 -0
  13. {execsql2-2.21.1 → execsql2-2.22.0}/docs/reference/metacommands.md +9 -5
  14. {execsql2-2.21.1 → execsql2-2.22.0}/docs/reference/security.md +14 -2
  15. {execsql2-2.21.1 → execsql2-2.22.0}/justfile +2 -2
  16. {execsql2-2.21.1 → execsql2-2.22.0}/pyproject.toml +11 -2
  17. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/api.py +21 -4
  18. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/cli/run.py +37 -17
  19. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/config.py +37 -22
  20. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/data/execsql.conf.template +6 -0
  21. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/db/access.py +6 -5
  22. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/db/base.py +23 -5
  23. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/db/dsn.py +2 -0
  24. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/db/duckdb.py +1 -1
  25. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/db/mysql.py +6 -8
  26. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/db/sqlite.py +1 -1
  27. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/exceptions.py +14 -11
  28. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/exporters/base.py +1 -1
  29. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/exporters/delimited.py +22 -16
  30. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/exporters/duckdb.py +6 -1
  31. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/exporters/feather.py +2 -2
  32. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/exporters/json.py +5 -3
  33. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/exporters/latex.py +2 -2
  34. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/exporters/markdown.py +1 -0
  35. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/exporters/ods.py +7 -3
  36. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/exporters/parquet.py +1 -1
  37. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/exporters/raw.py +8 -8
  38. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/exporters/sqlite.py +11 -1
  39. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/exporters/templates.py +2 -1
  40. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/exporters/xls.py +10 -10
  41. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/exporters/xml.py +1 -0
  42. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/exporters/yaml.py +1 -0
  43. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/exporters/zip.py +9 -7
  44. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/format.py +6 -6
  45. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/gui/console.py +3 -3
  46. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/gui/desktop.py +28 -10
  47. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/gui/tui.py +8 -6
  48. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/importers/base.py +14 -8
  49. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/importers/csv.py +8 -3
  50. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/importers/xls.py +1 -0
  51. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/metacommands/conditions.py +14 -14
  52. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/metacommands/connect.py +2 -2
  53. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/metacommands/control.py +1 -1
  54. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/metacommands/data.py +2 -1
  55. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/metacommands/io_export.py +4 -0
  56. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/metacommands/io_fileops.py +85 -39
  57. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/metacommands/io_import.py +1 -0
  58. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/metacommands/io_write.py +1 -1
  59. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/metacommands/prompt.py +24 -22
  60. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/metacommands/system.py +14 -1
  61. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/models.py +56 -17
  62. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/parser.py +8 -8
  63. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/plugins.py +1 -1
  64. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/script/engine.py +4 -4
  65. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/script/executor.py +67 -28
  66. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/script/parser.py +13 -12
  67. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/script/variables.py +4 -2
  68. execsql2-2.22.0/src/execsql/state.pyi +122 -0
  69. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/types.py +31 -26
  70. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/utils/auth.py +6 -2
  71. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/utils/datetime.py +3 -3
  72. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/utils/errors.py +7 -5
  73. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/utils/fileio.py +77 -31
  74. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/utils/gui.py +15 -11
  75. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/utils/mail.py +9 -2
  76. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/utils/numeric.py +1 -1
  77. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/utils/regex.py +9 -9
  78. {execsql2-2.21.1 → execsql2-2.22.0}/tests/cli/test_cli_run.py +45 -1
  79. {execsql2-2.21.1 → execsql2-2.22.0}/tests/db/test_mysql_case_folding.py +18 -1
  80. {execsql2-2.21.1 → execsql2-2.22.0}/tests/db/test_postgres.py +46 -0
  81. {execsql2-2.21.1 → execsql2-2.22.0}/tests/exporters/test_duckdb_exporter.py +29 -0
  82. {execsql2-2.21.1 → execsql2-2.22.0}/tests/exporters/test_feather.py +1 -1
  83. {execsql2-2.21.1 → execsql2-2.22.0}/tests/exporters/test_latex_extended.py +10 -7
  84. {execsql2-2.21.1 → execsql2-2.22.0}/tests/exporters/test_parquet.py +8 -0
  85. {execsql2-2.21.1 → execsql2-2.22.0}/tests/exporters/test_sqlite_exporter.py +38 -0
  86. {execsql2-2.21.1 → execsql2-2.22.0}/tests/importers/test_base_extended.py +19 -0
  87. {execsql2-2.21.1 → execsql2-2.22.0}/tests/importers/test_csv_importer.py +8 -25
  88. {execsql2-2.21.1 → execsql2-2.22.0}/tests/integration/test_postgres.py +51 -0
  89. {execsql2-2.21.1 → execsql2-2.22.0}/tests/metacommands/test_metacommands_fileops_extra.py +102 -0
  90. {execsql2-2.21.1 → execsql2-2.22.0}/tests/metacommands/test_metacommands_system.py +66 -0
  91. {execsql2-2.21.1 → execsql2-2.22.0}/tests/security/test_log_redaction.py +45 -0
  92. {execsql2-2.21.1 → execsql2-2.22.0}/tests/test_api.py +33 -0
  93. {execsql2-2.21.1 → execsql2-2.22.0}/tests/test_config_data.py +26 -0
  94. {execsql2-2.21.1 → execsql2-2.22.0}/tests/test_models.py +106 -4
  95. {execsql2-2.21.1 → execsql2-2.22.0}/tests/test_types.py +2 -3
  96. {execsql2-2.21.1 → execsql2-2.22.0}/uv.lock +1 -1
  97. {execsql2-2.21.1 → execsql2-2.22.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  98. {execsql2-2.21.1 → execsql2-2.22.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  99. {execsql2-2.21.1 → execsql2-2.22.0}/.github/dependabot.yml +0 -0
  100. {execsql2-2.21.1 → execsql2-2.22.0}/.gitignore +0 -0
  101. {execsql2-2.21.1 → execsql2-2.22.0}/.pre-commit-hooks.yaml +0 -0
  102. {execsql2-2.21.1 → execsql2-2.22.0}/.python-version +0 -0
  103. {execsql2-2.21.1 → execsql2-2.22.0}/.readthedocs.yaml +0 -0
  104. {execsql2-2.21.1 → execsql2-2.22.0}/LICENSE.txt +0 -0
  105. {execsql2-2.21.1 → execsql2-2.22.0}/NOTICE +0 -0
  106. {execsql2-2.21.1 → execsql2-2.22.0}/SECURITY.md +0 -0
  107. {execsql2-2.21.1 → execsql2-2.22.0}/docs/about/contributors.md +0 -0
  108. {execsql2-2.21.1 → execsql2-2.22.0}/docs/about/copyright.md +0 -0
  109. {execsql2-2.21.1 → execsql2-2.22.0}/docs/api/cli.md +0 -0
  110. {execsql2-2.21.1 → execsql2-2.22.0}/docs/api/db.md +0 -0
  111. {execsql2-2.21.1 → execsql2-2.22.0}/docs/api/exporters.md +0 -0
  112. {execsql2-2.21.1 → execsql2-2.22.0}/docs/api/importers.md +0 -0
  113. {execsql2-2.21.1 → execsql2-2.22.0}/docs/api/index.md +0 -0
  114. {execsql2-2.21.1 → execsql2-2.22.0}/docs/api/metacommands.md +0 -0
  115. {execsql2-2.21.1 → execsql2-2.22.0}/docs/dev/adding_db_adapters.md +0 -0
  116. {execsql2-2.21.1 → execsql2-2.22.0}/docs/dev/adding_exporters.md +0 -0
  117. {execsql2-2.21.1 → execsql2-2.22.0}/docs/dev/adding_importers.md +0 -0
  118. {execsql2-2.21.1 → execsql2-2.22.0}/docs/dev/adding_metacommands.md +0 -0
  119. {execsql2-2.21.1 → execsql2-2.22.0}/docs/dev/releasing.md +0 -0
  120. {execsql2-2.21.1 → execsql2-2.22.0}/docs/getting-started/requirements.md +0 -0
  121. {execsql2-2.21.1 → execsql2-2.22.0}/docs/getting-started/syntax.md +0 -0
  122. {execsql2-2.21.1 → execsql2-2.22.0}/docs/guides/debugging.md +0 -0
  123. {execsql2-2.21.1 → execsql2-2.22.0}/docs/guides/documentation.md +0 -0
  124. {execsql2-2.21.1 → execsql2-2.22.0}/docs/guides/encoding.md +0 -0
  125. {execsql2-2.21.1 → execsql2-2.22.0}/docs/guides/examples.md +0 -0
  126. {execsql2-2.21.1 → execsql2-2.22.0}/docs/guides/logging.md +0 -0
  127. {execsql2-2.21.1 → execsql2-2.22.0}/docs/guides/usage.md +0 -0
  128. {execsql2-2.21.1 → execsql2-2.22.0}/docs/guides/using_scripts.md +0 -0
  129. {execsql2-2.21.1 → execsql2-2.22.0}/docs/images/Compare_planets.png +0 -0
  130. {execsql2-2.21.1 → execsql2-2.22.0}/docs/images/actions.png +0 -0
  131. {execsql2-2.21.1 → execsql2-2.22.0}/docs/images/actions2.png +0 -0
  132. {execsql2-2.21.1 → execsql2-2.22.0}/docs/images/checkboxes.png +0 -0
  133. {execsql2-2.21.1 → execsql2-2.22.0}/docs/images/connect.b64 +0 -0
  134. {execsql2-2.21.1 → execsql2-2.22.0}/docs/images/connect.png +0 -0
  135. {execsql2-2.21.1 → execsql2-2.22.0}/docs/images/create_conf.png +0 -0
  136. {execsql2-2.21.1 → execsql2-2.22.0}/docs/images/data_error1_screenshot.jpg +0 -0
  137. {execsql2-2.21.1 → execsql2-2.22.0}/docs/images/entry_form.png +0 -0
  138. {execsql2-2.21.1 → execsql2-2.22.0}/docs/images/execsql_console.png +0 -0
  139. {execsql2-2.21.1 → execsql2-2.22.0}/docs/images/execsql_logo_01.png +0 -0
  140. {execsql2-2.21.1 → execsql2-2.22.0}/docs/images/fatals.png +0 -0
  141. {execsql2-2.21.1 → execsql2-2.22.0}/docs/images/logo_small.png +0 -0
  142. {execsql2-2.21.1 → execsql2-2.22.0}/docs/images/pause_terminal.png +0 -0
  143. {execsql2-2.21.1 → execsql2-2.22.0}/docs/images/pause_terminal_sm.b64 +0 -0
  144. {execsql2-2.21.1 → execsql2-2.22.0}/docs/images/pause_terminal_sm.png +0 -0
  145. {execsql2-2.21.1 → execsql2-2.22.0}/docs/images/prompt_compare.png +0 -0
  146. {execsql2-2.21.1 → execsql2-2.22.0}/docs/images/set_build_commands.jpg +0 -0
  147. {execsql2-2.21.1 → execsql2-2.22.0}/docs/images/unit_conversions.b64 +0 -0
  148. {execsql2-2.21.1 → execsql2-2.22.0}/docs/images/unit_conversions_029.png +0 -0
  149. {execsql2-2.21.1 → execsql2-2.22.0}/docs/images/unmatched.png +0 -0
  150. {execsql2-2.21.1 → execsql2-2.22.0}/docs/images/vim_execsql_highlight.png +0 -0
  151. {execsql2-2.21.1 → execsql2-2.22.0}/docs/index.md +0 -0
  152. {execsql2-2.21.1 → execsql2-2.22.0}/docs/reference/substitution_vars.md +0 -0
  153. {execsql2-2.21.1 → execsql2-2.22.0}/extras/plugin-template/README.md +0 -0
  154. {execsql2-2.21.1 → execsql2-2.22.0}/extras/plugin-template/pyproject.toml +0 -0
  155. {execsql2-2.21.1 → execsql2-2.22.0}/extras/plugin-template/src/execsql_plugin_YOURNAME/__init__.py +0 -0
  156. {execsql2-2.21.1 → execsql2-2.22.0}/extras/plugin-template/tests/test_plugin.py.example +0 -0
  157. {execsql2-2.21.1 → execsql2-2.22.0}/extras/vscode-execsql/README.md +0 -0
  158. {execsql2-2.21.1 → execsql2-2.22.0}/extras/vscode-execsql/package.json +0 -0
  159. {execsql2-2.21.1 → execsql2-2.22.0}/extras/vscode-execsql/syntaxes/execsql.tmLanguage.json +0 -0
  160. {execsql2-2.21.1 → execsql2-2.22.0}/scripts/generate_vscode_grammar.py +0 -0
  161. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/__init__.py +0 -0
  162. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/__main__.py +0 -0
  163. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/cli/__init__.py +0 -0
  164. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/cli/dsn.py +0 -0
  165. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/cli/help.py +0 -0
  166. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/cli/lint.py +0 -0
  167. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/data/__init__.py +0 -0
  168. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/db/__init__.py +0 -0
  169. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/db/factory.py +0 -0
  170. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/db/firebird.py +0 -0
  171. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/db/oracle.py +0 -0
  172. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/db/postgres.py +0 -0
  173. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/db/sqlserver.py +0 -0
  174. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/debug/__init__.py +0 -0
  175. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/debug/repl.py +0 -0
  176. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/exporters/__init__.py +0 -0
  177. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/exporters/html.py +0 -0
  178. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/exporters/pretty.py +0 -0
  179. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/exporters/protocol.py +0 -0
  180. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/exporters/values.py +0 -0
  181. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/exporters/xlsx.py +0 -0
  182. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/gui/__init__.py +0 -0
  183. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/gui/base.py +0 -0
  184. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/importers/__init__.py +0 -0
  185. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/importers/feather.py +0 -0
  186. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/importers/json.py +0 -0
  187. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/importers/ods.py +0 -0
  188. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/metacommands/__init__.py +0 -0
  189. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/metacommands/debug.py +0 -0
  190. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/metacommands/dispatch.py +0 -0
  191. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/metacommands/io.py +0 -0
  192. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/metacommands/script_ext.py +0 -0
  193. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/metacommands/upsert.py +0 -0
  194. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/py.typed +0 -0
  195. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/script/__init__.py +0 -0
  196. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/script/ast.py +0 -0
  197. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/script/control.py +0 -0
  198. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/state.py +0 -0
  199. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/utils/__init__.py +0 -0
  200. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/utils/crypto.py +0 -0
  201. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/utils/strings.py +0 -0
  202. {execsql2-2.21.1 → execsql2-2.22.0}/src/execsql/utils/timer.py +0 -0
  203. {execsql2-2.21.1 → execsql2-2.22.0}/templates/README.md +0 -0
  204. {execsql2-2.21.1 → execsql2-2.22.0}/templates/config_settings.sqlite +0 -0
  205. {execsql2-2.21.1 → execsql2-2.22.0}/templates/example_config_prompt.sql +0 -0
  206. {execsql2-2.21.1 → execsql2-2.22.0}/templates/make_config_db.sql +0 -0
  207. {execsql2-2.21.1 → execsql2-2.22.0}/templates/md_compare.sql +0 -0
  208. {execsql2-2.21.1 → execsql2-2.22.0}/templates/md_glossary.sql +0 -0
  209. {execsql2-2.21.1 → execsql2-2.22.0}/templates/md_upsert.sql +0 -0
  210. {execsql2-2.21.1 → execsql2-2.22.0}/templates/pg_compare.sql +0 -0
  211. {execsql2-2.21.1 → execsql2-2.22.0}/templates/pg_glossary.sql +0 -0
  212. {execsql2-2.21.1 → execsql2-2.22.0}/templates/pg_upsert.sql +0 -0
  213. {execsql2-2.21.1 → execsql2-2.22.0}/templates/script_template.sql +0 -0
  214. {execsql2-2.21.1 → execsql2-2.22.0}/templates/ss_compare.sql +0 -0
  215. {execsql2-2.21.1 → execsql2-2.22.0}/templates/ss_glossary.sql +0 -0
  216. {execsql2-2.21.1 → execsql2-2.22.0}/templates/ss_upsert.sql +0 -0
  217. {execsql2-2.21.1 → execsql2-2.22.0}/tests/__init__.py +0 -0
  218. {execsql2-2.21.1 → execsql2-2.22.0}/tests/cli/__init__.py +0 -0
  219. {execsql2-2.21.1 → execsql2-2.22.0}/tests/cli/test_cli.py +0 -0
  220. {execsql2-2.21.1 → execsql2-2.22.0}/tests/cli/test_cli_e2e.py +0 -0
  221. {execsql2-2.21.1 → execsql2-2.22.0}/tests/cli/test_lint.py +0 -0
  222. {execsql2-2.21.1 → execsql2-2.22.0}/tests/cli/test_ping.py +0 -0
  223. {execsql2-2.21.1 → execsql2-2.22.0}/tests/cli/test_profile.py +0 -0
  224. {execsql2-2.21.1 → execsql2-2.22.0}/tests/conftest.py +0 -0
  225. {execsql2-2.21.1 → execsql2-2.22.0}/tests/db/__init__.py +0 -0
  226. {execsql2-2.21.1 → execsql2-2.22.0}/tests/db/test_access_windows.py +0 -0
  227. {execsql2-2.21.1 → execsql2-2.22.0}/tests/db/test_base.py +0 -0
  228. {execsql2-2.21.1 → execsql2-2.22.0}/tests/db/test_db_adapters_mocked.py +0 -0
  229. {execsql2-2.21.1 → execsql2-2.22.0}/tests/db/test_db_adapters_mocked_extra.py +0 -0
  230. {execsql2-2.21.1 → execsql2-2.22.0}/tests/db/test_dsn.py +0 -0
  231. {execsql2-2.21.1 → execsql2-2.22.0}/tests/db/test_duckdb.py +0 -0
  232. {execsql2-2.21.1 → execsql2-2.22.0}/tests/db/test_factory.py +0 -0
  233. {execsql2-2.21.1 → execsql2-2.22.0}/tests/db/test_mysql_inprocess.py +0 -0
  234. {execsql2-2.21.1 → execsql2-2.22.0}/tests/db/test_postgres_inprocess.py +0 -0
  235. {execsql2-2.21.1 → execsql2-2.22.0}/tests/db/test_sqlite.py +0 -0
  236. {execsql2-2.21.1 → execsql2-2.22.0}/tests/db/test_sqlite_extra.py +0 -0
  237. {execsql2-2.21.1 → execsql2-2.22.0}/tests/db/test_sqlserver_inprocess.py +0 -0
  238. {execsql2-2.21.1 → execsql2-2.22.0}/tests/exporters/__init__.py +0 -0
  239. {execsql2-2.21.1 → execsql2-2.22.0}/tests/exporters/test_base.py +0 -0
  240. {execsql2-2.21.1 → execsql2-2.22.0}/tests/exporters/test_db.py +0 -0
  241. {execsql2-2.21.1 → execsql2-2.22.0}/tests/exporters/test_delimited.py +0 -0
  242. {execsql2-2.21.1 → execsql2-2.22.0}/tests/exporters/test_exporters.py +0 -0
  243. {execsql2-2.21.1 → execsql2-2.22.0}/tests/exporters/test_html_extended.py +0 -0
  244. {execsql2-2.21.1 → execsql2-2.22.0}/tests/exporters/test_html_latex.py +0 -0
  245. {execsql2-2.21.1 → execsql2-2.22.0}/tests/exporters/test_json.py +0 -0
  246. {execsql2-2.21.1 → execsql2-2.22.0}/tests/exporters/test_json_extended.py +0 -0
  247. {execsql2-2.21.1 → execsql2-2.22.0}/tests/exporters/test_markdown.py +0 -0
  248. {execsql2-2.21.1 → execsql2-2.22.0}/tests/exporters/test_ods.py +0 -0
  249. {execsql2-2.21.1 → execsql2-2.22.0}/tests/exporters/test_ods_export.py +0 -0
  250. {execsql2-2.21.1 → execsql2-2.22.0}/tests/exporters/test_pretty_extended.py +0 -0
  251. {execsql2-2.21.1 → execsql2-2.22.0}/tests/exporters/test_raw_extended.py +0 -0
  252. {execsql2-2.21.1 → execsql2-2.22.0}/tests/exporters/test_templates.py +0 -0
  253. {execsql2-2.21.1 → execsql2-2.22.0}/tests/exporters/test_templates_extended.py +0 -0
  254. {execsql2-2.21.1 → execsql2-2.22.0}/tests/exporters/test_values_extended.py +0 -0
  255. {execsql2-2.21.1 → execsql2-2.22.0}/tests/exporters/test_xls_xlsx.py +0 -0
  256. {execsql2-2.21.1 → execsql2-2.22.0}/tests/exporters/test_xlsx.py +0 -0
  257. {execsql2-2.21.1 → execsql2-2.22.0}/tests/exporters/test_xml.py +0 -0
  258. {execsql2-2.21.1 → execsql2-2.22.0}/tests/exporters/test_yaml.py +0 -0
  259. {execsql2-2.21.1 → execsql2-2.22.0}/tests/exporters/test_zip.py +0 -0
  260. {execsql2-2.21.1 → execsql2-2.22.0}/tests/gui/__init__.py +0 -0
  261. {execsql2-2.21.1 → execsql2-2.22.0}/tests/gui/test_backends.py +0 -0
  262. {execsql2-2.21.1 → execsql2-2.22.0}/tests/gui/test_backends_extended.py +0 -0
  263. {execsql2-2.21.1 → execsql2-2.22.0}/tests/gui/test_compare_stats.py +0 -0
  264. {execsql2-2.21.1 → execsql2-2.22.0}/tests/gui/test_compute_row_diffs.py +0 -0
  265. {execsql2-2.21.1 → execsql2-2.22.0}/tests/gui/test_desktop_dialogs.py +0 -0
  266. {execsql2-2.21.1 → execsql2-2.22.0}/tests/gui/test_tui_pilot.py +0 -0
  267. {execsql2-2.21.1 → execsql2-2.22.0}/tests/gui/test_tui_pilot_complex.py +0 -0
  268. {execsql2-2.21.1 → execsql2-2.22.0}/tests/gui/test_utils_gui_extended.py +0 -0
  269. {execsql2-2.21.1 → execsql2-2.22.0}/tests/importers/__init__.py +0 -0
  270. {execsql2-2.21.1 → execsql2-2.22.0}/tests/importers/test_csv_edge_cases.py +0 -0
  271. {execsql2-2.21.1 → execsql2-2.22.0}/tests/importers/test_feather_importer.py +0 -0
  272. {execsql2-2.21.1 → execsql2-2.22.0}/tests/importers/test_json_importer.py +0 -0
  273. {execsql2-2.21.1 → execsql2-2.22.0}/tests/importers/test_ods_importer.py +0 -0
  274. {execsql2-2.21.1 → execsql2-2.22.0}/tests/importers/test_xls_importer.py +0 -0
  275. {execsql2-2.21.1 → execsql2-2.22.0}/tests/integration/__init__.py +0 -0
  276. {execsql2-2.21.1 → execsql2-2.22.0}/tests/integration/conftest.py +0 -0
  277. {execsql2-2.21.1 → execsql2-2.22.0}/tests/integration/test_dsn.py +0 -0
  278. {execsql2-2.21.1 → execsql2-2.22.0}/tests/integration/test_duckdb.py +0 -0
  279. {execsql2-2.21.1 → execsql2-2.22.0}/tests/integration/test_mysql.py +0 -0
  280. {execsql2-2.21.1 → execsql2-2.22.0}/tests/integration/test_sqlite.py +0 -0
  281. {execsql2-2.21.1 → execsql2-2.22.0}/tests/metacommands/__init__.py +0 -0
  282. {execsql2-2.21.1 → execsql2-2.22.0}/tests/metacommands/test_assert.py +0 -0
  283. {execsql2-2.21.1 → execsql2-2.22.0}/tests/metacommands/test_breakpoint.py +0 -0
  284. {execsql2-2.21.1 → execsql2-2.22.0}/tests/metacommands/test_conditions_extra.py +0 -0
  285. {execsql2-2.21.1 → execsql2-2.22.0}/tests/metacommands/test_connect.py +0 -0
  286. {execsql2-2.21.1 → execsql2-2.22.0}/tests/metacommands/test_io_export.py +0 -0
  287. {execsql2-2.21.1 → execsql2-2.22.0}/tests/metacommands/test_io_import.py +0 -0
  288. {execsql2-2.21.1 → execsql2-2.22.0}/tests/metacommands/test_metacommands.py +0 -0
  289. {execsql2-2.21.1 → execsql2-2.22.0}/tests/metacommands/test_metacommands_connect.py +0 -0
  290. {execsql2-2.21.1 → execsql2-2.22.0}/tests/metacommands/test_metacommands_data.py +0 -0
  291. {execsql2-2.21.1 → execsql2-2.22.0}/tests/metacommands/test_metacommands_extended.py +0 -0
  292. {execsql2-2.21.1 → execsql2-2.22.0}/tests/metacommands/test_metacommands_io.py +0 -0
  293. {execsql2-2.21.1 → execsql2-2.22.0}/tests/metacommands/test_metacommands_io_write_extra.py +0 -0
  294. {execsql2-2.21.1 → execsql2-2.22.0}/tests/metacommands/test_metacommands_script_ext.py +0 -0
  295. {execsql2-2.21.1 → execsql2-2.22.0}/tests/metacommands/test_metacommands_system_extra.py +0 -0
  296. {execsql2-2.21.1 → execsql2-2.22.0}/tests/metacommands/test_pg_upsert.py +0 -0
  297. {execsql2-2.21.1 → execsql2-2.22.0}/tests/metacommands/test_prompt.py +0 -0
  298. {execsql2-2.21.1 → execsql2-2.22.0}/tests/metacommands/test_row_count.py +0 -0
  299. {execsql2-2.21.1 → execsql2-2.22.0}/tests/metacommands/test_show_scripts.py +0 -0
  300. {execsql2-2.21.1 → execsql2-2.22.0}/tests/scripts/__init__.py +0 -0
  301. {execsql2-2.21.1 → execsql2-2.22.0}/tests/scripts/fixtures/audit_lint_bad.sql +0 -0
  302. {execsql2-2.21.1 → execsql2-2.22.0}/tests/scripts/fixtures/audit_smoke/sample.json +0 -0
  303. {execsql2-2.21.1 → execsql2-2.22.0}/tests/scripts/fixtures/audit_smoke/sample.jsonl +0 -0
  304. {execsql2-2.21.1 → execsql2-2.22.0}/tests/scripts/fixtures/audit_smoke.sql +0 -0
  305. {execsql2-2.21.1 → execsql2-2.22.0}/tests/scripts/fixtures/config_runtime.sql +0 -0
  306. {execsql2-2.21.1 → execsql2-2.22.0}/tests/scripts/fixtures/control_flow.sql +0 -0
  307. {execsql2-2.21.1 → execsql2-2.22.0}/tests/scripts/fixtures/counters_and_locals.sql +0 -0
  308. {execsql2-2.21.1 → execsql2-2.22.0}/tests/scripts/fixtures/error_handling.sql +0 -0
  309. {execsql2-2.21.1 → execsql2-2.22.0}/tests/scripts/fixtures/import_options/has_comments.csv +0 -0
  310. {execsql2-2.21.1 → execsql2-2.22.0}/tests/scripts/fixtures/import_options.sql +0 -0
  311. {execsql2-2.21.1 → execsql2-2.22.0}/tests/scripts/fixtures/includes/helper.sql +0 -0
  312. {execsql2-2.21.1 → execsql2-2.22.0}/tests/scripts/fixtures/includes.sql +0 -0
  313. {execsql2-2.21.1 → execsql2-2.22.0}/tests/scripts/fixtures/io_formats.sql +0 -0
  314. {execsql2-2.21.1 → execsql2-2.22.0}/tests/scripts/fixtures/io_roundtrip.sql +0 -0
  315. {execsql2-2.21.1 → execsql2-2.22.0}/tests/scripts/fixtures/multi_db.sql +0 -0
  316. {execsql2-2.21.1 → execsql2-2.22.0}/tests/scripts/fixtures/parquet_feather_roundtrip.sql +0 -0
  317. {execsql2-2.21.1 → execsql2-2.22.0}/tests/scripts/fixtures/parse_only/parse_tree.sql +0 -0
  318. {execsql2-2.21.1 → execsql2-2.22.0}/tests/scripts/fixtures/predicates.sql +0 -0
  319. {execsql2-2.21.1 → execsql2-2.22.0}/tests/scripts/fixtures/scripts.sql +0 -0
  320. {execsql2-2.21.1 → execsql2-2.22.0}/tests/scripts/fixtures/smoke.sql +0 -0
  321. {execsql2-2.21.1 → execsql2-2.22.0}/tests/scripts/fixtures/subroutine_loops.sql +0 -0
  322. {execsql2-2.21.1 → execsql2-2.22.0}/tests/scripts/fixtures/subvars_advanced.sql +0 -0
  323. {execsql2-2.21.1 → execsql2-2.22.0}/tests/scripts/fixtures/template_export/greeting.tmpl +0 -0
  324. {execsql2-2.21.1 → execsql2-2.22.0}/tests/scripts/fixtures/template_export.sql +0 -0
  325. {execsql2-2.21.1 → execsql2-2.22.0}/tests/scripts/fixtures/timer.sql +0 -0
  326. {execsql2-2.21.1 → execsql2-2.22.0}/tests/scripts/fixtures/transactions.sql +0 -0
  327. {execsql2-2.21.1 → execsql2-2.22.0}/tests/scripts/fixtures/write_create_table.sql +0 -0
  328. {execsql2-2.21.1 → execsql2-2.22.0}/tests/scripts/fixtures/xlsx_ods_roundtrip.sql +0 -0
  329. {execsql2-2.21.1 → execsql2-2.22.0}/tests/scripts/test_sql_scripts.py +0 -0
  330. {execsql2-2.21.1 → execsql2-2.22.0}/tests/security/__init__.py +0 -0
  331. {execsql2-2.21.1 → execsql2-2.22.0}/tests/security/test_env_detection.py +0 -0
  332. {execsql2-2.21.1 → execsql2-2.22.0}/tests/security/test_expansion_bomb.py +0 -0
  333. {execsql2-2.21.1 → execsql2-2.22.0}/tests/security/test_import_regex_hardening.py +0 -0
  334. {execsql2-2.21.1 → execsql2-2.22.0}/tests/security/test_odbc_injection.py +0 -0
  335. {execsql2-2.21.1 → execsql2-2.22.0}/tests/security/test_path_containment.py +0 -0
  336. {execsql2-2.21.1 → execsql2-2.22.0}/tests/security/test_substitution_injection.py +0 -0
  337. {execsql2-2.21.1 → execsql2-2.22.0}/tests/security/test_zip_bomb.py +0 -0
  338. {execsql2-2.21.1 → execsql2-2.22.0}/tests/test_api_extended.py +0 -0
  339. {execsql2-2.21.1 → execsql2-2.22.0}/tests/test_ast.py +0 -0
  340. {execsql2-2.21.1 → execsql2-2.22.0}/tests/test_ast_parser.py +0 -0
  341. {execsql2-2.21.1 → execsql2-2.22.0}/tests/test_bundled_templates.py +0 -0
  342. {execsql2-2.21.1 → execsql2-2.22.0}/tests/test_config.py +0 -0
  343. {execsql2-2.21.1 → execsql2-2.22.0}/tests/test_config_extended.py +0 -0
  344. {execsql2-2.21.1 → execsql2-2.22.0}/tests/test_debug_repl.py +0 -0
  345. {execsql2-2.21.1 → execsql2-2.22.0}/tests/test_engine.py +0 -0
  346. {execsql2-2.21.1 → execsql2-2.22.0}/tests/test_error_messages.py +0 -0
  347. {execsql2-2.21.1 → execsql2-2.22.0}/tests/test_exceptions.py +0 -0
  348. {execsql2-2.21.1 → execsql2-2.22.0}/tests/test_executor.py +0 -0
  349. {execsql2-2.21.1 → execsql2-2.22.0}/tests/test_executor_inprocess.py +0 -0
  350. {execsql2-2.21.1 → execsql2-2.22.0}/tests/test_format.py +0 -0
  351. {execsql2-2.21.1 → execsql2-2.22.0}/tests/test_mail.py +0 -0
  352. {execsql2-2.21.1 → execsql2-2.22.0}/tests/test_package.py +0 -0
  353. {execsql2-2.21.1 → execsql2-2.22.0}/tests/test_parser.py +0 -0
  354. {execsql2-2.21.1 → execsql2-2.22.0}/tests/test_parser_params.py +0 -0
  355. {execsql2-2.21.1 → execsql2-2.22.0}/tests/test_plugins.py +0 -0
  356. {execsql2-2.21.1 → execsql2-2.22.0}/tests/test_registry.py +0 -0
  357. {execsql2-2.21.1 → execsql2-2.22.0}/tests/test_script.py +0 -0
  358. {execsql2-2.21.1 → execsql2-2.22.0}/tests/test_state.py +0 -0
  359. {execsql2-2.21.1 → execsql2-2.22.0}/tests/utils/__init__.py +0 -0
  360. {execsql2-2.21.1 → execsql2-2.22.0}/tests/utils/test_auth.py +0 -0
  361. {execsql2-2.21.1 → execsql2-2.22.0}/tests/utils/test_auth_extra.py +0 -0
  362. {execsql2-2.21.1 → execsql2-2.22.0}/tests/utils/test_crypto.py +0 -0
  363. {execsql2-2.21.1 → execsql2-2.22.0}/tests/utils/test_datetime.py +0 -0
  364. {execsql2-2.21.1 → execsql2-2.22.0}/tests/utils/test_encodedfile_context.py +0 -0
  365. {execsql2-2.21.1 → execsql2-2.22.0}/tests/utils/test_errors.py +0 -0
  366. {execsql2-2.21.1 → execsql2-2.22.0}/tests/utils/test_errors_extra.py +0 -0
  367. {execsql2-2.21.1 → execsql2-2.22.0}/tests/utils/test_fileio.py +0 -0
  368. {execsql2-2.21.1 → execsql2-2.22.0}/tests/utils/test_fileio_extra.py +0 -0
  369. {execsql2-2.21.1 → execsql2-2.22.0}/tests/utils/test_numeric.py +0 -0
  370. {execsql2-2.21.1 → execsql2-2.22.0}/tests/utils/test_regex.py +0 -0
  371. {execsql2-2.21.1 → execsql2-2.22.0}/tests/utils/test_strings.py +0 -0
  372. {execsql2-2.21.1 → execsql2-2.22.0}/tests/utils/test_timer.py +0 -0
  373. {execsql2-2.21.1 → execsql2-2.22.0}/tests/utils/test_timer_extra.py +0 -0
  374. {execsql2-2.21.1 → execsql2-2.22.0}/zensical.toml +0 -0
@@ -20,14 +20,63 @@ jobs:
20
20
  permissions:
21
21
  contents: read
22
22
  steps:
23
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
24
- - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
23
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
24
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
25
25
  with:
26
26
  python-version: "3.13"
27
27
  - run: python -m pip install ruff
28
28
  - run: ruff check src/ tests/
29
29
  - run: ruff format --check src/ tests/
30
30
 
31
+ typecheck:
32
+ name: typecheck
33
+ runs-on: ubuntu-latest
34
+ permissions:
35
+ contents: read
36
+ steps:
37
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
38
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
39
+ with:
40
+ python-version: "3.13"
41
+ cache: "pip"
42
+ - run: python -m pip install --upgrade pip
43
+ - run: python -m pip install ".[dev]"
44
+ - run: mypy src/execsql/
45
+
46
+ pre-commit:
47
+ name: pre-commit
48
+ runs-on: ubuntu-latest
49
+ permissions:
50
+ contents: read
51
+ steps:
52
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
53
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
54
+ with:
55
+ python-version: "3.13"
56
+ cache: "pip"
57
+ - run: python -m pip install --upgrade pip
58
+ - run: python -m pip install ".[dev]"
59
+ # mypy runs in the dedicated typecheck job; the local hook needs uv
60
+ - run: pre-commit run --all-files
61
+ env:
62
+ SKIP: mypy
63
+
64
+ docs:
65
+ name: docs
66
+ runs-on: ubuntu-latest
67
+ permissions:
68
+ contents: read
69
+ steps:
70
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
71
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
72
+ with:
73
+ python-version: "3.13"
74
+ cache: "pip"
75
+ - run: python -m pip install --upgrade pip
76
+ - run: python -m pip install ".[dev]"
77
+ - run: cp CHANGELOG.md docs/about/change_log.md
78
+ - run: zensical build
79
+
31
80
  tests:
32
81
  strategy:
33
82
  fail-fast: false
@@ -48,13 +97,13 @@ jobs:
48
97
  contents: read
49
98
  steps:
50
99
  - name: Check out repository code
51
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
100
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
52
101
  - name: Setup Python ${{ matrix.python-version }}
53
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
102
+ uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
54
103
  with:
55
104
  python-version: ${{ matrix.python-version }}
56
105
  - name: Cache pip dependencies
57
- uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
106
+ uses: actions/cache@2c8a9bd7457de244a408f35966fab2fb45fda9c8 # v6.0.0
58
107
  with:
59
108
  path: ~/.cache/pip
60
109
  key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{
@@ -139,9 +188,9 @@ jobs:
139
188
  --health-interval 10s --health-timeout 5s --health-retries 20
140
189
  steps:
141
190
  - name: Check out repository code
142
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
191
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
143
192
  - name: Setup Python ${{ matrix.python-version }}
144
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
193
+ uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
145
194
  with:
146
195
  python-version: ${{ matrix.python-version }}
147
196
  - name: Install tkinter (Ubuntu)
@@ -187,8 +236,8 @@ jobs:
187
236
  permissions:
188
237
  contents: read
189
238
  steps:
190
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
191
- - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
239
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
240
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
192
241
  with:
193
242
  python-version: ${{ matrix.python-version }}
194
243
  - name: Install Microsoft Access Database Engine 2016 (x64)
@@ -233,14 +282,14 @@ jobs:
233
282
  build:
234
283
  name: Build distribution 📦
235
284
  runs-on: ubuntu-latest
236
- needs: [ lint, tests, integration-tests ]
285
+ needs: [ lint, typecheck, pre-commit, docs, tests, integration-tests ]
237
286
  if: startsWith(github.ref, 'refs/tags/v')
238
287
  permissions:
239
288
  contents: read
240
289
  steps:
241
- - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
290
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
242
291
  - name: Set up Python
243
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
292
+ uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
244
293
  with:
245
294
  python-version: "3.13"
246
295
  cache: "pip"
@@ -288,14 +337,14 @@ jobs:
288
337
  contents: write
289
338
  steps:
290
339
  - name: Checkout
291
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
340
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
292
341
  - name: Download dist
293
342
  uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
294
343
  with:
295
344
  name: ${{ github.event.repository.name }}
296
345
  path: dist/
297
346
  - name: Create release
298
- uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
347
+ uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3.0.1
299
348
  with:
300
349
  draft: false
301
350
  prerelease: false
@@ -80,3 +80,15 @@ repos:
80
80
  rev: v0.25
81
81
  hooks:
82
82
  - id: validate-pyproject
83
+ # Static type checking — same command as `just typecheck`, run in the
84
+ # project environment so results match local and CI. Whole-program run
85
+ # (pass_filenames: false) because mypy cannot check files in isolation.
86
+ # The CI pre-commit job sets SKIP=mypy; CI has a dedicated typecheck gate.
87
+ - repo: local
88
+ hooks:
89
+ - id: mypy
90
+ name: mypy
91
+ entry: uv run mypy src/execsql/
92
+ language: system
93
+ pass_filenames: false
94
+ files: ^(src/execsql/.*\.pyi?|pyproject\.toml)$
@@ -13,6 +13,29 @@ ______________________________________________________________________
13
13
 
14
14
  ______________________________________________________________________
15
15
 
16
+ ## [2.22.0] - 2026-07-03
17
+
18
+ ### Added
19
+
20
+ - `run()` now accepts `allow_rm_file=False` and `allow_serve=False` to disable the `RM_FILE` and `SERVE` metacommands for library callers, matching the existing `allow_system_cmd` kwarg and the `--no-rm-file` / `--no-serve` CLI flags.
21
+ - `[config] system_cmd_timeout` can bound foreground `SYSTEM_CMD` execution; the default remains no timeout.
22
+
23
+ ### Fixed
24
+
25
+ - `EXPORT … FORMAT SQLITE` and `EXPORT … FORMAT DUCKDB` correctly preserve source column types when exporting from a typed database: text columns stay text (not re-inferred as TIMESTAMP or numeric from value shape), `decimal.Decimal` values bind without error (converted to their exact string representation for SQLite), and unsupported Python objects such as PostGIS geometry are stringified instead of raising a bind error.
26
+ - `EXPORT … FORMAT LATEX` into a ZIP archive no longer crashes with `AttributeError`.
27
+ - Connecting to a server-based database without a server or database name now stops with a clear error message instead of a driver traceback.
28
+
29
+ ______________________________________________________________________
30
+
31
+ ## [2.21.2] - 2026-06-30
32
+
33
+ ### Fixed
34
+
35
+ - Fixed `EXPORT … AS PARQUET` and `EXPORT … AS FEATHER` `ComputeError` when a result column has more than 100 leading `NULL` values followed by non-null numeric values.
36
+
37
+ ______________________________________________________________________
38
+
16
39
  ## [2.21.1] - 2026-06-24
17
40
 
18
41
  ### Fixed
@@ -32,13 +32,19 @@ Linting and formatting are handled by [ruff](https://docs.astral.sh/ruff/):
32
32
  just lint
33
33
  ```
34
34
 
35
- [pre-commit](https://pre-commit.com/) hooks enforce additional checks (gitleaks, uv-lock, mdformat, markdownlint, typos, validate-pyproject) on every commit. To run them manually against all files:
35
+ Static type checking is handled by [mypy](https://mypy.readthedocs.io/) and must pass with zero errors:
36
+
37
+ ```bash
38
+ just typecheck
39
+ ```
40
+
41
+ [pre-commit](https://pre-commit.com/) hooks enforce additional checks (gitleaks, uv-lock, mdformat, markdownlint, typos, validate-pyproject, mypy) on every commit. To run them manually against all files:
36
42
 
37
43
  ```bash
38
44
  just pre-commit
39
45
  ```
40
46
 
41
- CI rejects PRs that fail linting or any pre-commit check.
47
+ CI rejects PRs that fail ruff lint/format, mypy type checking, the full pre-commit suite, docs build, or tests.
42
48
 
43
49
  ## Running Tests
44
50
 
@@ -96,12 +102,18 @@ Pre-releases are published to PyPI but are not installed by default — `pip ins
96
102
 
97
103
  Triggered on pushes to `main`, any `v*.*.*` tag, and pull requests.
98
104
 
99
- | Job | Trigger | What it does |
100
- | ------------------ | ------------------ | ----------------------------------------------- |
101
- | `tests` | all events | Runs the test matrix (3 OS × 5 Python versions) |
102
- | `build` | `v*.*.*` tags only | Builds sdist + wheel, checks with twine |
103
- | `publish` | `v*.*.*` tags only | Publishes to PyPI via OIDC trusted publishing |
104
- | `generate-release` | `v*.*.*` tags only | Creates a GitHub Release with dist artifacts |
105
+ | Job | Trigger | What it does |
106
+ | ---------------------- | ------------------ | ----------------------------------------------------- |
107
+ | `lint` | all events | Runs `ruff check` and `ruff format --check` |
108
+ | `typecheck` | all events | Runs `mypy src/execsql/` (zero-error gate) |
109
+ | `pre-commit` | all events | Runs the full pre-commit suite against all files |
110
+ | `docs` | all events | Builds the documentation site |
111
+ | `tests` | all events | Runs the test matrix (3 OS × 5 Python versions) |
112
+ | `integration-tests` | all events | Runs tests against live database service containers |
113
+ | `access-tests-windows` | all events | Runs MS Access driver tests on Windows (non-blocking) |
114
+ | `build` | `v*.*.*` tags only | Builds sdist + wheel, checks with twine |
115
+ | `publish` | `v*.*.*` tags only | Publishes to PyPI via OIDC trusted publishing |
116
+ | `generate-release` | `v*.*.*` tags only | Creates a GitHub Release with dist artifacts |
105
117
 
106
118
  ### PyPI trusted publishing setup
107
119
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: execsql2
3
- Version: 2.21.1
3
+ Version: 2.22.0
4
4
  Summary: Runs a SQL script against a PostgreSQL, SQLite, MariaDB/MySQL, DuckDB, Firebird, MS-Access, MS-SQL-Server, or Oracle database, or an ODBC DSN. Provides metacommands to import and export data, copy data between databases, conditionally execute SQL and metacommands, and dynamically alter SQL and metacommands with substitution variables.
5
5
  Project-URL: Homepage, https://execsql2.readthedocs.io
6
6
  Project-URL: Repository, https://github.com/geocoug/execsql
@@ -173,7 +173,7 @@ Optional extras install database drivers and feature bundles:
173
173
 
174
174
  ```bash
175
175
  # Database drivers
176
- pip install execsql2[postgres] # PostgreSQL (psycopg2-binary)
176
+ pip install execsql2[postgres] # PostgreSQL (psycopg3 / psycopg[binary])
177
177
  pip install execsql2[mysql] # MySQL / MariaDB (pymysql)
178
178
  pip install execsql2[mssql] # SQL Server (pyodbc)
179
179
  pip install execsql2[duckdb] # DuckDB
@@ -403,7 +403,7 @@ execsql-format --no-sql --in-place scripts/
403
403
  ```yaml
404
404
  repos:
405
405
  - repo: https://github.com/geocoug/execsql
406
- rev: v2.21.1
406
+ rev: v2.22.0
407
407
  hooks:
408
408
  - id: execsql-format
409
409
  ```
@@ -42,7 +42,7 @@ Optional extras install database drivers and feature bundles:
42
42
 
43
43
  ```bash
44
44
  # Database drivers
45
- pip install execsql2[postgres] # PostgreSQL (psycopg2-binary)
45
+ pip install execsql2[postgres] # PostgreSQL (psycopg3 / psycopg[binary])
46
46
  pip install execsql2[mysql] # MySQL / MariaDB (pymysql)
47
47
  pip install execsql2[mssql] # SQL Server (pyodbc)
48
48
  pip install execsql2[duckdb] # DuckDB
@@ -272,7 +272,7 @@ execsql-format --no-sql --in-place scripts/
272
272
  ```yaml
273
273
  repos:
274
274
  - repo: https://github.com/geocoug/execsql
275
- rev: v2.21.1
275
+ rev: v2.22.0
276
276
  hooks:
277
277
  - id: execsql-format
278
278
  ```
@@ -179,7 +179,7 @@ ______________________________________________________________________
179
179
 
180
180
  ### CLI Interface
181
181
 
182
- The CLI framework changed from `optparse` to [Typer](https://typer.tiangolo.com/) with Rich-formatted help text. All original short flags (`-a` through `-z`) are preserved. The tool can be invoked as either `execsql` or `execsql2`.
182
+ The CLI framework changed from `optparse` to [Typer](https://typer.tiangolo.com/) with Rich-formatted help text. All original short flags (`-a` through `-z`) are preserved. The PyPI distribution is named `execsql2`, but the installed executable remains `execsql` for compatibility.
183
183
 
184
184
  Seven upstream long-form flags were renamed underscore → hyphen and the underscore forms are **not** accepted: `--database-encoding`, `--script-encoding`, `--output-encoding`, `--import-encoding`, `--import-buffer`, `--user-logfile`, `--visible-prompts` (upstream wrote these with underscores). Scripts and CI pipelines that invoke the long-form flags must update the spelling; the short letters (`-e`, `-f`, `-g`, `-i`, `-l`, `-v`, `-z`) are unchanged.
185
185
 
@@ -271,45 +271,49 @@ These are behavioral changes driven by security or correctness issues in the ups
271
271
 
272
272
  ### Bug Fixes
273
273
 
274
- | Area | Fix |
275
- | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
276
- | Oracle default port | Corrected from `5432` (PostgreSQL) to `1521`. |
277
- | MySQL `LOAD DATA INFILE` encoding | Python encoding names are now mapped to MySQL charset names. |
278
- | `dt_cast` type converters | Base `Database` class auto-populates 8 type converters that were previously left empty after the refactor. |
279
- | `FileWriter` CPU busy-loop | Uses blocking `queue.get(timeout=0.1)` instead of `get_nowait()` in a tight loop. |
280
- | Substitution variable cycles | 100-iteration limit prevents infinite loops on cyclic variable references. |
281
- | 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. |
282
- | `$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. |
283
- | 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. |
284
- | 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. |
285
- | `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. |
286
- | `win_config_file` broken | Checked `os.name == "windows"` which Python never returns (correct value is `"nt"`). Fixed to `os.name == "nt"`. |
287
- | ELSEIF + ANDIF/ORIF | ANDIF/ORIF after an ELSEIF were silently attached to the parent IF condition instead of the ELSEIF clause. The compound condition was never evaluated for the ELSEIF branch. Fixed in the AST parser and executor. |
288
- | Cursor leak in `select_rowsource()` | Cursor was not closed on query execution failure. Row generators in EXPORT and COPY paths were not explicitly closed on error, relying on garbage collection for cleanup. |
289
- | `NumericParser` right-associative | Arithmetic operators were parsed right-to-left. `10 - 3 - 2` evaluated as `9` instead of `5`. Fixed to left-associative parsing. |
290
- | Empty-column check precedence | `DataTable` and `Database.populate_table()` had an operator precedence bug in the extra-column emptiness check — a redundant `and conf.del_empty_cols` caused incorrect short-circuit evaluation. |
291
- | SQLite import string processing | `SQLiteDatabase.populate_table()` applied `trim_strings`, `replace_newlines`, and `empty_strings` after copying row data, so processing never reached the INSERT. Fixed to process before extraction. |
292
- | `$CURRENT_DATABASE`/`$CURRENT_DBMS` stale after USE | These variables were only set at startup and on CONNECT, not refreshed when `USE` switched the active database. Now set in `set_static_system_vars()` so they update on any connection change. |
293
- | `DT_Text.data_type_name` wrong | Was `"character"` (same as `DT_Character`), making error messages indistinguishable. Corrected to `"text"`. |
294
- | `DT_Varchar` non-string data unchecked | `_from_data()` only enforced the 255-char limit for `str` inputs; non-string values passed through without conversion or length check. |
295
- | `WriteHooks.write_err()` crash on empty string | `strval[-1]` raised `IndexError` on empty input. Fixed to use `str.endswith()`. |
296
- | `NumericParser` division by zero | `NumericAstNode.eval()` raised unhandled `ZeroDivisionError`. Now raises `NumericParserError` with a clear message. |
297
- | `CondAstNode.eval()` could return `None` | Missing fallthrough for unknown node types silently returned `None`. Now raises `CondParserError`. |
298
- | `DT_Timestamp` claims time-only values | `dateutil.parser.parse()` silently fills in today's date for bare time strings like `"13:15:45"`, so `DT_Timestamp` matched before `DT_Time` in the inference order. `parse_datetime()` now rejects time-only strings. |
299
- | `CounterVars.substitute` skipped pos 0–1 | `re.I` was passed as the positional `pos` argument, skipping the first 2 characters of every string during counter variable expansion. Counter variables at the very start of a line were never matched. |
300
- | `exec_cmd` (SQLite/DuckDB) always raised TypeError | Passed bytes to `curs.execute()` via `.encode()`, which Python 3 `sqlite3`/`duckdb` reject. `EXECUTE PROCEDURE` metacommand was non-functional on these backends. |
301
- | MySQL `LOAD DATA INFILE` injection | File path, delimiter, and quotechar were interpolated without escaping. Now single-quotes are escaped consistent with the PostgreSQL COPY path. |
302
- | Config file chain infinite loop | The `config_file` directive could chain config files without limit. A circular reference (via symlinks or different relative paths) caused an infinite loop at startup. Now capped at 20 files. |
303
- | Cursor leaks in database adapters | ~15 methods across all adapters used `curs = self.cursor()` / `curs.close()` without `try/finally`. If the query raised, the cursor leaked. Converted to `with self._cursor() as curs:`. |
304
- | JSON export malformed on special column names | Column names containing `"` or `\` produced invalid JSON. Now uses `json.dumps()` for all field names. |
305
- | Temp file creation TOCTOU race | `TempFileMgr.new_temp_fn()` discarded the `NamedTemporaryFile` handle, creating a race window. Now uses `tempfile.mkstemp()` for secure creation. |
306
- | `shlex.split` on Windows incorrect mode | Called without `posix=False` on Windows, mishandling backslash-heavy paths in SHELL commands. |
307
- | AST executor `~`/`+` variable scoping broken | The AST executor passed `localvars` through function parameters but never pushed `CommandList` frames onto `commandliststack`. Legacy metacommand handlers (`x_sub`, `x_rm_sub`, `xf_sub_defined`, `SUB_LOCAL`, prompt handlers, REPL) access `commandliststack[-1]` for `~` local and `+` outer-scope variables. This caused `~` vars to be invisible to SQL, `SUB_DEFINED(~var)` to always return false, and the REPL `.vars`/`.stack` to show empty state. Fixed by pushing/popping `CommandList` frames in `execute()` and `_execute_script_native()`. |
308
- | Firebird adapter import | Adapter imported `fdb` (the unmaintained legacy driver) while the `[firebird]` extra declared `firebird-driver`. `pip install execsql2[firebird]` followed by a Firebird connect raised `ModuleNotFoundError: No module named 'fdb'`. Adapter now imports `firebird.driver`, matching the declared dependency. |
309
- | Connect timeouts on network adapters | MySQL, SQL Server / Access / DSN (pyodbc), Firebird, and SMTP all now apply a 30 s connect timeout matching the existing PostgreSQL default. A silently-dropped network peer (firewall rule, dead VM) used to hang scripts indefinitely; now the connect fails fast. |
310
- | AST parser INCLUDE quoted paths broken | The AST parser captured the full INCLUDE target including surrounding quotes (`"path"`), but the legacy dispatch regex stripped them. Quoted INCLUDE paths failed with "File does not exist" even when the file was present. Fixed by stripping matched quote pairs in the parser. |
311
- | AST parser `BEGIN SCRIPT name(params)` rejected | The regex required whitespace between the script name and parameter list. `BEGIN SCRIPT foo(a,b)` (no space before `(`) silently failed to match, causing the matching `END SCRIPT` to raise "Unmatched END SCRIPT metacommand." Fixed by allowing optional whitespace before the parameter expression. |
312
- | AST executor forward SCRIPT references broken | The legacy engine registered all `BEGIN SCRIPT` blocks at parse time (two-pass), so `EXECUTE SCRIPT foo` could appear before the `BEGIN SCRIPT foo` definition. The AST executor walked the tree in a single pass, so forward references failed with "There is no SCRIPT named foo." Fixed by adding a pre-registration scan of all SCRIPT blocks before execution begins. |
274
+ | Area | Fix |
275
+ | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
276
+ | Oracle default port | Corrected from `5432` (PostgreSQL) to `1521`. |
277
+ | MySQL `LOAD DATA INFILE` encoding | Python encoding names are now mapped to MySQL charset names. |
278
+ | `dt_cast` type converters | Base `Database` class auto-populates 8 type converters that were previously left empty after the refactor. |
279
+ | `FileWriter` CPU busy-loop | Uses blocking `queue.get(timeout=0.1)` instead of `get_nowait()` in a tight loop. |
280
+ | Substitution variable cycles | 100-iteration limit prevents infinite loops on cyclic variable references. |
281
+ | 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. |
282
+ | `$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. |
283
+ | 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. |
284
+ | 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. |
285
+ | `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. |
286
+ | `win_config_file` broken | Checked `os.name == "windows"` which Python never returns (correct value is `"nt"`). Fixed to `os.name == "nt"`. |
287
+ | ELSEIF + ANDIF/ORIF | ANDIF/ORIF after an ELSEIF were silently attached to the parent IF condition instead of the ELSEIF clause. The compound condition was never evaluated for the ELSEIF branch. Fixed in the AST parser and executor. |
288
+ | Cursor leak in `select_rowsource()` | Cursor was not closed on query execution failure. Row generators in EXPORT and COPY paths were not explicitly closed on error, relying on garbage collection for cleanup. |
289
+ | `NumericParser` right-associative | Arithmetic operators were parsed right-to-left. `10 - 3 - 2` evaluated as `9` instead of `5`. Fixed to left-associative parsing. |
290
+ | Empty-column check precedence | `DataTable` and `Database.populate_table()` had an operator precedence bug in the extra-column emptiness check — a redundant `and conf.del_empty_cols` caused incorrect short-circuit evaluation. |
291
+ | SQLite import string processing | `SQLiteDatabase.populate_table()` applied `trim_strings`, `replace_newlines`, and `empty_strings` after copying row data, so processing never reached the INSERT. Fixed to process before extraction. |
292
+ | `$CURRENT_DATABASE`/`$CURRENT_DBMS` stale after USE | These variables were only set at startup and on CONNECT, not refreshed when `USE` switched the active database. Now set in `set_static_system_vars()` so they update on any connection change. |
293
+ | `DT_Text.data_type_name` wrong | Was `"character"` (same as `DT_Character`), making error messages indistinguishable. Corrected to `"text"`. |
294
+ | `DT_Varchar` non-string data unchecked | `_from_data()` only enforced the 255-char limit for `str` inputs; non-string values passed through without conversion or length check. |
295
+ | `WriteHooks.write_err()` crash on empty string | `strval[-1]` raised `IndexError` on empty input. Fixed to use `str.endswith()`. |
296
+ | `NumericParser` division by zero | `NumericAstNode.eval()` raised unhandled `ZeroDivisionError`. Now raises `NumericParserError` with a clear message. |
297
+ | `CondAstNode.eval()` could return `None` | Missing fallthrough for unknown node types silently returned `None`. Now raises `CondParserError`. |
298
+ | `DT_Timestamp` claims time-only values | `dateutil.parser.parse()` silently fills in today's date for bare time strings like `"13:15:45"`, so `DT_Timestamp` matched before `DT_Time` in the inference order. `parse_datetime()` now rejects time-only strings. |
299
+ | `CounterVars.substitute` skipped pos 0–1 | `re.I` was passed as the positional `pos` argument, skipping the first 2 characters of every string during counter variable expansion. Counter variables at the very start of a line were never matched. |
300
+ | `exec_cmd` (SQLite/DuckDB) always raised TypeError | Passed bytes to `curs.execute()` via `.encode()`, which Python 3 `sqlite3`/`duckdb` reject. `EXECUTE PROCEDURE` metacommand was non-functional on these backends. |
301
+ | MySQL `LOAD DATA INFILE` injection | File path, delimiter, and quotechar were interpolated without escaping. Now single-quotes are escaped consistent with the PostgreSQL COPY path. |
302
+ | Missing server/database name at connect | Connecting to a server-based database without a server or database name now stops with a clear fatal error ("Configured database requires a server name, but none is provided.") instead of crashing inside the driver with a `TypeError`. |
303
+ | Config file chain infinite loop | The `config_file` directive could chain config files without limit. A circular reference (via symlinks or different relative paths) caused an infinite loop at startup. Now capped at 20 files. |
304
+ | Cursor leaks in database adapters | ~15 methods across all adapters used `curs = self.cursor()` / `curs.close()` without `try/finally`. If the query raised, the cursor leaked. Converted to `with self._cursor() as curs:`. |
305
+ | JSON export malformed on special column names | Column names containing `"` or `\` produced invalid JSON. Now uses `json.dumps()` for all field names. |
306
+ | Temp file creation TOCTOU race | `TempFileMgr.new_temp_fn()` discarded the `NamedTemporaryFile` handle, creating a race window. Now uses `tempfile.mkstemp()` for secure creation. |
307
+ | `shlex.split` on Windows incorrect mode | Called without `posix=False` on Windows, mishandling backslash-heavy paths in SHELL commands. |
308
+ | AST executor `~`/`+` variable scoping broken | The AST executor passed `localvars` through function parameters but never pushed `CommandList` frames onto `commandliststack`. Legacy metacommand handlers (`x_sub`, `x_rm_sub`, `xf_sub_defined`, `SUB_LOCAL`, prompt handlers, REPL) access `commandliststack[-1]` for `~` local and `+` outer-scope variables. This caused `~` vars to be invisible to SQL, `SUB_DEFINED(~var)` to always return false, and the REPL `.vars`/`.stack` to show empty state. Fixed by pushing/popping `CommandList` frames in `execute()` and `_execute_script_native()`. |
309
+ | Firebird adapter import | Adapter imported `fdb` (the unmaintained legacy driver) while the `[firebird]` extra declared `firebird-driver`. `pip install execsql2[firebird]` followed by a Firebird connect raised `ModuleNotFoundError: No module named 'fdb'`. Adapter now imports `firebird.driver`, matching the declared dependency. |
310
+ | Connect timeouts on network adapters | MySQL, SQL Server / Access / DSN (pyodbc), Firebird, and SMTP all now apply a 30 s connect timeout matching the existing PostgreSQL default. A silently-dropped network peer (firewall rule, dead VM) used to hang scripts indefinitely; now the connect fails fast. |
311
+ | AST parser INCLUDE quoted paths broken | The AST parser captured the full INCLUDE target including surrounding quotes (`"path"`), but the legacy dispatch regex stripped them. Quoted INCLUDE paths failed with "File does not exist" even when the file was present. Fixed by stripping matched quote pairs in the parser. |
312
+ | AST parser `BEGIN SCRIPT name(params)` rejected | The regex required whitespace between the script name and parameter list. `BEGIN SCRIPT foo(a,b)` (no space before `(`) silently failed to match, causing the matching `END SCRIPT` to raise "Unmatched END SCRIPT metacommand." Fixed by allowing optional whitespace before the parameter expression. |
313
+ | AST executor forward SCRIPT references broken | The legacy engine registered all `BEGIN SCRIPT` blocks at parse time (two-pass), so `EXECUTE SCRIPT foo` could appear before the `BEGIN SCRIPT foo` definition. The AST executor walked the tree in a single pass, so forward references failed with "There is no SCRIPT named foo." Fixed by adding a pre-registration scan of all SCRIPT blocks before execution begins. |
314
+ | DB-export type inference overrides source types | `DataTable`/`Column` inferred column types from the *string shape* of values — correct for CSV import (everything is `str`) but wrong for DB-query exports where the driver returns typed Python objects. A text column with values like `"2026-06-30 10:07:20 PM"` was inferred as `TIMESTAMP`, causing DuckDB bind errors. SQLite, DuckDB, HDF5, and JSON-schema exports now pass `infer_strings=False` to `DataTable`: string values are classified as text types only, while native Python objects (`Decimal`, `datetime`, etc.) still run through the full accumulator set for accurate schema inference. |
315
+ | `EXPORT FORMAT SQLITE` rejects `decimal.Decimal` | SQLite's Python adapter does not accept `Decimal` in `executemany`, raising `ProgrammingError`. SQLite exports now convert `Decimal` values to their exact string representation before binding; SQLite's NUMERIC affinity stores them without loss of precision. |
316
+ | Unsupported Python objects crash native exports | `EXPORT FORMAT SQLITE` and `EXPORT FORMAT DUCKDB` raised bind errors when the DB driver returned user-defined Python objects (e.g. PostGIS geometry). Both exporters now stringify any object whose type is not in the adapter's safe-binding set, preserving values as their `str()` form instead of crashing. |
313
317
 
314
318
  ______________________________________________________________________
315
319
 
@@ -6,7 +6,7 @@ ______________________________________________________________________
6
6
 
7
7
  ## Execution Flow
8
8
 
9
- When a user runs `execsql2 script.sql mydb.sqlite -t l`, the following sequence occurs:
9
+ When a user runs `execsql script.sql mydb.sqlite -t l`, the following sequence occurs:
10
10
 
11
11
  ```mermaid
12
12
  flowchart TD
@@ -14,7 +14,8 @@ Or with [uv](https://docs.astral.sh/uv/):
14
14
  uv add execsql2
15
15
  ```
16
16
 
17
- This installs the `execsql2` command-line tool.
17
+ This installs the `execsql` and `execsql-format` command-line tools. The PyPI distribution is named
18
+ `execsql2`; the executable remains `execsql` for compatibility.
18
19
 
19
20
  To install with optional dependencies:
20
21
 
@@ -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.21.1
163
+ rev: v2.22.0
164
164
  hooks:
165
165
  - id: execsql-format
166
166
  ```
@@ -12,7 +12,7 @@ When adding a very large amount of data with the [IMPORT](../reference/metacomma
12
12
 
13
13
  ## Implicit DROP TABLE Statements
14
14
 
15
- The "REPLACEMENT" keyword for the [IMPORT](../reference/metacommands.md#import) and [COPY](../reference/metacommands.md#copy) metacommands allows a previously existing table to be replaced. To accomplish this, execsql issues a "DROP TABLE" statement to the database in use. PostgreSQL, SQLite, MySQL, MariaDB, Oracle, and DuckDB support a form of the "DROP TABLE" statement that automatically removes all foreign keys to the named table. execsql uses these forms of the "DROP TABLE" statement for these DBMSs, and therefore use of the "REPLACEMENT" keyword always succeeds at removing the named table before trying to create a new table with the same name. SQL Server, MS-Access, and Firebird do not have a form of the "DROP TABLE" statement that automatically removes foreign keys. Therefore, if the "REPLACEMENT" keyword is used with any of these three DBMSs, for a table that has foreign keys into it, that table will not be dropped, and an error will subsequently occur when execsql issues a "CREATE TABLE" statement to create a new table of the same name. To avoid this, when using any of these three DBMSs, you should include in the script the appropriate SQL commands to remove foreign keys (and possibly even to remove the table) before using the IMPORT or COPY metacommands.
15
+ The "REPLACEMENT" keyword for the [IMPORT](../reference/metacommands.md#import) and [COPY](../reference/metacommands.md#copy) metacommands allows a previously existing table to be replaced. To accomplish this, execsql issues a "DROP TABLE" statement to the database in use. This is not an atomic replacement: if the drop succeeds but a later create or load step fails, the old table has already been removed. If the drop fails, execsql stops the metacommand before creating or loading the replacement table. PostgreSQL, SQLite, MySQL, MariaDB, Oracle, and DuckDB support a form of the "DROP TABLE" statement that automatically removes all foreign keys to the named table. execsql uses these forms of the "DROP TABLE" statement for these DBMSs. SQL Server, MS-Access, and Firebird do not have a form of the "DROP TABLE" statement that automatically removes foreign keys. Therefore, if the "REPLACEMENT" keyword is used with any of these three DBMSs, for a table that has foreign keys into it, that table will not be dropped, and the IMPORT or COPY command will stop. To avoid this, when using any of these three DBMSs, you should include in the script the appropriate SQL commands to remove foreign keys (and possibly even to remove the table) before using the IMPORT or COPY metacommands.
16
16
 
17
17
  ## Boolean Data Types
18
18
 
@@ -333,6 +333,9 @@ The section and property names that may be used in a configuration file are list
333
333
  `allow_system_cmd` { #allow_system_cmd }
334
334
  : When set to "No", the `SYSTEM_CMD` (SHELL) metacommand is disabled. Any script that attempts to execute an OS command will fail with an error. The default is "Yes". This can also be set via the `--no-system-cmd` CLI flag or `allow_system_cmd=False` in the library API. See [Security — Disabling SYSTEM_CMD](security.md#disable_system_cmd) for details.
335
335
 
336
+ `system_cmd_timeout` { #system_cmd_timeout }
337
+ : Maximum number of seconds a foreground `SYSTEM_CMD` may run before *execsql* stops it. The default is `0`, which means no timeout. When a foreground command times out, `$SYSTEM_CMD_EXIT_STATUS` is set to `124` and the metacommand fails. `SYSTEM_CMD ... CONTINUE` starts a detached background process and is not timeout-managed by this setting.
338
+
336
339
  `allow_rm_file` { #allow_rm_file }
337
340
  : When set to "No", the `RM_FILE` metacommand (which deletes a file) is disabled. Any script that attempts to remove a file will fail with an error. The default is "Yes". This can also be set via the `--no-rm-file` CLI flag or `allow_rm_file=False` in the library API. Symmetric with `allow_system_cmd`.
338
341