execsql2 2.5.0__tar.gz → 2.6.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 (282) hide show
  1. {execsql2-2.5.0 → execsql2-2.6.0}/.claude/project_context.md +107 -19
  2. {execsql2-2.5.0 → execsql2-2.6.0}/.gitignore +1 -0
  3. {execsql2-2.5.0 → execsql2-2.6.0}/CHANGELOG.md +21 -1
  4. {execsql2-2.5.0 → execsql2-2.6.0}/CLAUDE.md +1 -0
  5. {execsql2-2.5.0 → execsql2-2.6.0}/PKG-INFO +1 -1
  6. execsql2-2.6.0/docs/about/divergence.md +168 -0
  7. {execsql2-2.5.0 → execsql2-2.6.0}/pyproject.toml +7 -3
  8. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/gui/tui.py +132 -0
  9. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/metacommands/__init__.py +180 -180
  10. execsql2-2.6.0/src/execsql/state.py +452 -0
  11. execsql2-2.6.0/tests/db/test_base.py +1953 -0
  12. execsql2-2.6.0/tests/exporters/test_delimited.py +1248 -0
  13. execsql2-2.6.0/tests/metacommands/test_connect.py +1807 -0
  14. execsql2-2.6.0/tests/test_engine.py +1305 -0
  15. {execsql2-2.5.0 → execsql2-2.6.0}/tests/test_state.py +109 -3
  16. {execsql2-2.5.0 → execsql2-2.6.0}/uv.lock +1 -1
  17. {execsql2-2.5.0 → execsql2-2.6.0}/zensical.toml +1 -0
  18. execsql2-2.5.0/src/execsql/state.py +0 -391
  19. execsql2-2.5.0/tests/db/test_base.py +0 -423
  20. execsql2-2.5.0/tests/exporters/test_delimited.py +0 -460
  21. {execsql2-2.5.0 → execsql2-2.6.0}/.claude/agents/dba.md +0 -0
  22. {execsql2-2.5.0 → execsql2-2.6.0}/.claude/agents/herald.md +0 -0
  23. {execsql2-2.5.0 → execsql2-2.6.0}/.claude/agents/inspector.md +0 -0
  24. {execsql2-2.5.0 → execsql2-2.6.0}/.claude/agents/oracle.md +0 -0
  25. {execsql2-2.5.0 → execsql2-2.6.0}/.claude/agents/patcher.md +0 -0
  26. {execsql2-2.5.0 → execsql2-2.6.0}/.claude/agents/qa.md +0 -0
  27. {execsql2-2.5.0 → execsql2-2.6.0}/.claude/agents/scribe.md +0 -0
  28. {execsql2-2.5.0 → execsql2-2.6.0}/.claude/commands/code-oracle.md +0 -0
  29. {execsql2-2.5.0 → execsql2-2.6.0}/.claude/commands/migrate.md +0 -0
  30. {execsql2-2.5.0 → execsql2-2.6.0}/.claude/commands/review-changes.md +0 -0
  31. {execsql2-2.5.0 → execsql2-2.6.0}/.claude/commands/test-module.md +0 -0
  32. {execsql2-2.5.0 → execsql2-2.6.0}/.claude/commands/update-changelog.md +0 -0
  33. {execsql2-2.5.0 → execsql2-2.6.0}/.claude/commands/where-is.md +0 -0
  34. {execsql2-2.5.0 → execsql2-2.6.0}/.claude/state/status.md +0 -0
  35. {execsql2-2.5.0 → execsql2-2.6.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  36. {execsql2-2.5.0 → execsql2-2.6.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  37. {execsql2-2.5.0 → execsql2-2.6.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  38. {execsql2-2.5.0 → execsql2-2.6.0}/.github/workflows/ci-cd.yml +0 -0
  39. {execsql2-2.5.0 → execsql2-2.6.0}/.pre-commit-config.yaml +0 -0
  40. {execsql2-2.5.0 → execsql2-2.6.0}/.pre-commit-hooks.yaml +0 -0
  41. {execsql2-2.5.0 → execsql2-2.6.0}/.python-version +0 -0
  42. {execsql2-2.5.0 → execsql2-2.6.0}/.readthedocs.yaml +0 -0
  43. {execsql2-2.5.0 → execsql2-2.6.0}/CONTRIBUTING.md +0 -0
  44. {execsql2-2.5.0 → execsql2-2.6.0}/LICENSE.txt +0 -0
  45. {execsql2-2.5.0 → execsql2-2.6.0}/NOTICE +0 -0
  46. {execsql2-2.5.0 → execsql2-2.6.0}/README.md +0 -0
  47. {execsql2-2.5.0 → execsql2-2.6.0}/SECURITY.md +0 -0
  48. {execsql2-2.5.0 → execsql2-2.6.0}/docs/about/contributors.md +0 -0
  49. {execsql2-2.5.0 → execsql2-2.6.0}/docs/about/copyright.md +0 -0
  50. {execsql2-2.5.0 → execsql2-2.6.0}/docs/api/cli.md +0 -0
  51. {execsql2-2.5.0 → execsql2-2.6.0}/docs/api/db.md +0 -0
  52. {execsql2-2.5.0 → execsql2-2.6.0}/docs/api/exporters.md +0 -0
  53. {execsql2-2.5.0 → execsql2-2.6.0}/docs/api/importers.md +0 -0
  54. {execsql2-2.5.0 → execsql2-2.6.0}/docs/api/index.md +0 -0
  55. {execsql2-2.5.0 → execsql2-2.6.0}/docs/api/metacommands.md +0 -0
  56. {execsql2-2.5.0 → execsql2-2.6.0}/docs/dev/adding_db_adapters.md +0 -0
  57. {execsql2-2.5.0 → execsql2-2.6.0}/docs/dev/adding_exporters.md +0 -0
  58. {execsql2-2.5.0 → execsql2-2.6.0}/docs/dev/adding_importers.md +0 -0
  59. {execsql2-2.5.0 → execsql2-2.6.0}/docs/dev/adding_metacommands.md +0 -0
  60. {execsql2-2.5.0 → execsql2-2.6.0}/docs/dev/architecture.md +0 -0
  61. {execsql2-2.5.0 → execsql2-2.6.0}/docs/getting-started/installation.md +0 -0
  62. {execsql2-2.5.0 → execsql2-2.6.0}/docs/getting-started/requirements.md +0 -0
  63. {execsql2-2.5.0 → execsql2-2.6.0}/docs/getting-started/syntax.md +0 -0
  64. {execsql2-2.5.0 → execsql2-2.6.0}/docs/guides/debugging.md +0 -0
  65. {execsql2-2.5.0 → execsql2-2.6.0}/docs/guides/documentation.md +0 -0
  66. {execsql2-2.5.0 → execsql2-2.6.0}/docs/guides/encoding.md +0 -0
  67. {execsql2-2.5.0 → execsql2-2.6.0}/docs/guides/examples.md +0 -0
  68. {execsql2-2.5.0 → execsql2-2.6.0}/docs/guides/formatter.md +0 -0
  69. {execsql2-2.5.0 → execsql2-2.6.0}/docs/guides/logging.md +0 -0
  70. {execsql2-2.5.0 → execsql2-2.6.0}/docs/guides/sql_syntax.md +0 -0
  71. {execsql2-2.5.0 → execsql2-2.6.0}/docs/guides/usage.md +0 -0
  72. {execsql2-2.5.0 → execsql2-2.6.0}/docs/guides/using_scripts.md +0 -0
  73. {execsql2-2.5.0 → execsql2-2.6.0}/docs/images/Compare_planets.png +0 -0
  74. {execsql2-2.5.0 → execsql2-2.6.0}/docs/images/actions.png +0 -0
  75. {execsql2-2.5.0 → execsql2-2.6.0}/docs/images/actions2.png +0 -0
  76. {execsql2-2.5.0 → execsql2-2.6.0}/docs/images/checkboxes.png +0 -0
  77. {execsql2-2.5.0 → execsql2-2.6.0}/docs/images/connect.b64 +0 -0
  78. {execsql2-2.5.0 → execsql2-2.6.0}/docs/images/connect.png +0 -0
  79. {execsql2-2.5.0 → execsql2-2.6.0}/docs/images/create_conf.png +0 -0
  80. {execsql2-2.5.0 → execsql2-2.6.0}/docs/images/data_error1_screenshot.jpg +0 -0
  81. {execsql2-2.5.0 → execsql2-2.6.0}/docs/images/entry_form.png +0 -0
  82. {execsql2-2.5.0 → execsql2-2.6.0}/docs/images/execsql_console.png +0 -0
  83. {execsql2-2.5.0 → execsql2-2.6.0}/docs/images/execsql_logo_01.png +0 -0
  84. {execsql2-2.5.0 → execsql2-2.6.0}/docs/images/fatals.png +0 -0
  85. {execsql2-2.5.0 → execsql2-2.6.0}/docs/images/logo_small.png +0 -0
  86. {execsql2-2.5.0 → execsql2-2.6.0}/docs/images/pause_terminal.png +0 -0
  87. {execsql2-2.5.0 → execsql2-2.6.0}/docs/images/pause_terminal_sm.b64 +0 -0
  88. {execsql2-2.5.0 → execsql2-2.6.0}/docs/images/pause_terminal_sm.png +0 -0
  89. {execsql2-2.5.0 → execsql2-2.6.0}/docs/images/prompt_compare.png +0 -0
  90. {execsql2-2.5.0 → execsql2-2.6.0}/docs/images/set_build_commands.jpg +0 -0
  91. {execsql2-2.5.0 → execsql2-2.6.0}/docs/images/unit_conversions.b64 +0 -0
  92. {execsql2-2.5.0 → execsql2-2.6.0}/docs/images/unit_conversions_029.png +0 -0
  93. {execsql2-2.5.0 → execsql2-2.6.0}/docs/images/unmatched.png +0 -0
  94. {execsql2-2.5.0 → execsql2-2.6.0}/docs/images/vim_execsql_highlight.png +0 -0
  95. {execsql2-2.5.0 → execsql2-2.6.0}/docs/index.md +0 -0
  96. {execsql2-2.5.0 → execsql2-2.6.0}/docs/reference/configuration.md +0 -0
  97. {execsql2-2.5.0 → execsql2-2.6.0}/docs/reference/metacommands.md +0 -0
  98. {execsql2-2.5.0 → execsql2-2.6.0}/docs/reference/security.md +0 -0
  99. {execsql2-2.5.0 → execsql2-2.6.0}/docs/reference/substitution_vars.md +0 -0
  100. {execsql2-2.5.0 → execsql2-2.6.0}/extras/vscode-execsql/README.md +0 -0
  101. {execsql2-2.5.0 → execsql2-2.6.0}/extras/vscode-execsql/package.json +0 -0
  102. {execsql2-2.5.0 → execsql2-2.6.0}/extras/vscode-execsql/syntaxes/execsql.tmLanguage.json +0 -0
  103. {execsql2-2.5.0 → execsql2-2.6.0}/justfile +0 -0
  104. {execsql2-2.5.0 → execsql2-2.6.0}/scripts/generate_vscode_grammar.py +0 -0
  105. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/__init__.py +0 -0
  106. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/__main__.py +0 -0
  107. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/cli/__init__.py +0 -0
  108. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/cli/dsn.py +0 -0
  109. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/cli/help.py +0 -0
  110. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/cli/run.py +0 -0
  111. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/config.py +0 -0
  112. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/constants.py +0 -0
  113. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/db/__init__.py +0 -0
  114. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/db/access.py +0 -0
  115. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/db/base.py +0 -0
  116. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/db/dsn.py +0 -0
  117. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/db/duckdb.py +0 -0
  118. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/db/factory.py +0 -0
  119. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/db/firebird.py +0 -0
  120. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/db/mysql.py +0 -0
  121. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/db/oracle.py +0 -0
  122. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/db/postgres.py +0 -0
  123. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/db/sqlite.py +0 -0
  124. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/db/sqlserver.py +0 -0
  125. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/exceptions.py +0 -0
  126. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/exporters/__init__.py +0 -0
  127. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/exporters/base.py +0 -0
  128. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/exporters/delimited.py +0 -0
  129. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/exporters/duckdb.py +0 -0
  130. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/exporters/feather.py +0 -0
  131. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/exporters/html.py +0 -0
  132. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/exporters/json.py +0 -0
  133. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/exporters/latex.py +0 -0
  134. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/exporters/ods.py +0 -0
  135. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/exporters/parquet.py +0 -0
  136. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/exporters/pretty.py +0 -0
  137. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/exporters/protocol.py +0 -0
  138. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/exporters/raw.py +0 -0
  139. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/exporters/sqlite.py +0 -0
  140. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/exporters/templates.py +0 -0
  141. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/exporters/values.py +0 -0
  142. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/exporters/xls.py +0 -0
  143. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/exporters/xml.py +0 -0
  144. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/exporters/zip.py +0 -0
  145. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/format.py +0 -0
  146. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/gui/__init__.py +0 -0
  147. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/gui/base.py +0 -0
  148. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/gui/console.py +0 -0
  149. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/gui/desktop.py +0 -0
  150. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/importers/__init__.py +0 -0
  151. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/importers/base.py +0 -0
  152. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/importers/csv.py +0 -0
  153. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/importers/feather.py +0 -0
  154. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/importers/ods.py +0 -0
  155. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/importers/xls.py +0 -0
  156. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/metacommands/conditions.py +0 -0
  157. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/metacommands/connect.py +0 -0
  158. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/metacommands/control.py +0 -0
  159. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/metacommands/data.py +0 -0
  160. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/metacommands/debug.py +0 -0
  161. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/metacommands/dispatch.py +0 -0
  162. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/metacommands/io.py +0 -0
  163. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/metacommands/io_export.py +0 -0
  164. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/metacommands/io_fileops.py +0 -0
  165. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/metacommands/io_import.py +0 -0
  166. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/metacommands/io_write.py +0 -0
  167. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/metacommands/prompt.py +0 -0
  168. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/metacommands/script_ext.py +0 -0
  169. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/metacommands/system.py +0 -0
  170. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/models.py +0 -0
  171. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/parser.py +0 -0
  172. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/py.typed +0 -0
  173. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/script/__init__.py +0 -0
  174. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/script/control.py +0 -0
  175. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/script/engine.py +0 -0
  176. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/script/variables.py +0 -0
  177. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/types.py +0 -0
  178. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/utils/__init__.py +0 -0
  179. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/utils/auth.py +0 -0
  180. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/utils/crypto.py +0 -0
  181. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/utils/datetime.py +0 -0
  182. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/utils/errors.py +0 -0
  183. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/utils/fileio.py +0 -0
  184. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/utils/gui.py +0 -0
  185. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/utils/mail.py +0 -0
  186. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/utils/numeric.py +0 -0
  187. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/utils/regex.py +0 -0
  188. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/utils/strings.py +0 -0
  189. {execsql2-2.5.0 → execsql2-2.6.0}/src/execsql/utils/timer.py +0 -0
  190. {execsql2-2.5.0 → execsql2-2.6.0}/templates/README.md +0 -0
  191. {execsql2-2.5.0 → execsql2-2.6.0}/templates/config_settings.sqlite +0 -0
  192. {execsql2-2.5.0 → execsql2-2.6.0}/templates/example_config_prompt.sql +0 -0
  193. {execsql2-2.5.0 → execsql2-2.6.0}/templates/execsql.conf +0 -0
  194. {execsql2-2.5.0 → execsql2-2.6.0}/templates/make_config_db.sql +0 -0
  195. {execsql2-2.5.0 → execsql2-2.6.0}/templates/md_compare.sql +0 -0
  196. {execsql2-2.5.0 → execsql2-2.6.0}/templates/md_glossary.sql +0 -0
  197. {execsql2-2.5.0 → execsql2-2.6.0}/templates/md_upsert.sql +0 -0
  198. {execsql2-2.5.0 → execsql2-2.6.0}/templates/pg_compare.sql +0 -0
  199. {execsql2-2.5.0 → execsql2-2.6.0}/templates/pg_glossary.sql +0 -0
  200. {execsql2-2.5.0 → execsql2-2.6.0}/templates/pg_upsert.sql +0 -0
  201. {execsql2-2.5.0 → execsql2-2.6.0}/templates/script_template.sql +0 -0
  202. {execsql2-2.5.0 → execsql2-2.6.0}/templates/ss_compare.sql +0 -0
  203. {execsql2-2.5.0 → execsql2-2.6.0}/templates/ss_glossary.sql +0 -0
  204. {execsql2-2.5.0 → execsql2-2.6.0}/templates/ss_upsert.sql +0 -0
  205. {execsql2-2.5.0 → execsql2-2.6.0}/tests/__init__.py +0 -0
  206. {execsql2-2.5.0 → execsql2-2.6.0}/tests/cli/__init__.py +0 -0
  207. {execsql2-2.5.0 → execsql2-2.6.0}/tests/cli/test_cli.py +0 -0
  208. {execsql2-2.5.0 → execsql2-2.6.0}/tests/cli/test_cli_e2e.py +0 -0
  209. {execsql2-2.5.0 → execsql2-2.6.0}/tests/cli/test_cli_run.py +0 -0
  210. {execsql2-2.5.0 → execsql2-2.6.0}/tests/conftest.py +0 -0
  211. {execsql2-2.5.0 → execsql2-2.6.0}/tests/db/__init__.py +0 -0
  212. {execsql2-2.5.0 → execsql2-2.6.0}/tests/db/test_duckdb.py +0 -0
  213. {execsql2-2.5.0 → execsql2-2.6.0}/tests/db/test_factory.py +0 -0
  214. {execsql2-2.5.0 → execsql2-2.6.0}/tests/db/test_postgres.py +0 -0
  215. {execsql2-2.5.0 → execsql2-2.6.0}/tests/db/test_sqlite.py +0 -0
  216. {execsql2-2.5.0 → execsql2-2.6.0}/tests/db/test_sqlite_extra.py +0 -0
  217. {execsql2-2.5.0 → execsql2-2.6.0}/tests/exporters/__init__.py +0 -0
  218. {execsql2-2.5.0 → execsql2-2.6.0}/tests/exporters/test_base.py +0 -0
  219. {execsql2-2.5.0 → execsql2-2.6.0}/tests/exporters/test_db.py +0 -0
  220. {execsql2-2.5.0 → execsql2-2.6.0}/tests/exporters/test_duckdb_exporter.py +0 -0
  221. {execsql2-2.5.0 → execsql2-2.6.0}/tests/exporters/test_exporters.py +0 -0
  222. {execsql2-2.5.0 → execsql2-2.6.0}/tests/exporters/test_feather.py +0 -0
  223. {execsql2-2.5.0 → execsql2-2.6.0}/tests/exporters/test_html_latex.py +0 -0
  224. {execsql2-2.5.0 → execsql2-2.6.0}/tests/exporters/test_json.py +0 -0
  225. {execsql2-2.5.0 → execsql2-2.6.0}/tests/exporters/test_ods.py +0 -0
  226. {execsql2-2.5.0 → execsql2-2.6.0}/tests/exporters/test_parquet.py +0 -0
  227. {execsql2-2.5.0 → execsql2-2.6.0}/tests/exporters/test_sqlite_exporter.py +0 -0
  228. {execsql2-2.5.0 → execsql2-2.6.0}/tests/exporters/test_templates.py +0 -0
  229. {execsql2-2.5.0 → execsql2-2.6.0}/tests/exporters/test_xls_xlsx.py +0 -0
  230. {execsql2-2.5.0 → execsql2-2.6.0}/tests/exporters/test_xml.py +0 -0
  231. {execsql2-2.5.0 → execsql2-2.6.0}/tests/exporters/test_zip.py +0 -0
  232. {execsql2-2.5.0 → execsql2-2.6.0}/tests/gui/__init__.py +0 -0
  233. {execsql2-2.5.0 → execsql2-2.6.0}/tests/gui/test_backends.py +0 -0
  234. {execsql2-2.5.0 → execsql2-2.6.0}/tests/importers/__init__.py +0 -0
  235. {execsql2-2.5.0 → execsql2-2.6.0}/tests/importers/test_csv_importer.py +0 -0
  236. {execsql2-2.5.0 → execsql2-2.6.0}/tests/importers/test_feather_importer.py +0 -0
  237. {execsql2-2.5.0 → execsql2-2.6.0}/tests/importers/test_ods_importer.py +0 -0
  238. {execsql2-2.5.0 → execsql2-2.6.0}/tests/importers/test_xls_importer.py +0 -0
  239. {execsql2-2.5.0 → execsql2-2.6.0}/tests/integration/__init__.py +0 -0
  240. {execsql2-2.5.0 → execsql2-2.6.0}/tests/integration/conftest.py +0 -0
  241. {execsql2-2.5.0 → execsql2-2.6.0}/tests/integration/test_dsn.py +0 -0
  242. {execsql2-2.5.0 → execsql2-2.6.0}/tests/integration/test_duckdb.py +0 -0
  243. {execsql2-2.5.0 → execsql2-2.6.0}/tests/integration/test_mysql.py +0 -0
  244. {execsql2-2.5.0 → execsql2-2.6.0}/tests/integration/test_postgres.py +0 -0
  245. {execsql2-2.5.0 → execsql2-2.6.0}/tests/integration/test_sqlite.py +0 -0
  246. {execsql2-2.5.0 → execsql2-2.6.0}/tests/metacommands/__init__.py +0 -0
  247. {execsql2-2.5.0 → execsql2-2.6.0}/tests/metacommands/test_metacommands.py +0 -0
  248. {execsql2-2.5.0 → execsql2-2.6.0}/tests/metacommands/test_metacommands_connect.py +0 -0
  249. {execsql2-2.5.0 → execsql2-2.6.0}/tests/metacommands/test_metacommands_data.py +0 -0
  250. {execsql2-2.5.0 → execsql2-2.6.0}/tests/metacommands/test_metacommands_extended.py +0 -0
  251. {execsql2-2.5.0 → execsql2-2.6.0}/tests/metacommands/test_metacommands_fileops_extra.py +0 -0
  252. {execsql2-2.5.0 → execsql2-2.6.0}/tests/metacommands/test_metacommands_io.py +0 -0
  253. {execsql2-2.5.0 → execsql2-2.6.0}/tests/metacommands/test_metacommands_io_write_extra.py +0 -0
  254. {execsql2-2.5.0 → execsql2-2.6.0}/tests/metacommands/test_metacommands_script_ext.py +0 -0
  255. {execsql2-2.5.0 → execsql2-2.6.0}/tests/metacommands/test_metacommands_system.py +0 -0
  256. {execsql2-2.5.0 → execsql2-2.6.0}/tests/metacommands/test_metacommands_system_extra.py +0 -0
  257. {execsql2-2.5.0 → execsql2-2.6.0}/tests/test_config.py +0 -0
  258. {execsql2-2.5.0 → execsql2-2.6.0}/tests/test_config_data.py +0 -0
  259. {execsql2-2.5.0 → execsql2-2.6.0}/tests/test_constants.py +0 -0
  260. {execsql2-2.5.0 → execsql2-2.6.0}/tests/test_exceptions.py +0 -0
  261. {execsql2-2.5.0 → execsql2-2.6.0}/tests/test_format.py +0 -0
  262. {execsql2-2.5.0 → execsql2-2.6.0}/tests/test_mail.py +0 -0
  263. {execsql2-2.5.0 → execsql2-2.6.0}/tests/test_models.py +0 -0
  264. {execsql2-2.5.0 → execsql2-2.6.0}/tests/test_package.py +0 -0
  265. {execsql2-2.5.0 → execsql2-2.6.0}/tests/test_parser.py +0 -0
  266. {execsql2-2.5.0 → execsql2-2.6.0}/tests/test_registry.py +0 -0
  267. {execsql2-2.5.0 → execsql2-2.6.0}/tests/test_script.py +0 -0
  268. {execsql2-2.5.0 → execsql2-2.6.0}/tests/test_types.py +0 -0
  269. {execsql2-2.5.0 → execsql2-2.6.0}/tests/utils/__init__.py +0 -0
  270. {execsql2-2.5.0 → execsql2-2.6.0}/tests/utils/test_auth.py +0 -0
  271. {execsql2-2.5.0 → execsql2-2.6.0}/tests/utils/test_auth_extra.py +0 -0
  272. {execsql2-2.5.0 → execsql2-2.6.0}/tests/utils/test_crypto.py +0 -0
  273. {execsql2-2.5.0 → execsql2-2.6.0}/tests/utils/test_datetime.py +0 -0
  274. {execsql2-2.5.0 → execsql2-2.6.0}/tests/utils/test_errors.py +0 -0
  275. {execsql2-2.5.0 → execsql2-2.6.0}/tests/utils/test_errors_extra.py +0 -0
  276. {execsql2-2.5.0 → execsql2-2.6.0}/tests/utils/test_fileio.py +0 -0
  277. {execsql2-2.5.0 → execsql2-2.6.0}/tests/utils/test_fileio_extra.py +0 -0
  278. {execsql2-2.5.0 → execsql2-2.6.0}/tests/utils/test_numeric.py +0 -0
  279. {execsql2-2.5.0 → execsql2-2.6.0}/tests/utils/test_regex.py +0 -0
  280. {execsql2-2.5.0 → execsql2-2.6.0}/tests/utils/test_strings.py +0 -0
  281. {execsql2-2.5.0 → execsql2-2.6.0}/tests/utils/test_timer.py +0 -0
  282. {execsql2-2.5.0 → execsql2-2.6.0}/tests/utils/test_timer_extra.py +0 -0
@@ -189,10 +189,10 @@ Triggered on: push to `main`, any tag `v*.*.*`, pull requests.
189
189
 
190
190
  ## Versioning
191
191
 
192
- `bump-my-version` manages versions. Current: `2.4.6`. Bump commands:
192
+ `bump-my-version` manages versions. Current: `2.5.0`. Bump commands:
193
193
 
194
- - `just bump-patch` → 2.4.6 → 2.4.7
195
- - `just bump-minor` → 2.4.6 → 2.5.0
194
+ - `just bump-patch` → 2.5.0 → 2.5.1
195
+ - `just bump-minor` → 2.5.0 → 2.6.0
196
196
  Bumps commit + tag. Pre-commit hook runs `uv lock` + stages `uv.lock`.
197
197
 
198
198
  ## Ruff Config
@@ -243,44 +243,132 @@ the foreseeable future.
243
243
  | Docs reorganized (getting-started/reference/guides/about) | 2.4.6 | 2026-03 |
244
244
  | Pre-commit hook for `execsql-format` | 2.4.3 | 2026-03 |
245
245
  | VS Code syntax highlighting extension | 2.4.x | 2026-03 |
246
+ | Docstring coverage 81% on public API | 2.5.0 | 2026-04 |
247
+ | Developer architecture guide with Mermaid diagrams | 2.5.0 | 2026-04 |
248
+ | Cursor context managers (exec_cmd + vacuum) | 2.5.0 | 2026-04 |
249
+ | Exporter Protocol types (QueryExporter, RowsetExporter) | 2.5.0 | 2026-04 |
250
+ | SubVarSet.merge() optimization (O(1) vs O(V)) | 2.5.0 | 2026-04 |
251
+ | GitHub Actions upgraded to Node.js 24 | 2.5.0 | 2026-04 |
252
+ | GitHub issue/PR templates + SECURITY.md | 2.5.0 | 2026-04 |
253
+ | Database ABC already in place (verified) | 2.5.0 | 2026-04 |
254
+ | Dispatch optimization already in place (verified) | 2.5.0 | 2026-04 |
255
+ | PostgreSQL integration tests (9 tests, CI Docker) | 2.5.0 | 2026-04 |
256
+ | MySQL integration tests (9 tests, CI Docker) | 2.5.0 | 2026-04 |
246
257
 
247
258
  ______________________________________________________________________
248
259
 
249
- ### v2.5Remaining Code Quality
260
+ ### v2.6Architecture & Internal Quality
250
261
 
251
- Theme: Complete the remaining v2.2/v2.3 items and continue stabilization.
262
+ - [x] **`state.py` `RuntimeContext` refactor** — 33 mutable globals consolidated into a slotted `RuntimeContext` class with transparent module proxy. `get_context()`/`set_context()` API added. Zero external call-site changes.
263
+ - [x] **`noqa` cleanup in `metacommands/__init__.py`** — removed all 180 redundant `# noqa` comments; `__all__` already satisfies ruff F401.
264
+ - [x] **Coverage push to 86%** — 403 new tests (3010 total) covering `db/base.py` (55→99%), `metacommands/connect.py` (36→100%), `script/engine.py` (77→95%), `exporters/delimited.py` (76→95%). Remaining gap to 90% is in GUI, ODS, and import handlers.
252
265
 
253
- - **Docstring coverage** target 50%+ on public API (focus on `exporters/`, `db/`, `config.py`)
266
+ ### v2.7New Export/Import Formats
254
267
 
255
- ______________________________________________________________________
268
+ - [ ] **Parquet import** — complement existing Parquet export and Feather import. Natural fit with DuckDB support.
269
+ - [ ] **YAML export** — popular for config-generation workflows; rounds out the format matrix.
270
+ - [ ] **Markdown (GFM) export** — GitHub-flavored markdown tables. Lightweight and useful for docs/reports.
271
+ - [ ] **Excel (XLSX) multi-sheet export** — multiple queries → multiple named sheets in one workbook, with basic formatting.
256
272
 
257
- ### v2.6Architecture & Performance
273
+ ### v2.8Scripting Power Features
258
274
 
259
- Theme: Internal modernization. May include backward-incompatible internal API changes (public CLI behavior preserved).
275
+ - [ ] **`ASSERT` metacommand** `-- !x! ASSERT <condition> "message"`. Data validation for CI pipelines and sanity checks.
276
+ - [ ] **`--dry-run` improvements** — show SQL with substitution variables expanded, not just raw metacommands.
277
+ - [ ] **Script profiling (`--profile`)** — per-statement execution times, summary report at end. Leverages existing `Timer` infrastructure.
278
+ - [ ] **Parallel execution blocks** — `PARALLEL BEGIN ... PARALLEL END` for independent statements. See design notes below.
260
279
 
261
- - **Database ABC with `@abstractmethod`** replace runtime `DatabaseNotImplementedError` with true abstract methods
262
- - **Cursor context managers** — explicit cursor lifecycle in `execute()`, `select_data()`, `select_rowsource()`
263
- - **Metacommand dispatch optimization** — consider dict/trie lookup instead of O(N) regex scan
264
- - **Variable substitution optimization** — reduce O(V×D) complexity
265
- - **Exporter protocol** — define a `Protocol` or ABC for exporters with a common interface
280
+ ### v2.9Library API & Developer Experience
266
281
 
267
- ______________________________________________________________________
282
+ - [ ] **Programmatic Python API** — `execsql.run(script, db=...)` for notebook/pipeline usage. Depends on `RuntimeContext` refactor.
283
+ - [ ] **TOML configuration** — `execsql.toml` as modern alternative to legacy INI format (coexist initially).
284
+
285
+ ### v2.10 — Testing & CI Hardening
286
+
287
+ - [ ] **Property-based testing (Hypothesis)** — for parsers, type inference, substitution variables.
288
+ - [ ] **Parser fuzzing** — `CondParser` and `NumericParser` handle arbitrary user input; fuzz for edge cases.
289
+ - [ ] **Nightly CI against latest DB driver versions** — catch upstream breakage in psycopg2, pymysql, duckdb, etc.
290
+ - [ ] **CI benchmarks** — track substitution variable and dispatch performance over time.
291
+
292
+ ### v2.11 — Documentation & Community
293
+
294
+ - [ ] **Cookbook / recipes page** — real-world examples: ETL workflows, HTML reports, data validation pipelines.
295
+ - [ ] **Migration guide from upstream execsql** — what changed, what's new, how to switch.
296
+ - [ ] **Interactive tutorial** — guided walkthrough script against a bundled SQLite DB.
268
297
 
269
298
  ### v3.0+ — Future
270
299
 
271
- - **Plugin system** — allow external packages to register exporters, importers, or metacommands
300
+ - [ ] **Plugin system** — entry points for `execsql.exporters`, `execsql.importers`, `execsql.metacommands` allowing external packages to register new handlers.
301
+ - [ ] **LSP / language server** — for the VS Code extension: autocomplete metacommands, validate substitution variables, jump-to-definition for `INCLUDE`d scripts.
272
302
 
273
303
  ______________________________________________________________________
274
304
 
275
305
  ### Ongoing / No-milestone
276
306
 
277
- - PostgreSQL integration tests (requires external server — CI docker service)
278
- - MySQL integration tests (same)
279
- - `savedscripts` memory pruning
280
307
  - Textual TUI polish
281
308
 
282
309
  ______________________________________________________________________
283
310
 
311
+ ### Design Notes: Parallel Execution Blocks
312
+
313
+ **Concept:** Allow users to declare groups of independent SQL statements that
314
+ can run concurrently, reducing wall-clock time for ETL scripts with
315
+ independent work.
316
+
317
+ **Syntax:**
318
+ ```sql
319
+ -- !x! PARALLEL BEGIN [WORKERS=4]
320
+ INSERT INTO summary_a SELECT ... FROM raw_data;
321
+ INSERT INTO summary_b SELECT ... FROM raw_data;
322
+ INSERT INTO summary_c SELECT ... FROM raw_data;
323
+ -- !x! PARALLEL END
324
+ ```
325
+
326
+ **How it would work in the engine:**
327
+
328
+ 1. When `runscripts()` encounters `PARALLEL BEGIN`, the engine enters a
329
+ "collecting" mode (similar to how `LOOP` compiles commands into a
330
+ `CommandList` before executing). Statements are accumulated but not run.
331
+
332
+ 2. On `PARALLEL END`, the collected statements are dispatched to a
333
+ `concurrent.futures.ThreadPoolExecutor` (or `ProcessPoolExecutor`).
334
+ Each statement gets its own database cursor (or connection from
335
+ `DatabasePool`).
336
+
337
+ 3. The main `runscripts()` loop blocks at the `PARALLEL END` until all
338
+ futures complete. Errors from any worker are collected and raised as
339
+ a combined `ErrInfo`.
340
+
341
+ **Key constraints and design decisions:**
342
+
343
+ - **No shared mutable state inside parallel blocks.** Substitution variable
344
+ writes (`SET`), `IF/ELSE`, `LOOP`, `INCLUDE`, and other control-flow
345
+ metacommands are **prohibited** inside `PARALLEL` blocks — only raw SQL
346
+ and simple export metacommands are allowed. The parser rejects anything
347
+ else at compile time.
348
+
349
+ - **Connection handling.** Each parallel worker needs its own cursor or
350
+ connection. `DatabasePool` already exists in `db/base.py` but currently
351
+ manages one connection per named alias. This would need a pool-per-alias
352
+ model (e.g., min/max connections) or each worker opens a fresh connection
353
+ from the same DSN.
354
+
355
+ - **Depends on `RuntimeContext` refactor.** The current `state.py` globals
356
+ (especially `commandliststack`, `if_stack`, `subvars`) are not
357
+ thread-safe. Workers would need isolated read-only snapshots of
358
+ substitution variables and their own cursor state. The `RuntimeContext`
359
+ work in v2.6 is a prerequisite.
360
+
361
+ - **`WORKERS=N`** defaults to `min(len(statements), os.cpu_count())`.
362
+ Configurable via the metacommand or `execsql.toml`.
363
+
364
+ - **Transaction semantics.** Each statement runs in its own implicit
365
+ transaction (autocommit). If the user needs atomicity across the whole
366
+ block, they wrap it in `BEGIN BATCH ... END BATCH` outside the parallel
367
+ block — but that negates parallelism for most backends, so this is
368
+ mainly useful for independent ETL loads.
369
+
370
+ ______________________________________________________________________
371
+
284
372
  ## Open Design Questions
285
373
 
286
374
  ### Distribution / single-file invocation model
@@ -29,6 +29,7 @@ Thumbs.db
29
29
  .pytest_cache/
30
30
  .coverage
31
31
  coverage.xml
32
+ *.py,cover
32
33
  .tox/
33
34
  htmlcov/
34
35
  execsql.log
@@ -13,7 +13,27 @@ ______________________________________________________________________
13
13
 
14
14
  ______________________________________________________________________
15
15
 
16
- ## [2.5.0] - 2026-03-31
16
+ ## [2.6.0] - 2026-04-01
17
+
18
+ ### Added
19
+
20
+ - Textual TUI `console_save()` — writes console output to a file, matching Tkinter parity.
21
+ - Keyboard shortcut hints on Textual TUI dialog screens — Escape to cancel, Enter to submit, with `Footer` widget on all major dialog screens.
22
+ - `RuntimeContext` class in `state.py` — groups all 33 mutable runtime globals into a single slotted object. Enables isolated contexts for testing and future concurrent execution.
23
+ - `get_context()` / `set_context()` public API for programmatic access to the active runtime context.
24
+ - Divergence from Upstream documentation page (`docs/about/divergence.md`) listing all user-visible changes since the fork.
25
+ - Test coverage raised from 80% to 86% — 403 new tests across `db/base.py`, `metacommands/connect.py`, `script/engine.py`, and `exporters/delimited.py`.
26
+
27
+ ### Changed
28
+
29
+ - `state.py` module now uses a `types.ModuleType` subclass that transparently proxies attribute reads and writes to the active `RuntimeContext` instance. All existing `_state.foo` call sites continue working with zero changes.
30
+ - `reset()` simplified from 40 lines with 7 `global` statements to a clean context replacement (preserving `filewriter`).
31
+ - `initialize()` and `endloop()` rewritten to use `_ctx` directly instead of `global` statements.
32
+ - Removed 180 redundant `# noqa` suppressions from `metacommands/__init__.py` — the existing `__all__` already satisfies ruff F401.
33
+
34
+ ______________________________________________________________________
35
+
36
+ ## [2.5.0] - 2026-04-01
17
37
 
18
38
  ### Added
19
39
 
@@ -51,4 +51,5 @@ A multi-agent system where specialized agents collaborate to improve, extend, de
51
51
  - All code must pass `ruff check` and target Python 3.10+
52
52
  - **Every user-visible change must be reflected in `CHANGELOG.md`** under the `[Unreleased]` section using [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) categories: Added, Changed, Fixed, Removed. Do not leave changelog updates for later — include them in the same commit or PR as the code change.
53
53
  - **Every user-visible change must also update documentation** — review and revise `README.md`, `docs/`, and any other relevant documentation to stay consistent with the code. New CLI options, features, or behavior changes must be reflected in the README Options table, feature list, and the corresponding docs page. Do not leave doc updates for later. Docs are organized into subdirectories: `docs/getting-started/`, `docs/reference/` (configuration, metacommands, substitution_vars, security), `docs/guides/`, `docs/about/`, `docs/api/`, `docs/dev/`.
54
+ - **Every change that diverges from upstream execsql v1.130.1 must update `docs/about/divergence.md`** — this includes new features, changed behavior, security fixes, and removed functionality. The divergence page is the canonical record of how execsql2 differs from the original monolith. Keep entries concise and organized under the existing section headings (Added Features, Changed Behavior, Security and Correctness Fixes, Removed Features).
54
55
  - **After every version bump and push, monitor CI** — push with `git push && git push --tags` to include version tags, then run `gh run list --limit 1` to get the run ID, then `gh run watch <id> --exit-status` to block until it completes. Bump commits trigger PyPI publish and GitHub Release, so failures must be caught and fixed immediately.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: execsql2
3
- Version: 2.5.0
3
+ Version: 2.6.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: Repository, https://github.com/geocoug/execsql
6
6
  Project-URL: Issues, https://github.com/geocoug/execsql/issues
@@ -0,0 +1,168 @@
1
+ # Divergence from Upstream
2
+
3
+ execsql2 is a maintained fork of [execsql](https://execsql.readthedocs.io/)
4
+ v1.130.1 by R. Dreas Nielsen. This page documents all user-visible changes
5
+ since the fork was created: new features, changed behavior, security fixes,
6
+ and removed functionality.
7
+
8
+ For a chronological view, see the [Change Log](change_log.md).
9
+
10
+ ______________________________________________________________________
11
+
12
+ ## Added Features
13
+
14
+ ### CLI Options
15
+
16
+ | Flag | Description |
17
+ | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
18
+ | `--version` | Print version and exit (Rich-formatted). |
19
+ | `-c` / `--command` | Execute an inline SQL or metacommand string instead of a script file. |
20
+ | `--dsn` / `--connection-string` | Accept a standard database URL (e.g. `postgresql://user:pass@host/db`). Supports `postgresql`, `mysql`, `mssql`, `oracle`, `firebird`, `sqlite`, and `duckdb` schemes. |
21
+ | `--output-dir` | Set a default base directory for export output files. |
22
+ | `--progress` | Show a Rich progress bar during long-running IMPORT operations. |
23
+ | `--dump-keywords` | Emit all metacommand keywords, conditionals, config options, and export formats as structured JSON. |
24
+ | `--gui-framework` | Select GUI backend: `tkinter` (default) or `textual` (terminal UI). |
25
+ | `--dry-run` | Parse the script and print the full command list without connecting to a database or executing anything. |
26
+
27
+ ### Export Formats
28
+
29
+ | Format | Description |
30
+ | --------- | ------------------------------------------------------------------------------- |
31
+ | `PARQUET` | Export query or table results to Apache Parquet via `polars`. |
32
+ | `FEATHER` | Export to Apache Feather/IPC via `polars` + `pyarrow` (upstream used `pandas`). |
33
+
34
+ ### Metacommands
35
+
36
+ | Metacommand | Description |
37
+ | ---------------------- | --------------------------------------------------------------------- |
38
+ | `CONFIG SHOW_PROGRESS` | Enable the Rich progress bar for IMPORT operations at runtime. |
39
+ | `CONFIG LOG_SQL` | Enable SQL query audit logging — writes executed SQL to the log file. |
40
+
41
+ ### Configuration Options
42
+
43
+ New options in `execsql.conf`:
44
+
45
+ | Option | Section | Description |
46
+ | -------------------------- | ----------- | ----------------------------------------------------------------- |
47
+ | `use_keyring` | `[connect]` | Use the OS keyring for credential storage (default: `yes`). |
48
+ | `show_progress` | `[input]` | Enable Rich progress bar for IMPORT (default: `no`). |
49
+ | `import_progress_interval` | `[input]` | Log a status line every N rows during IMPORT (default: `0`). |
50
+ | `log_sql` | `[config]` | Enable SQL audit logging (default: `no`). |
51
+ | `max_log_size_mb` | `[config]` | Rotate the log file at this size in MB (default: `0` = disabled). |
52
+
53
+ ### Tools
54
+
55
+ | Tool | Description |
56
+ | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
57
+ | `execsql-format` | Standalone CLI for normalizing metacommand indentation and uppercasing SQL keywords. Supports `--check` and `--in-place` modes. Also available as a [pre-commit hook](../guides/formatter.md). |
58
+
59
+ ### GUI
60
+
61
+ | Feature | Description |
62
+ | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
63
+ | Textual TUI backend | Full terminal-UI backend via the `textual` library. Provides all dialog types (password, pause, message, entry, compare, action, etc.) in the terminal. |
64
+ | Console fallback | Text-only backend that handles GUI calls in headless environments by printing to stdout. |
65
+
66
+ ### Authentication
67
+
68
+ | Feature | Description |
69
+ | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
70
+ | OS keyring integration | When the `keyring` package is installed, passwords are stored in and retrieved from the OS credential store (macOS Keychain, Windows Credential Manager, Linux SecretService). |
71
+ | Keyring retry on auth failure | If a stored password is rejected, the stale entry is deleted, the user is re-prompted, and the new password is saved automatically. |
72
+
73
+ ### Logging Enhancements
74
+
75
+ | Feature | Description |
76
+ | ----------------------------- | ---------------------------------------------------------------------------------- |
77
+ | Per-event ISO 8601 timestamps | `status`, `connect`, `action`, and `user_msg` log entries include a timestamp. |
78
+ | Run duration in exit record | The `exit` log record includes elapsed wall-clock time. |
79
+ | Run ID millisecond precision | Run identifier format changed from `%Y%m%d_%H%M_%S` to `%Y%m%d_%H%M_%S_NNN`. |
80
+ | SQL audit log record | New `sql` record type containing DB name, line number, and query text. |
81
+ | Import progress log | Periodic row-count status lines during IMPORT when `import_progress_interval > 0`. |
82
+
83
+ ### Developer / Packaging
84
+
85
+ | Feature | Description |
86
+ | --------------------------- | --------------------------------------------------------------------------------------------------------------- |
87
+ | VS Code syntax highlighting | Auto-generated `tmLanguage.json` grammar from the dispatch table. |
88
+ | `py.typed` marker | PEP 561 marker enabling downstream static type checking. |
89
+ | Structured keyword registry | `--dump-keywords` introspects the dispatch table and outputs JSON used by the grammar generator and test suite. |
90
+
91
+ ______________________________________________________________________
92
+
93
+ ## Changed Behavior
94
+
95
+ ### CLI Interface
96
+
97
+ 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`.
98
+
99
+ ### Internal State Management
100
+
101
+ All 33 mutable runtime globals in `state.py` have been consolidated into a `RuntimeContext` object. The module uses a transparent proxy so existing code is unaffected, but the architecture now supports isolated contexts for testing and future concurrent execution.
102
+
103
+ ### Substitution Variables
104
+
105
+ - **Cycle detection** — `substitute_vars()` raises an error after 100 iterations to prevent infinite loops when variables reference each other cyclically. Upstream had no protection.
106
+ - **O(1) substitution** — Variable substitution uses a single combined regex and dict lookup instead of O(V) per-variable regex passes. Behavior is identical; performance is improved.
107
+
108
+ ### Database Adapters
109
+
110
+ - **`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.
111
+ - **Connection timeouts** — PostgreSQL and SQLite adapters accept a connection timeout parameter (default 30 seconds).
112
+ - **DuckDB temporal types** — `TIMESTAMPTZ`, `TIMESTAMP`, `DATE`, `TIME` now map to native DuckDB types instead of `TEXT`.
113
+
114
+ ### Error Handling
115
+
116
+ - **Exception hierarchy** — All custom exceptions inherit from `ExecSqlError`, enabling `except ExecSqlError` to catch any execsql-originated error.
117
+ - **Exception chaining** — All `raise` statements inside `except` blocks preserve the original traceback via `from`.
118
+
119
+ ______________________________________________________________________
120
+
121
+ ## Security and Correctness Fixes
122
+
123
+ These are behavioral changes driven by security or correctness issues in the upstream code.
124
+
125
+ ### Injection Fixes
126
+
127
+ | Area | Fix |
128
+ | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
129
+ | Database metadata queries | `schema_exists()`, `table_exists()`, `column_exists()`, `table_columns()`, `view_exists()`, `role_exists()` across all 9 adapters now use parameterized queries. Upstream used string interpolation. |
130
+ | `import_entire_file()` | Column names are quoted with `quote_identifier()` instead of interpolated into INSERT statements. |
131
+ | PostgreSQL `CREATE DATABASE` | Database name and encoding are quoted. COPY delimiter and quote character are validated. |
132
+ | `$SHEETS_TABLES_VALUES` | Sheet names from ODS/XLS imports are escaped before embedding in SQL. |
133
+ | HTTP `Content-Disposition` | Filename is sanitized to prevent HTTP response splitting in SERVE. |
134
+
135
+ ### Template and Export Safety
136
+
137
+ | Area | Fix |
138
+ | ----------------- | ------------------------------------------------------------------------------------------------------ |
139
+ | Jinja2 sandboxing | Templates run in `SandboxedEnvironment` instead of the default `jinja2.Template`. |
140
+ | HTML export | Column headers and cell values are escaped with `html.escape()` to prevent XSS. |
141
+ | XML export | Values are escaped with `xml.sax.saxutils.escape()`. Invalid XML element name characters are replaced. |
142
+ | JSON export | The `description` field uses `json.dumps()` instead of string interpolation. |
143
+
144
+ ### Credential and Logging Safety
145
+
146
+ | Area | Fix |
147
+ | ---------------------------- | ------------------------------------------------------------------------------------------ |
148
+ | ODBC password redaction | Connection strings in log output have `Pwd=***` substituted before logging. |
149
+ | `enc_password` documentation | Prominent warnings that XOR encryption is obfuscation only — keys are hardcoded in source. |
150
+
151
+ ### Bug Fixes
152
+
153
+ | Area | Fix |
154
+ | --------------------------------- | ---------------------------------------------------------------------------------------------------------- |
155
+ | Oracle default port | Corrected from `5432` (PostgreSQL) to `1521`. |
156
+ | MySQL `LOAD DATA INFILE` encoding | Python encoding names are now mapped to MySQL charset names. |
157
+ | `dt_cast` type converters | Base `Database` class auto-populates 8 type converters that were previously left empty after the refactor. |
158
+ | `FileWriter` CPU busy-loop | Uses blocking `queue.get(timeout=0.1)` instead of `get_nowait()` in a tight loop. |
159
+ | Substitution variable cycles | 100-iteration limit prevents infinite loops on cyclic variable references. |
160
+
161
+ ______________________________________________________________________
162
+
163
+ ## Removed Features
164
+
165
+ | Feature | Reason |
166
+ | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
167
+ | Airspeed template processor | The `airspeed` library (Velocity clone) is unmaintained since ~2018. Use `FORMAT jinja` instead. The `airspeed` value for `template_processor` in `execsql.conf` is no longer accepted. |
168
+ | Python 2 compatibility | All Python 2 constructs (`stringtypes`, `u""` literals, `optparse`, etc.) have been removed. execsql2 requires Python 3.10+. |
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "execsql2"
7
- version = "2.5.0"
7
+ version = "2.6.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" }
@@ -147,6 +147,10 @@ ignore = [
147
147
  "UP017", # Use datetime.UTC alias (not available in Python 3.10)
148
148
  ]
149
149
 
150
+ [tool.ruff.lint.per-file-ignores]
151
+ # F822: context attrs are proxied via __getattr__/__setattr__ on the module class, not defined as module-level variables
152
+ "src/execsql/state.py" = ["F822"]
153
+
150
154
  [tool.ruff.format]
151
155
  quote-style = "double"
152
156
  indent-style = "space"
@@ -154,7 +158,7 @@ skip-magic-trailing-comma = false
154
158
  line-ending = "auto"
155
159
 
156
160
  [tool.bumpversion]
157
- current_version = "2.5.0"
161
+ current_version = "2.6.0"
158
162
  commit = true
159
163
  commit_args = "--no-verify"
160
164
  tag = true
@@ -178,7 +182,7 @@ addopts = [
178
182
  "--cov=execsql",
179
183
  "--cov-branch",
180
184
  "--cov-report=xml",
181
- "--cov-fail-under=80",
185
+ "--cov-fail-under=85",
182
186
  "--strict-markers",
183
187
  "--strict-config",
184
188
  "--color=yes",