execsql2 2.1.1__tar.gz → 2.2.1__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 (255) hide show
  1. {execsql2-2.1.1 → execsql2-2.2.1}/.claude/project_context.md +87 -52
  2. {execsql2-2.1.1 → execsql2-2.2.1}/.github/workflows/ci-cd.yml +1 -0
  3. {execsql2-2.1.1 → execsql2-2.2.1}/.gitignore +6 -1
  4. {execsql2-2.1.1 → execsql2-2.2.1}/.pre-commit-config.yaml +2 -1
  5. {execsql2-2.1.1 → execsql2-2.2.1}/.readthedocs.yaml +2 -1
  6. {execsql2-2.1.1 → execsql2-2.2.1}/CHANGELOG.md +128 -0
  7. {execsql2-2.1.1 → execsql2-2.2.1}/PKG-INFO +12 -7
  8. {execsql2-2.1.1 → execsql2-2.2.1}/README.md +11 -6
  9. {execsql2-2.1.1 → execsql2-2.2.1}/docs/api/cli.md +1 -1
  10. {execsql2-2.1.1 → execsql2-2.2.1}/docs/api/db.md +20 -0
  11. {execsql2-2.1.1 → execsql2-2.2.1}/docs/api/exporters.md +4 -0
  12. {execsql2-2.1.1 → execsql2-2.2.1}/docs/configuration.md +7 -0
  13. {execsql2-2.1.1 → execsql2-2.2.1}/docs/dev/adding_metacommands.md +14 -10
  14. {execsql2-2.1.1 → execsql2-2.2.1}/docs/index.md +11 -11
  15. {execsql2-2.1.1 → execsql2-2.2.1}/docs/syntax.md +3 -0
  16. {execsql2-2.1.1 → execsql2-2.2.1}/docs/usage.md +41 -0
  17. execsql2-2.2.1/extras/vscode-execsql/README.md +153 -0
  18. execsql2-2.2.1/extras/vscode-execsql/package.json +24 -0
  19. execsql2-2.2.1/extras/vscode-execsql/syntaxes/execsql.tmLanguage.json +240 -0
  20. {execsql2-2.1.1 → execsql2-2.2.1}/justfile +9 -0
  21. {execsql2-2.1.1 → execsql2-2.2.1}/pyproject.toml +3 -3
  22. execsql2-2.2.1/scripts/generate_vscode_grammar.py +306 -0
  23. execsql2-2.2.1/src/execsql/cli/__init__.py +436 -0
  24. execsql2-2.2.1/src/execsql/cli/dsn.py +86 -0
  25. execsql2-2.2.1/src/execsql/cli/help.py +140 -0
  26. execsql2-2.1.1/src/execsql/cli.py → execsql2-2.2.1/src/execsql/cli/run.py +14 -589
  27. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/config.py +13 -1
  28. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/db/access.py +16 -12
  29. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/db/base.py +158 -90
  30. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/db/dsn.py +6 -5
  31. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/db/duckdb.py +2 -2
  32. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/db/firebird.py +23 -19
  33. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/db/mysql.py +8 -7
  34. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/db/oracle.py +11 -11
  35. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/db/postgres.py +28 -16
  36. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/db/sqlite.py +12 -11
  37. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/db/sqlserver.py +5 -3
  38. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/exceptions.py +7 -7
  39. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/exporters/base.py +6 -1
  40. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/exporters/delimited.py +44 -35
  41. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/exporters/duckdb.py +2 -2
  42. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/exporters/feather.py +6 -6
  43. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/exporters/html.py +83 -69
  44. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/exporters/json.py +50 -42
  45. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/exporters/latex.py +33 -27
  46. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/exporters/ods.py +4 -4
  47. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/exporters/parquet.py +2 -2
  48. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/exporters/pretty.py +11 -9
  49. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/exporters/raw.py +17 -13
  50. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/exporters/sqlite.py +2 -2
  51. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/exporters/templates.py +23 -15
  52. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/exporters/values.py +22 -20
  53. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/exporters/xls.py +4 -4
  54. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/exporters/xml.py +28 -13
  55. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/importers/base.py +4 -4
  56. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/importers/csv.py +6 -6
  57. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/importers/feather.py +4 -4
  58. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/importers/ods.py +4 -4
  59. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/importers/xls.py +4 -4
  60. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/metacommands/__init__.py +518 -67
  61. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/metacommands/conditions.py +101 -27
  62. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/metacommands/control.py +8 -4
  63. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/metacommands/data.py +6 -6
  64. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/metacommands/debug.py +6 -2
  65. execsql2-2.2.1/src/execsql/metacommands/io.py +70 -0
  66. execsql2-2.2.1/src/execsql/metacommands/io_export.py +442 -0
  67. execsql2-2.2.1/src/execsql/metacommands/io_fileops.py +287 -0
  68. execsql2-2.2.1/src/execsql/metacommands/io_import.py +398 -0
  69. execsql2-2.2.1/src/execsql/metacommands/io_write.py +248 -0
  70. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/metacommands/prompt.py +22 -66
  71. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/metacommands/system.py +7 -2
  72. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/script.py +49 -5
  73. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/types.py +20 -20
  74. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/utils/fileio.py +15 -8
  75. {execsql2-2.1.1 → execsql2-2.2.1}/tests/exporters/test_sqlite_exporter.py +28 -7
  76. {execsql2-2.1.1 → execsql2-2.2.1}/tests/exporters/test_templates.py +1 -1
  77. {execsql2-2.1.1 → execsql2-2.2.1}/tests/metacommands/test_metacommands.py +4 -1
  78. execsql2-2.2.1/tests/metacommands/test_metacommands_data.py +381 -0
  79. {execsql2-2.1.1 → execsql2-2.2.1}/tests/metacommands/test_metacommands_extended.py +9 -9
  80. execsql2-2.2.1/tests/metacommands/test_metacommands_fileops_extra.py +335 -0
  81. {execsql2-2.1.1 → execsql2-2.2.1}/tests/metacommands/test_metacommands_io.py +10 -7
  82. execsql2-2.2.1/tests/metacommands/test_metacommands_io_write_extra.py +453 -0
  83. execsql2-2.2.1/tests/metacommands/test_metacommands_system_extra.py +324 -0
  84. {execsql2-2.1.1 → execsql2-2.2.1}/tests/test_cli.py +136 -0
  85. {execsql2-2.1.1 → execsql2-2.2.1}/tests/test_format.py +2 -0
  86. execsql2-2.2.1/tests/test_integration.py +818 -0
  87. execsql2-2.1.1/tests/test_integration.py → execsql2-2.2.1/tests/test_integration_duckdb.py +136 -53
  88. execsql2-2.2.1/tests/test_registry.py +172 -0
  89. execsql2-2.2.1/tests/utils/__init__.py +0 -0
  90. execsql2-2.2.1/tests/utils/test_auth_extra.py +111 -0
  91. execsql2-2.2.1/tests/utils/test_errors_extra.py +193 -0
  92. execsql2-2.2.1/tests/utils/test_fileio_extra.py +332 -0
  93. execsql2-2.2.1/tests/utils/test_timer_extra.py +36 -0
  94. {execsql2-2.1.1 → execsql2-2.2.1}/uv.lock +1 -1
  95. execsql2-2.1.1/src/execsql/metacommands/io.py +0 -1313
  96. {execsql2-2.1.1 → execsql2-2.2.1}/.claude/agents/changelog-manager.md +0 -0
  97. {execsql2-2.1.1 → execsql2-2.2.1}/.claude/agents/code-oracle.md +0 -0
  98. {execsql2-2.1.1 → execsql2-2.2.1}/.claude/agents/code-reviewer.md +0 -0
  99. {execsql2-2.1.1 → execsql2-2.2.1}/.claude/agents/docs-author.md +0 -0
  100. {execsql2-2.1.1 → execsql2-2.2.1}/.claude/agents/migration-coder.md +0 -0
  101. {execsql2-2.1.1 → execsql2-2.2.1}/.claude/agents/monolith-navigator.md +0 -0
  102. {execsql2-2.1.1 → execsql2-2.2.1}/.claude/agents/test-engineer.md +0 -0
  103. {execsql2-2.1.1 → execsql2-2.2.1}/.claude/commands/code-oracle.md +0 -0
  104. {execsql2-2.1.1 → execsql2-2.2.1}/.claude/commands/migrate.md +0 -0
  105. {execsql2-2.1.1 → execsql2-2.2.1}/.claude/commands/review-changes.md +0 -0
  106. {execsql2-2.1.1 → execsql2-2.2.1}/.claude/commands/test-module.md +0 -0
  107. {execsql2-2.1.1 → execsql2-2.2.1}/.claude/commands/update-changelog.md +0 -0
  108. {execsql2-2.1.1 → execsql2-2.2.1}/.claude/commands/where-is.md +0 -0
  109. {execsql2-2.1.1 → execsql2-2.2.1}/.python-version +0 -0
  110. {execsql2-2.1.1 → execsql2-2.2.1}/CONTRIBUTING.md +0 -0
  111. {execsql2-2.1.1 → execsql2-2.2.1}/LICENSE.txt +0 -0
  112. {execsql2-2.1.1 → execsql2-2.2.1}/NOTICE +0 -0
  113. {execsql2-2.1.1 → execsql2-2.2.1}/docs/api/importers.md +0 -0
  114. {execsql2-2.1.1 → execsql2-2.2.1}/docs/api/index.md +0 -0
  115. {execsql2-2.1.1 → execsql2-2.2.1}/docs/api/metacommands.md +0 -0
  116. {execsql2-2.1.1 → execsql2-2.2.1}/docs/change_log.md +0 -0
  117. {execsql2-2.1.1 → execsql2-2.2.1}/docs/contributors.md +0 -0
  118. {execsql2-2.1.1 → execsql2-2.2.1}/docs/copyright.md +0 -0
  119. {execsql2-2.1.1 → execsql2-2.2.1}/docs/debugging.md +0 -0
  120. {execsql2-2.1.1 → execsql2-2.2.1}/docs/dev/adding_db_adapters.md +0 -0
  121. {execsql2-2.1.1 → execsql2-2.2.1}/docs/dev/adding_exporters.md +0 -0
  122. {execsql2-2.1.1 → execsql2-2.2.1}/docs/dev/adding_importers.md +0 -0
  123. {execsql2-2.1.1 → execsql2-2.2.1}/docs/documentation.md +0 -0
  124. {execsql2-2.1.1 → execsql2-2.2.1}/docs/encoding.md +0 -0
  125. {execsql2-2.1.1 → execsql2-2.2.1}/docs/examples.md +0 -0
  126. {execsql2-2.1.1 → execsql2-2.2.1}/docs/formatter.md +0 -0
  127. {execsql2-2.1.1 → execsql2-2.2.1}/docs/images/Compare_planets.png +0 -0
  128. {execsql2-2.1.1 → execsql2-2.2.1}/docs/images/actions.png +0 -0
  129. {execsql2-2.1.1 → execsql2-2.2.1}/docs/images/actions2.png +0 -0
  130. {execsql2-2.1.1 → execsql2-2.2.1}/docs/images/checkboxes.png +0 -0
  131. {execsql2-2.1.1 → execsql2-2.2.1}/docs/images/connect.b64 +0 -0
  132. {execsql2-2.1.1 → execsql2-2.2.1}/docs/images/connect.png +0 -0
  133. {execsql2-2.1.1 → execsql2-2.2.1}/docs/images/create_conf.png +0 -0
  134. {execsql2-2.1.1 → execsql2-2.2.1}/docs/images/data_error1_screenshot.jpg +0 -0
  135. {execsql2-2.1.1 → execsql2-2.2.1}/docs/images/entry_form.png +0 -0
  136. {execsql2-2.1.1 → execsql2-2.2.1}/docs/images/execsql_console.png +0 -0
  137. {execsql2-2.1.1 → execsql2-2.2.1}/docs/images/execsql_logo_01.png +0 -0
  138. {execsql2-2.1.1 → execsql2-2.2.1}/docs/images/fatals.png +0 -0
  139. {execsql2-2.1.1 → execsql2-2.2.1}/docs/images/logo_small.png +0 -0
  140. {execsql2-2.1.1 → execsql2-2.2.1}/docs/images/pause_terminal.png +0 -0
  141. {execsql2-2.1.1 → execsql2-2.2.1}/docs/images/pause_terminal_sm.b64 +0 -0
  142. {execsql2-2.1.1 → execsql2-2.2.1}/docs/images/pause_terminal_sm.png +0 -0
  143. {execsql2-2.1.1 → execsql2-2.2.1}/docs/images/prompt_compare.png +0 -0
  144. {execsql2-2.1.1 → execsql2-2.2.1}/docs/images/set_build_commands.jpg +0 -0
  145. {execsql2-2.1.1 → execsql2-2.2.1}/docs/images/unit_conversions.b64 +0 -0
  146. {execsql2-2.1.1 → execsql2-2.2.1}/docs/images/unit_conversions_029.png +0 -0
  147. {execsql2-2.1.1 → execsql2-2.2.1}/docs/images/unmatched.png +0 -0
  148. {execsql2-2.1.1 → execsql2-2.2.1}/docs/images/vim_execsql_highlight.png +0 -0
  149. {execsql2-2.1.1 → execsql2-2.2.1}/docs/installation.md +0 -0
  150. {execsql2-2.1.1 → execsql2-2.2.1}/docs/logging.md +0 -0
  151. {execsql2-2.1.1 → execsql2-2.2.1}/docs/metacommands.md +0 -0
  152. {execsql2-2.1.1 → execsql2-2.2.1}/docs/requirements.md +0 -0
  153. {execsql2-2.1.1 → execsql2-2.2.1}/docs/sql_syntax.md +0 -0
  154. {execsql2-2.1.1 → execsql2-2.2.1}/docs/substitution_vars.md +0 -0
  155. {execsql2-2.1.1 → execsql2-2.2.1}/docs/using_scripts.md +0 -0
  156. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/__init__.py +0 -0
  157. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/__main__.py +0 -0
  158. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/constants.py +0 -0
  159. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/db/__init__.py +0 -0
  160. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/db/factory.py +0 -0
  161. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/exporters/__init__.py +0 -0
  162. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/exporters/zip.py +0 -0
  163. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/format.py +0 -0
  164. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/gui/__init__.py +0 -0
  165. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/gui/base.py +0 -0
  166. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/gui/console.py +0 -0
  167. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/gui/desktop.py +0 -0
  168. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/gui/tui.py +0 -0
  169. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/importers/__init__.py +0 -0
  170. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/metacommands/connect.py +0 -0
  171. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/metacommands/script_ext.py +0 -0
  172. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/models.py +0 -0
  173. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/parser.py +0 -0
  174. /execsql2-2.1.1/tests/__init__.py → /execsql2-2.2.1/src/execsql/py.typed +0 -0
  175. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/state.py +0 -0
  176. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/utils/__init__.py +0 -0
  177. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/utils/auth.py +0 -0
  178. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/utils/crypto.py +0 -0
  179. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/utils/datetime.py +0 -0
  180. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/utils/errors.py +0 -0
  181. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/utils/gui.py +0 -0
  182. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/utils/mail.py +0 -0
  183. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/utils/numeric.py +0 -0
  184. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/utils/regex.py +0 -0
  185. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/utils/strings.py +0 -0
  186. {execsql2-2.1.1 → execsql2-2.2.1}/src/execsql/utils/timer.py +0 -0
  187. {execsql2-2.1.1 → execsql2-2.2.1}/templates/READ_ME.rst +0 -0
  188. {execsql2-2.1.1 → execsql2-2.2.1}/templates/config_settings.sqlite +0 -0
  189. {execsql2-2.1.1 → execsql2-2.2.1}/templates/example_config_prompt.sql +0 -0
  190. {execsql2-2.1.1 → execsql2-2.2.1}/templates/execsql.conf +0 -0
  191. {execsql2-2.1.1 → execsql2-2.2.1}/templates/make_config_db.sql +0 -0
  192. {execsql2-2.1.1 → execsql2-2.2.1}/templates/md_compare.sql +0 -0
  193. {execsql2-2.1.1 → execsql2-2.2.1}/templates/md_glossary.sql +0 -0
  194. {execsql2-2.1.1 → execsql2-2.2.1}/templates/md_upsert.sql +0 -0
  195. {execsql2-2.1.1 → execsql2-2.2.1}/templates/pg_compare.sql +0 -0
  196. {execsql2-2.1.1 → execsql2-2.2.1}/templates/pg_glossary.sql +0 -0
  197. {execsql2-2.1.1 → execsql2-2.2.1}/templates/pg_upsert.sql +0 -0
  198. {execsql2-2.1.1 → execsql2-2.2.1}/templates/script_template.sql +0 -0
  199. {execsql2-2.1.1 → execsql2-2.2.1}/templates/ss_compare.sql +0 -0
  200. {execsql2-2.1.1 → execsql2-2.2.1}/templates/ss_glossary.sql +0 -0
  201. {execsql2-2.1.1 → execsql2-2.2.1}/templates/ss_upsert.sql +0 -0
  202. {execsql2-2.1.1/tests/db → execsql2-2.2.1/tests}/__init__.py +0 -0
  203. {execsql2-2.1.1 → execsql2-2.2.1}/tests/conftest.py +0 -0
  204. {execsql2-2.1.1/tests/exporters → execsql2-2.2.1/tests/db}/__init__.py +0 -0
  205. {execsql2-2.1.1 → execsql2-2.2.1}/tests/db/test_base.py +0 -0
  206. {execsql2-2.1.1 → execsql2-2.2.1}/tests/db/test_duckdb.py +0 -0
  207. {execsql2-2.1.1 → execsql2-2.2.1}/tests/db/test_factory.py +0 -0
  208. {execsql2-2.1.1 → execsql2-2.2.1}/tests/db/test_postgres.py +0 -0
  209. {execsql2-2.1.1 → execsql2-2.2.1}/tests/db/test_sqlite.py +0 -0
  210. {execsql2-2.1.1/tests/gui → execsql2-2.2.1/tests/exporters}/__init__.py +0 -0
  211. {execsql2-2.1.1 → execsql2-2.2.1}/tests/exporters/test_base.py +0 -0
  212. {execsql2-2.1.1 → execsql2-2.2.1}/tests/exporters/test_db.py +0 -0
  213. {execsql2-2.1.1 → execsql2-2.2.1}/tests/exporters/test_delimited.py +0 -0
  214. {execsql2-2.1.1 → execsql2-2.2.1}/tests/exporters/test_duckdb_exporter.py +0 -0
  215. {execsql2-2.1.1 → execsql2-2.2.1}/tests/exporters/test_exporters.py +0 -0
  216. {execsql2-2.1.1 → execsql2-2.2.1}/tests/exporters/test_feather.py +0 -0
  217. {execsql2-2.1.1 → execsql2-2.2.1}/tests/exporters/test_html_latex.py +0 -0
  218. {execsql2-2.1.1 → execsql2-2.2.1}/tests/exporters/test_json.py +0 -0
  219. {execsql2-2.1.1 → execsql2-2.2.1}/tests/exporters/test_ods.py +0 -0
  220. {execsql2-2.1.1 → execsql2-2.2.1}/tests/exporters/test_parquet.py +0 -0
  221. {execsql2-2.1.1 → execsql2-2.2.1}/tests/exporters/test_xls_xlsx.py +0 -0
  222. {execsql2-2.1.1 → execsql2-2.2.1}/tests/exporters/test_xml.py +0 -0
  223. {execsql2-2.1.1 → execsql2-2.2.1}/tests/exporters/test_zip.py +0 -0
  224. {execsql2-2.1.1/tests/importers → execsql2-2.2.1/tests/gui}/__init__.py +0 -0
  225. {execsql2-2.1.1 → execsql2-2.2.1}/tests/gui/test_backends.py +0 -0
  226. {execsql2-2.1.1/tests/metacommands → execsql2-2.2.1/tests/importers}/__init__.py +0 -0
  227. {execsql2-2.1.1 → execsql2-2.2.1}/tests/importers/test_csv_importer.py +0 -0
  228. {execsql2-2.1.1 → execsql2-2.2.1}/tests/importers/test_feather_importer.py +0 -0
  229. {execsql2-2.1.1 → execsql2-2.2.1}/tests/importers/test_ods_importer.py +0 -0
  230. {execsql2-2.1.1 → execsql2-2.2.1}/tests/importers/test_xls_importer.py +0 -0
  231. {execsql2-2.1.1/tests/utils → execsql2-2.2.1/tests/metacommands}/__init__.py +0 -0
  232. {execsql2-2.1.1 → execsql2-2.2.1}/tests/metacommands/test_metacommands_connect.py +0 -0
  233. {execsql2-2.1.1 → execsql2-2.2.1}/tests/metacommands/test_metacommands_script_ext.py +0 -0
  234. {execsql2-2.1.1 → execsql2-2.2.1}/tests/metacommands/test_metacommands_system.py +0 -0
  235. {execsql2-2.1.1 → execsql2-2.2.1}/tests/test_config.py +0 -0
  236. {execsql2-2.1.1 → execsql2-2.2.1}/tests/test_config_data.py +0 -0
  237. {execsql2-2.1.1 → execsql2-2.2.1}/tests/test_constants.py +0 -0
  238. {execsql2-2.1.1 → execsql2-2.2.1}/tests/test_exceptions.py +0 -0
  239. {execsql2-2.1.1 → execsql2-2.2.1}/tests/test_mail.py +0 -0
  240. {execsql2-2.1.1 → execsql2-2.2.1}/tests/test_models.py +0 -0
  241. {execsql2-2.1.1 → execsql2-2.2.1}/tests/test_package.py +0 -0
  242. {execsql2-2.1.1 → execsql2-2.2.1}/tests/test_parser.py +0 -0
  243. {execsql2-2.1.1 → execsql2-2.2.1}/tests/test_script.py +0 -0
  244. {execsql2-2.1.1 → execsql2-2.2.1}/tests/test_state.py +0 -0
  245. {execsql2-2.1.1 → execsql2-2.2.1}/tests/test_types.py +0 -0
  246. {execsql2-2.1.1 → execsql2-2.2.1}/tests/utils/test_auth.py +0 -0
  247. {execsql2-2.1.1 → execsql2-2.2.1}/tests/utils/test_crypto.py +0 -0
  248. {execsql2-2.1.1 → execsql2-2.2.1}/tests/utils/test_datetime.py +0 -0
  249. {execsql2-2.1.1 → execsql2-2.2.1}/tests/utils/test_errors.py +0 -0
  250. {execsql2-2.1.1 → execsql2-2.2.1}/tests/utils/test_fileio.py +0 -0
  251. {execsql2-2.1.1 → execsql2-2.2.1}/tests/utils/test_numeric.py +0 -0
  252. {execsql2-2.1.1 → execsql2-2.2.1}/tests/utils/test_regex.py +0 -0
  253. {execsql2-2.1.1 → execsql2-2.2.1}/tests/utils/test_strings.py +0 -0
  254. {execsql2-2.1.1 → execsql2-2.2.1}/tests/utils/test_timer.py +0 -0
  255. {execsql2-2.1.1 → execsql2-2.2.1}/zensical.toml +0 -0
@@ -7,6 +7,8 @@ ______________________________________________________________________
7
7
  > **Canonical location:** `.claude/project_context.md` in the repo.
8
8
  > Global memory (`~/.claude/projects/.../memory/`) mirrors this — the repo file is the source of truth.
9
9
  > Update this file whenever any architectural, tooling, or directional decision is made.
10
+ > Update the CHANGELOG.md whenever making a release or significant change.
11
+ > Update ROADMAP.md whenever making a release or significant change (if direction changes or if features have already been implemented)
10
12
 
11
13
  ______________________________________________________________________
12
14
 
@@ -78,9 +80,17 @@ src/execsql/ # active codebase — all new work goes here
78
80
  auth.py / crypto.py / datetime.py / errors.py / fileio.py
79
81
  gui.py / mail.py / numeric.py / regex.py / strings.py / timer.py
80
82
  docs/ # 18 MkDocs Markdown pages (Material theme)
83
+ extras/
84
+ vscode-execsql/ # VS Code syntax highlighting extension (repo-only, not in wheel)
85
+ package.json
86
+ syntaxes/
87
+ execsql.tmLanguage.json # auto-generated via `just generate-vscode-grammar`
88
+ scripts/
89
+ generate_vscode_grammar.py # generates tmLanguage.json from --dump-keywords
81
90
  templates/ # SQL templates + config files
82
91
  tests/
83
92
  test_placeholder.py
93
+ test_registry.py # keyword consistency tests (dispatch table ↔ grammar ↔ CLI)
84
94
  .github/workflows/
85
95
  ci-cd.yml # CI/CD pipeline (see below)
86
96
  .pre-commit-config.yaml
@@ -97,18 +107,18 @@ ______________________________________________________________________
97
107
 
98
108
  ## Tooling Decisions
99
109
 
100
- | Tool | Purpose | Decision |
101
- | ------------------- | ------------------------ | ---------------------------------------------------------------------------- |
102
- | `uv` | Package/env management | Chosen over pip/poetry |
103
- | `hatchling` | Build backend | via pyproject.toml |
104
- | `ruff` | Lint + format | Permissive config during migration, tighten during refactor |
105
- | `tox-uv` | Multi-Python test matrix | py310–py313 |
106
- | `bump-my-version` | Version bumping | tags + commits, hooks run `uv lock` |
107
- | `just` | Task runner | `lint`, `test`, `test-all`, `docs`, `docs-serve`, `bump-patch`, `bump-minor` |
108
- | `pre-commit` | Git hooks | gitleaks, uv-lock, ruff, mdformat, markdownlint, typos, validate-pyproject |
109
- | `mkdocs` + Material | Docs site | converted from Sphinx/RST |
110
- | `mkdocstrings` | API docs from docstrings | installed, not yet wired in |
111
- | `pytest-cov` | Coverage | configured, `--cov-fail-under` commented out until tests are written |
110
+ | Tool | Purpose | Decision |
111
+ | ------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------- |
112
+ | `uv` | Package/env management | Chosen over pip/poetry |
113
+ | `hatchling` | Build backend | via pyproject.toml |
114
+ | `ruff` | Lint + format | Permissive config during migration, tighten during refactor |
115
+ | `tox-uv` | Multi-Python test matrix | py310–py313 |
116
+ | `bump-my-version` | Version bumping | tags + commits, hooks run `uv lock` |
117
+ | `just` | Task runner | `lint`, `test`, `test-all`, `docs`, `docs-serve`, `bump-*`, `generate-vscode-grammar`, `install-vscode` |
118
+ | `pre-commit` | Git hooks | gitleaks, uv-lock, ruff, mdformat, markdownlint, typos, validate-pyproject |
119
+ | `mkdocs` + Material | Docs site | converted from Sphinx/RST |
120
+ | `mkdocstrings` | API docs from docstrings | installed, not yet wired in |
121
+ | `pytest-cov` | Coverage | configured, `--cov-fail-under` commented out until tests are written |
112
122
 
113
123
  ## Package Layout Decision
114
124
 
@@ -154,72 +164,97 @@ Triggered on: push to `main`, any tag `v*.*.*`, pull requests.
154
164
 
155
165
  `target-version = "py313"`, `line-length = 120`. Currently permissive — many legacy rules ignored (tabs, bare except, ambiguous names, unused imports, etc.). Intent is to tighten rules progressively during refactor.
156
166
 
157
- ## Known Issues / Docs Debt
167
+ ## Keyword Registry & VS Code Extension
158
168
 
159
- *(Docs cleanup completed anchor IDs fixed, RST artifacts resolved.)*
169
+ The dispatch table is the single source of truth for all metacommand keywords. Every `mcl.add()` call in `metacommands/__init__.py` has `description=` (keyword name) and `category=` (one of: `control`, `block`, `action`, `config`, `config_option`, `prompt`). Conditional functions in `conditions.py` use `category="condition"`.
160
170
 
161
- ## Roadmap / Open Work
171
+ - `execsql --dump-keywords` introspects the dispatch table at runtime and outputs structured JSON
172
+ - `scripts/generate_vscode_grammar.py` consumes that JSON to produce `extras/vscode-execsql/syntaxes/execsql.tmLanguage.json`
173
+ - `tests/test_registry.py` validates keyword consistency across dispatch table, CLI output, and grammar
174
+ - Export format constants (`QUERY_EXPORT_FORMATS`, `TABLE_EXPORT_FORMATS`, etc.) are centralized in `metacommands/__init__.py`
175
+ - The VS Code extension lives at `extras/vscode-execsql/` — it is NOT included in the Python wheel (it's an editor extension, not a library). Install via `just install-vscode` (symlinks into `~/.vscode/extensions/`).
162
176
 
163
- Items are ordered by recommended priority. Each has a status and a brief
164
- rationale so the right one can be picked up without re-investigating context.
177
+ **When adding a new metacommand:** add `description=` and `category=` to the `mcl.add()` call, then run `just generate-vscode-grammar`.
165
178
 
166
179
  ______________________________________________________________________
167
180
 
168
- ### 1. Orphaned optional features ✅ *completed 2026-03-23*
181
+ ## Known Issues / Docs Debt
169
182
 
170
- - **Airspeed removed:** `AirspeedTemplateReport` and `FORMAT airspeed` deleted from
171
- `exporters/templates.py`; `airspeed` removed from valid `template_processor` config values.
172
- Noted in `CHANGELOG.md` under `[Unreleased]`.
173
- - **`feather = ["pandas", "pyarrow"]`** added to `pyproject.toml` optional-deps and `all` group.
174
- - **`hdf5 = ["tables"]`** added to `pyproject.toml` optional-deps and `all` group.
183
+ *(Docs cleanup completed anchor IDs fixed, RST artifacts resolved.)*
175
184
 
176
- ______________________________________________________________________
185
+ ## Roadmap
177
186
 
178
- ### 2. Wire `mkdocstrings` into the docs *(low effort, high payoff)*
187
+ Milestones are v2.x minor releases. Python 3.10 support is maintained for
188
+ the foreseeable future. See also `ROADMAP.md` in the repo root.
179
189
 
180
- `mkdocstrings-python` is installed as a dev dep but no `:::` autodoc directives
181
- exist in any docs page. The modular refactor means every public class and
182
- function now has a docstring. Adding API reference pages would give users
183
- discoverable, always-up-to-date docs from zero extra writing.
190
+ ### Completed
184
191
 
185
- **Recommended action:** Add an `api/` section to the MkDocs nav with at least
186
- one page per top-level module group (`db/`, `exporters/`, `importers/`,
187
- `metacommands/`). Start with the most user-facing ones (`db/base.py`,
188
- `exporters/`, `cli.py`).
192
+ | Item | Version | Date |
193
+ | ------------------------------------------------- | ------------- | ------- |
194
+ | Monolith modular refactor (80+ files) | 2.0.0a1–2.1.0 | 2026-03 |
195
+ | 30+ security/correctness fixes (ANALYSIS.md) | 2.1.x | 2026-03 |
196
+ | mkdocstrings API docs wired up | 2.1.0 | 2026-03 |
197
+ | `execsql-format` surfaced in README + docs nav | 2.1.0 | 2026-03 |
198
+ | Coverage floor raised to 70% (2,055 tests) | 2.1.2 | 2026-03 |
199
+ | Orphaned optional features cleaned up | 2.1.0 | 2026-03 |
200
+ | Keyring auth, Parquet/Feather/HDF5 export, DuckDB | 2.1.0 | 2026-03 |
201
+ | Progress bars, SQL audit logging | unreleased | 2026-03 |
202
+ | Full monolith parity verified | unreleased | 2026-03 |
189
203
 
190
204
  ______________________________________________________________________
191
205
 
192
- ### 3. Surface `execsql-format` in the README and docs nav ✅ *already complete*
206
+ ### v2.2 Stability & Testing
207
+
208
+ Theme: Harden what exists before adding features.
193
209
 
194
- - README has a "Formatting Scripts" section (lines 95–107) with usage examples and a docs link.
195
- - `zensical.toml` nav lists `{ "execsql-format" = "formatter.md" }` under Guides.
196
- - Completed in commit `a25d1f4`.
210
+ - **SQLite integration tests** full lifecycle (connect, DDL, CRUD, export/import, disconnect)
211
+ - **End-to-end CLI tests** run scripts through the actual CLI entry point
212
+ - **Exception chaining** add `from e` to ~80 `except Exception: raise ErrInfo(...)` patterns
213
+ - **Exception hierarchy** — make `ErrInfo`, `DataTypeError`, `DbTypeError`, `DatabaseNotImplementedError` inherit from `ExecSqlError`
214
+ - **`py.typed` marker** — add `src/execsql/py.typed` for downstream type checking
215
+ - **README accuracy** — remove "not yet stable" warning, update feature list
216
+ - **Raise coverage floor** — target 75%
197
217
 
198
218
  ______________________________________________________________________
199
219
 
200
- ### 4. Raise the coverage floor *(follows test work)* ✅ *floor at 68% as of 2026-03-24*
220
+ ### v2.3 Code Quality & Documentation
201
221
 
202
- Coverage is at 68.18% as of 2026-03-24 with the floor at 68% (`--cov-fail-under=68`). The next
203
- natural targets to push further:
222
+ Theme: Make the codebase welcoming to contributors and transparent to users.
204
223
 
205
- - `importers/{ods,xls,feather}`same pattern as the exporter tests already written
206
- - `metacommands/` handlers not yet exercised end-to-end (EXPORT SQLITE/DUCKDB via CLI)
207
- - `db/duckdb.py` deeper methods (mirrors what was done for `db/sqlite.py`)
224
+ - **Docstring coverage** target 50%+ on public API (focus on `db/`, `exporters/`, `script.py`, `config.py`)
225
+ - **CONTRIBUTING.md** dev setup, architecture overview, PR workflow, testing expectations
226
+ - **Security documentation** document trust model and security boundaries (SHELL, path traversal, SMTP, passwords)
227
+ - **Ruff tightening** — enable E722 (bare except), E721 (type comparison), remove legacy suppressions
228
+ - **`__all__` exports** — add to all public modules
229
+ - **Remove lazy import anti-pattern** — replace ~30 `global module; import module` with top-level or proper lazy imports
208
230
 
209
- **Recommended action:** Write the importer tests first (straightforward),
210
- then raise `--cov-fail-under` from 68 → 70+.
231
+ ______________________________________________________________________
232
+
233
+ ### v2.4 — Architecture & Performance
234
+
235
+ Theme: Internal modernization. May include backward-incompatible internal API changes (public CLI behavior preserved).
236
+
237
+ - **Split large modules** — `script.py` (1,157 lines) and `metacommands/__init__.py` (1,606 lines)
238
+ - **Database ABC with `@abstractmethod`** — replace runtime `DatabaseNotImplementedError` with true abstract methods
239
+ - **Cursor context managers** — explicit cursor lifecycle in `execute()`, `select_data()`, `select_rowsource()`
240
+ - **Metacommand dispatch optimization** — consider dict/trie lookup instead of O(N) regex scan
241
+ - **Variable substitution optimization** — reduce O(V×D) complexity
242
+ - **Exporter protocol** — define a `Protocol` or ABC for exporters with a common interface
211
243
 
212
244
  ______________________________________________________________________
213
245
 
214
- ### 5. Ruff tightening *(ongoing, lowest urgency)*
246
+ ### v2.5+ Future
247
+
248
+ - **Plugin system** — allow external packages to register exporters, importers, or metacommands
249
+
250
+ ______________________________________________________________________
215
251
 
216
- Progressive modernization: enable stricter Ruff rules, remove Py2 compat
217
- remnants (`type(x) == y` → `isinstance`, bare `except:` → `except Exception:`,
218
- etc.), modernize idioms. No user-facing change.
252
+ ### Ongoing / No-milestone
219
253
 
220
- **Recommended action:** Work rule-by-rule. Start by removing the most
221
- egregious suppressions in `pyproject.toml` (`E722` bare-except, `E721`
222
- type-comparison). Fix each batch of violations before enabling the next rule.
254
+ - PostgreSQL integration tests (requires external server CI docker service)
255
+ - MySQL integration tests (same)
256
+ - `savedscripts` memory pruning
257
+ - Textual TUI polish
223
258
 
224
259
  ______________________________________________________________________
225
260
 
@@ -50,6 +50,7 @@ jobs:
50
50
  python -m pip install --upgrade pip
51
51
  python -m pip install tox
52
52
  python -m pip install ".[dev]"
53
+ python -m pip install ".[formats]"
53
54
  - name: Install tkinter (Ubuntu)
54
55
  if: runner.os == 'Linux'
55
56
  run: sudo apt-get install -y python3-tk
@@ -31,7 +31,12 @@ coverage.xml
31
31
  .tox/
32
32
  htmlcov/
33
33
  execsql.log
34
- scripts/
34
+ scripts/execsql.conf
35
+ scripts/*.csv
36
+ scripts/test_import_sqlite.sql
37
+ scripts/test_script.sql
38
+ scripts/test.db
39
+ scripts/*.log
35
40
 
36
41
  # Distribution
37
42
  *.whl
@@ -18,7 +18,7 @@ repos:
18
18
  - id: gitleaks
19
19
  # uv lockfile management
20
20
  - repo: https://github.com/astral-sh/uv-pre-commit
21
- rev: 0.10.12
21
+ rev: 0.11.1
22
22
  hooks:
23
23
  - id: uv-lock
24
24
  # Base hooks
@@ -47,6 +47,7 @@ repos:
47
47
  - mdformat-mkdocs==5.1.1
48
48
  exclude: |
49
49
  (?x)^(
50
+ docs/api/.*|
50
51
  docs/metacommands\.md|
51
52
  docs/documentation\.md|
52
53
  docs/examples\.md
@@ -9,7 +9,8 @@ build:
9
9
  python: "3.13"
10
10
  jobs:
11
11
  install:
12
- - pip install zensical
12
+ - pip install zensical mkdocstrings-python
13
+ - pip install -e .
13
14
  pre_build:
14
15
  - cp CHANGELOG.md docs/change_log.md
15
16
  build:
@@ -13,6 +13,134 @@ ______________________________________________________________________
13
13
 
14
14
  ______________________________________________________________________
15
15
 
16
+ ## [2.2.1] - 2026-03-26
17
+
18
+ ### Fixed
19
+
20
+ - Skip `TimerHandler` alarm tests on Windows where `signal.setitimer` is unavailable.
21
+ - Fix `UnicodeDecodeError` in CLI subprocess tests on Windows by specifying UTF-8 encoding.
22
+
23
+ ______________________________________________________________________
24
+
25
+ ## [2.2.0] - 2026-03-26
26
+
27
+ ### Added
28
+
29
+ - `py.typed` marker for PEP 561 downstream type checking.
30
+ - 252 new tests (2,062 → 2,314) covering metacommands (`data`, `system`, `io_fileops`, `io_write`), utils (`auth`, `errors`, `fileio`, `timer`), and SQLite integration tests for JSON/HTML/LaTeX/TSV exports, DDL operations, WRITE-to-file, CONFIG metacommand, error handling, and inline commands; end-to-end CLI tests for `-c`, `--dsn sqlite://`, `-a` substitution, `--dry-run`, `--dump-keywords`, and `--version`. Coverage floor raised from 70% to 75%.
31
+ - `ods` optional-dependency extra in `pyproject.toml` (`pip install execsql2[ods]`).
32
+ - Keyring credential storage documentation in usage notes.
33
+ - `--progress` CLI flag and `CONFIG SHOW_PROGRESS` metacommand to display a rich progress bar during long-running IMPORT operations. Also configurable via `show_progress` in `execsql.conf`. (FEAT-5)
34
+ - Opt-in SQL query audit logging via `log_sql` config option and `CONFIG LOG_SQL` metacommand. When enabled, all executed SQL statements are written to the log file with a `sql` record type, database name, line number, and query text. (FEAT-6)
35
+ - `--dump-keywords` CLI option: outputs all metacommand keywords, conditional functions, config options, export formats, database types, and variable patterns as structured JSON. Enables tooling (e.g., editor grammar generators) to consume keyword data directly from the dispatch table.
36
+ - VS Code syntax highlighting extension colocated at `extras/vscode-execsql/`. The grammar (`execsql.tmLanguage.json`) is auto-generated from the dispatch table via `just generate-vscode-grammar`.
37
+ - Keyword registry: `MetaCommand` and `MetaCommandList` now support `category` parameter. All `mcl.add()` calls are tagged with `description=` and `category=`, making the dispatch table the single source of truth for keyword metadata.
38
+ - Export format constants (`QUERY_EXPORT_FORMATS`, `TABLE_EXPORT_FORMATS`, `SERVE_FORMATS`, etc.) centralized in `metacommands/__init__.py` and used in dispatch table regex construction.
39
+ - `tests/test_registry.py`: keyword consistency tests validating `--dump-keywords` output, dispatch table categories, conditional table coverage, export format constants, and grammar synchronization.
40
+
41
+ ### Changed
42
+
43
+ - CLI reorganized from flat `_cli_*.py` files into `cli/` subpackage (`cli/__init__.py`, `cli/run.py`, `cli/dsn.py`, `cli/help.py`). All existing `from execsql.cli import ...` paths preserved.
44
+ - Exception hierarchy: `ErrInfo`, `DataTypeError`, `DbTypeError`, and `DatabaseNotImplementedError` now inherit from `ExecSqlError` base class instead of bare `Exception`.
45
+ - Exception chaining: added `from e` to 115 `raise` statements across 38 files that previously discarded the original exception context.
46
+ - README: replaced "not yet stable" warning with "maintained fork" note reflecting current project maturity.
47
+ - Split `cli.py` (1245 lines) into `_cli_help.py`, `_cli_dsn.py`, and `_cli_run.py` with `cli.py` as a re-export façade. All existing import paths preserved. (REFAC-3)
48
+ - Split `metacommands/io.py` (1304 lines) into `io_export.py`, `io_import.py`, `io_write.py`, and `io_fileops.py` with `io.py` as a re-export façade. All existing import paths preserved. (REFAC-4)
49
+
50
+ ### Fixed
51
+
52
+ - `of` module typo throughout `exporters/ods.py`: all imports and references used `of` (e.g., `import of as of`, `of.table.Table`) instead of `of` (e.g., `import of as of`, `of.table.Table`). ODS export/import was completely broken when `odfpy` was installed. Same typo fixed in `tests/exporters/test_ods.py` and `tests/importers/test_ods_importer.py`.
53
+
54
+ - Unclosed `ScriptFile` in `script.py`: `read_sqlfile()` iterated through the file but never closed it, leaking a file handle on every script load.
55
+
56
+ - Unclosed CSV file handle in `exporters/delimited.py`: `evaluate_line_format()` opened a file for delimiter diagnosis but never closed it. `reader()` generator also leaked the file handle if not fully consumed or if an exception occurred mid-iteration. Both now use `try/finally`.
57
+
58
+ - Unclosed `sqlite3.Connection` in test assertions: `with sqlite3.connect(...)` only commits/rolls back but does not close the connection. Fixed in `tests/exporters/test_sqlite_exporter.py` and `tests/metacommands/test_metacommands.py`.
59
+
60
+ - File handle leaks in exporters: wrapped write operations in `try/finally` blocks to guarantee file handles are closed on error in `exporters/raw.py`, `exporters/xml.py`, `exporters/json.py`, `exporters/templates.py`, `exporters/values.py`, `exporters/pretty.py`, `exporters/html.py`, `exporters/latex.py`, `metacommands/debug.py`, `metacommands/control.py`, and `metacommands/prompt.py`. Previously, an exception during export could leak open file handles.
61
+
62
+ - XML export injection: `exporters/xml.py` now escapes `<`, `>`, `&` in cell values using `xml.sax.saxutils.escape()` and sanitizes `--` in XML comments. Previously, data containing XML special characters produced malformed XML output.
63
+
64
+ - HTML export XSS: `exporters/html.py` now escapes column headers and cell values using `html.escape()`. Previously, data containing `<script>` or other HTML markup was written directly to the output file.
65
+
66
+ - JSON export injection: `exporters/json.py` now uses `json.dumps()` to properly escape the `description` field in JSON-TS exports. Previously, descriptions containing quotes or backslashes produced malformed JSON. Also added missing `import json` to `write_query_to_json_ts` which previously relied on a global set by `write_query_to_json`.
67
+
68
+ - `JinjaTemplateReport.__repr__` incorrectly returned `StrTemplateReport(...)` instead of `JinjaTemplateReport(...)`.
69
+
70
+ - `exporters/templates.py`: removed bare `except: raise` clause in `JinjaTemplateReport.write_report()` and added proper exception chaining (`from e`) to Jinja2 template errors.
71
+
72
+ - `exporters/delimited.py`: `write_delimited_file()` now closes the output file handle via `try/finally`. Previously, the file was opened but never closed, leaking the handle on both success and error paths.
73
+
74
+ - SQL injection in remaining database metadata queries: `role_exists()` in `db/mysql.py`, `db/sqlserver.py`, and `db/firebird.py`, `schema_exists()` in `db/sqlserver.py`, `table_exists()` and `view_exists()` in `db/access.py` and `db/firebird.py` now use parameterized queries instead of f-string interpolation. `column_exists()` and `table_columns()` in `db/access.py` and `db/firebird.py` now use `quote_identifier()` for SQL identifiers that cannot be parameterized.
75
+
76
+ - SQL injection in `db/postgres.py`: `create_db()` now uses `quote_identifier()` for database name and encoding in `CREATE DATABASE` DDL. COPY command delimiter and quote character are now escaped/validated to prevent injection.
77
+
78
+ - CPU busy-loop in `utils/fileio.py`: `FileWriter.run()` now uses blocking `queue.get(timeout=0.1)` instead of `get_nowait()` in a tight loop, eliminating unnecessary CPU consumption when the file writer subprocess is idle.
79
+
80
+ - Substitution variable cycle detection in `script.py`: `substitute_vars()` now enforces a maximum of 100 iterations to prevent infinite loops when variables reference each other cyclically (e.g., `$A` expands to `!!$B!!` and `$B` expands to `!!$A!!`).
81
+
82
+ - Jinja2 template injection: `exporters/templates.py` now uses `jinja2.sandbox.SandboxedEnvironment` instead of the default `jinja2.Template` constructor. Previously, a malicious template file could access Python internals and execute arbitrary code.
83
+
84
+ - SQL injection in `import_entire_file()` across 6 database backends (`db/base.py`, `db/dsn.py`, `db/sqlite.py`, `db/sqlserver.py`, `db/postgres.py`, `db/access.py`): the `column_name` parameter is now quoted with `quote_identifier()` instead of being interpolated directly into the INSERT statement.
85
+
86
+ - XML export malformed output: `exporters/xml.py` now sanitizes column headers and table names used as XML element names, replacing invalid XML name characters with underscores. Previously, column names containing `>`, `<`, or other XML metacharacters produced malformed XML.
87
+
88
+ - `$SHEETS_TABLES_VALUES` SQL injection in `metacommands/io_import.py`: sheet names from ODS/XLS imports are now escaped (single quotes doubled) before embedding in SQL value expressions. Previously, a sheet name containing a single quote produced malformed or injectable SQL.
89
+
90
+ - HTTP header injection in SERVE metacommand (`metacommands/io_fileops.py`): the `Content-Disposition` filename is now sanitized (newlines/carriage returns stripped, quotes escaped) to prevent HTTP response splitting.
91
+
92
+ - Empty `dt_cast` type-cast mapping in `Database` base class (`db/base.py`): the monolith populated this dict with 8 type converters (int, float, str, bool, datetime, date, Decimal, bytearray) but the refactored version was initialized to `{}`. Now uses a lazy property that auto-populates on first access, ensuring all backends get the correct type casters even though none call `super().__init__()`.
93
+
94
+ - `WriteSpec.write()` file descriptor leak (`exporters/base.py`): the one-liner `EncodedFile(...).open("a").write(msg)` opened a file, wrote, and discarded the handle without closing. Now properly closes the handle in a try/finally block.
95
+
96
+ - Removed duplicate `x_halt_msg` function from `metacommands/prompt.py`. The canonical version in `metacommands/control.py` (used by the dispatch table) was the correct one; the prompt.py copy was dead code.
97
+
98
+ - `read_sqlfile()` double-open file leak (`script.py`): `ScriptFile.__init__` already opens the file, but `read_sqlfile()` called `.open("r")` again, creating a second leaked handle. Now uses the ScriptFile directly.
99
+
100
+ - Missing WRITE metacommand delimiter patterns in dispatch table (`metacommands/__init__.py`): added 5 missing delimiter variants (tilde, hash, backtick, bracket, single-quote) for WRITE and ON ERROR_HALT/ON CANCEL_HALT WRITE metacommands that were present in the monolith but missing from the refactored dispatch table.
101
+
102
+ - Missing bare (non-CONFIG) settings aliases in dispatch table (`metacommands/__init__.py`): added 10 bare settings forms (e.g., `FEEDBACK ON` without the `CONFIG` prefix) that the monolith supported but the refactored dispatch table omitted.
103
+
104
+ - `CONNECT TO DSN` metacommand unreachable (`metacommands/__init__.py`): the `x_connect_dsn` handler was imported but never registered with `mcl.add()`. DSN-based connections via metacommand were completely broken.
105
+
106
+ - `PARQUET` missing from EXPORT format regex lists (`metacommands/__init__.py`): the export handler code supported Parquet output but the dispatch table regex didn't include `PARQUET` as a valid format, making `EXPORT ... AS PARQUET` unreachable. Added to both EXPORT QUERY and EXPORT table format lists.
107
+
108
+ - Firebird error message typo (`db/firebird.py`): error message said "required to connect to MySQL" instead of "required to connect to Firebird".
109
+
110
+ - Oracle default port in function signature (`db/oracle.py`): default port parameter was `5432` (PostgreSQL's port) instead of `1521` (Oracle's port). The body already corrected to 1521 but the signature was misleading.
111
+
112
+ - Missing documentation for `show_progress` and `log_sql` config options in `docs/configuration.md`.
113
+
114
+ - Backward compatibility for `TXT-AND` export format (`metacommands/__init__.py`, `metacommands/io_export.py`): the monolith used `TXT-AND`/`TEXT-AND` but the refactored code renamed it to `TXT-AND`/`TEXT-AND`. Added `TXT-AND` as a backward-compatible alias so existing scripts continue to work.
115
+
116
+ - `docs/api/db.md` missing individual database adapter documentation — added all 9 adapters (postgres, sqlite, duckdb, sqlserver, mysql, oracle, firebird, access, dsn).
117
+
118
+ - `docs/api/exporters.md` missing `latex` and `zip` module references — added both.
119
+
120
+ ______________________________________________________________________
121
+
122
+ ## [2.1.2] - 2026-03-25
123
+
124
+ ### Added
125
+
126
+ - DuckDB integration tests: 15 end-to-end tests (`tests/test_integration_duckdb.py`) covering basic SQL, substitution variables, CSV export/import, conditional execution, WRITE metacommand, round-trip, and DuckDB-specific features (views, schemas, native types).
127
+
128
+ ### Changed
129
+
130
+ - Added PyPI version, Python versions, license, and Read the Docs badges to `README.md`.
131
+
132
+ ### Fixed
133
+
134
+ - Config parser now accepts `k` (DuckDB) as a valid `db_type` in `execsql.conf`. Previously only the CLI flag `-t k` worked; config file validation rejected it.
135
+
136
+ - Read the Docs build: added `mkdocstrings-python` and editable project install to `.readthedocs.yaml` so `mkdocstrings` can resolve API references.
137
+
138
+ - Fixed escaped underscore in `docs/api/cli.md` (`\_run` → `_run`) that caused `mkdocstrings` to fail resolving `execsql.cli._run`.
139
+
140
+ - Excluded `docs/api/` from `mdformat` pre-commit hook to prevent it from re-escaping underscores in `mkdocstrings` `:::` directives.
141
+
142
+ ______________________________________________________________________
143
+
16
144
  ## [2.1.1] - 2026-03-25
17
145
 
18
146
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: execsql2
3
- Version: 2.1.1
3
+ Version: 2.2.1
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
@@ -98,14 +98,15 @@ Provides-Extra: postgres
98
98
  Requires-Dist: psycopg2-binary; extra == 'postgres'
99
99
  Description-Content-Type: text/markdown
100
100
 
101
- > [!WARNING]
102
- > **This project is in active development and is not yet stable.**
103
- > The codebase is being modernized from the upstream monolith into a modular package.
104
- > APIs, CLI behavior, and configuration options may change without notice.
105
- > Not recommended for production use. For the stable upstream release, see [execsql](https://execsql.readthedocs.io/).
101
+ > [!NOTE]
102
+ > **This is a maintained fork of [execsql](https://execsql.readthedocs.io/).**
103
+ > The original monolith has been fully refactored into a modular package with 2,000+ tests.
104
+ > The CLI and configuration are backwards-compatible with upstream v1.130.1.
105
+ > Report issues at [github.com/geocoug/execsql/issues](https://github.com/geocoug/execsql/issues).
106
106
 
107
107
  <div align="center">
108
- ![execsql logo](https://execsql2.readthedocs.io/en/latest/images/execsql_logo_01.png)
108
+
109
+ <img src="https://execsql2.readthedocs.io/en/latest/images/execsql_logo_01.png" alt="execsql logo">
109
110
 
110
111
  *Multi-DBMS SQL script processor.*
111
112
 
@@ -115,6 +116,10 @@ Description-Content-Type: text/markdown
115
116
 
116
117
  [![CI/CD](https://github.com/geocoug/execsql/actions/workflows/ci-cd.yml/badge.svg)](https://github.com/geocoug/execsql/actions/workflows/ci-cd.yml)
117
118
  [![codecov](https://codecov.io/gh/geocoug/execsql/graph/badge.svg)](https://codecov.io/gh/geocoug/execsql)
119
+ [![Docs](https://readthedocs.org/projects/execsql2/badge/)](https://execsql2.readthedocs.io/)
120
+ [![PyPI](https://img.shields.io/pypi/v/execsql2)](https://pypi.org/project/execsql2/)
121
+ [![Python](https://img.shields.io/pypi/pyversions/execsql2)](https://pypi.org/project/execsql2/)
122
+ [![License](https://img.shields.io/pypi/l/execsql2)](https://pypi.org/project/execsql2/)
118
123
  [![Downloads](https://pepy.tech/badge/execsql2/month)](https://pepy.tech/project/execsql2)
119
124
 
120
125
  </div>
@@ -1,11 +1,12 @@
1
- > [!WARNING]
2
- > **This project is in active development and is not yet stable.**
3
- > The codebase is being modernized from the upstream monolith into a modular package.
4
- > APIs, CLI behavior, and configuration options may change without notice.
5
- > Not recommended for production use. For the stable upstream release, see [execsql](https://execsql.readthedocs.io/).
1
+ > [!NOTE]
2
+ > **This is a maintained fork of [execsql](https://execsql.readthedocs.io/).**
3
+ > The original monolith has been fully refactored into a modular package with 2,000+ tests.
4
+ > The CLI and configuration are backwards-compatible with upstream v1.130.1.
5
+ > Report issues at [github.com/geocoug/execsql/issues](https://github.com/geocoug/execsql/issues).
6
6
 
7
7
  <div align="center">
8
- ![execsql logo](https://execsql2.readthedocs.io/en/latest/images/execsql_logo_01.png)
8
+
9
+ <img src="https://execsql2.readthedocs.io/en/latest/images/execsql_logo_01.png" alt="execsql logo">
9
10
 
10
11
  *Multi-DBMS SQL script processor.*
11
12
 
@@ -15,6 +16,10 @@
15
16
 
16
17
  [![CI/CD](https://github.com/geocoug/execsql/actions/workflows/ci-cd.yml/badge.svg)](https://github.com/geocoug/execsql/actions/workflows/ci-cd.yml)
17
18
  [![codecov](https://codecov.io/gh/geocoug/execsql/graph/badge.svg)](https://codecov.io/gh/geocoug/execsql)
19
+ [![Docs](https://readthedocs.org/projects/execsql2/badge/)](https://execsql2.readthedocs.io/)
20
+ [![PyPI](https://img.shields.io/pypi/v/execsql2)](https://pypi.org/project/execsql2/)
21
+ [![Python](https://img.shields.io/pypi/pyversions/execsql2)](https://pypi.org/project/execsql2/)
22
+ [![License](https://img.shields.io/pypi/l/execsql2)](https://pypi.org/project/execsql2/)
18
23
  [![Downloads](https://pepy.tech/badge/execsql2/month)](https://pepy.tech/project/execsql2)
19
24
 
20
25
  </div>
@@ -16,6 +16,6 @@ members: false
16
16
 
17
17
  ## Core execution
18
18
 
19
- ::: execsql.cli.\_run
19
+ ::: execsql.cli._run
20
20
  options:
21
21
  show_signature_annotations: false
@@ -13,3 +13,23 @@ If you are adding support for a new database, start with the [Adding Database Ad
13
13
  Convenience constructors used internally to create typed `Database` instances. The CLI calls the appropriate factory function based on the `-t` flag value. Each factory validates its arguments (e.g., checking that a file exists) before constructing and returning the adapter.
14
14
 
15
15
  ::: execsql.db.factory
16
+
17
+ ## Adapters
18
+
19
+ ::: execsql.db.postgres
20
+
21
+ ::: execsql.db.sqlite
22
+
23
+ ::: execsql.db.duckdb
24
+
25
+ ::: execsql.db.sqlserver
26
+
27
+ ::: execsql.db.mysql
28
+
29
+ ::: execsql.db.oracle
30
+
31
+ ::: execsql.db.firebird
32
+
33
+ ::: execsql.db.access
34
+
35
+ ::: execsql.db.dsn
@@ -26,6 +26,8 @@ Each module below implements one or more output formats. Every writer follows th
26
26
 
27
27
  ::: execsql.exporters.templates
28
28
 
29
+ ::: execsql.exporters.latex
30
+
29
31
  ::: execsql.exporters.ods
30
32
 
31
33
  ::: execsql.exporters.xls
@@ -37,3 +39,5 @@ Each module below implements one or more output formats. Every writer follows th
37
39
  ::: execsql.exporters.sqlite
38
40
 
39
41
  ::: execsql.exporters.duckdb
42
+
43
+ ::: execsql.exporters.zip
@@ -152,6 +152,10 @@ The section and property names that may be used in a configuration file are list
152
152
 
153
153
  : Replaces newline characters that are in text values on [IMPORT](metacommands.md#import). Every sequence of a newline and any surrounding whitespace is replaced with a single space character.
154
154
 
155
+ `show_progress`
156
+
157
+ : Whether or not to display a rich progress bar during long-running IMPORT operations. The property value should be either "Yes" or "No". The default is "No". This can also be enabled via the `--progress` CLI flag or the `CONFIG SHOW_PROGRESS` metacommand. Requires the `rich` Python package.
158
+
155
159
  `scan_lines` { #scan_lines }
156
160
 
157
161
  : The number of lines of a data file to scan to determine the quoting character and delimiter character used. This is equivalent to the `-s` command-line option. The default value is 100.
@@ -306,6 +310,9 @@ The section and property names that may be used in a configuration file are list
306
310
  `linux_config_file`
307
311
  : The full name or path to an additional configuration file to be read if *execsql* is running on Linux. If only a path is specified, the name of the configuration file should be `execsql.conf`. The configuration file specified will be read immediately following the configuration file in which it is named. No configuration file will be read more than once. If the name or path are invalid, this setting will be silently ignored. This setting may include [substitution variables](substitution_vars.md#substitution_vars); at the time that configuration files are read, however, only environment variables and system variables related to the script name and path are defined.
308
312
 
313
+ `log_sql` { #log_sql }
314
+ : When set to "Yes", all executed SQL statements are written to the log file with a `sql` record type, including the database name, line number, and query text. The property value should be either "Yes" or "No". The default is "No". This can also be enabled via the `CONFIG LOG_SQL` metacommand.
315
+
309
316
  `max_log_size_mb` { #max_log_size_mb }
310
317
  : Maximum size of the log file in megabytes before it is rotated. When set to a positive integer, the log file is rotated to `.1` before a new run appends to it if the file size exceeds the configured threshold. The default is `0` (disabled — no rotation).
311
318
 
@@ -89,19 +89,22 @@ Open `src/execsql/metacommands/__init__.py` and add a `mcl.add()` call inside `b
89
89
  mcl.add(
90
90
  r"^\s*MY_COMMAND\s+(?P<target>\w+)\s+(?P<value>.+)$",
91
91
  x_my_command,
92
+ description="MY_COMMAND",
93
+ category="action",
92
94
  )
93
95
  ```
94
96
 
95
97
  `MetaCommandList.add()` accepts these parameters:
96
98
 
97
- | Parameter | Type | Default | Purpose |
98
- | ------------------ | -------------------------- | -------- | -------------------------------------------------------------------------------------- |
99
- | `matching_regexes` | `str` or `tuple[str, ...]` | required | One regex string, or a tuple of strings all mapped to the same handler |
100
- | `exec_func` | callable | required | The handler function |
101
- | `description` | `str \| None` | `None` | Human-readable label (shown in `DEBUG WRITE METACOMMANDLIST`) |
102
- | `run_in_batch` | `bool` | `False` | Allow execution inside an open `BEGIN_BATCH`/`END_BATCH` block |
103
- | `run_when_false` | `bool` | `False` | Execute even when the `IF`-stack condition is false (needed for `ELSE`, `ENDIF`, etc.) |
104
- | `set_error_flag` | `bool` | `True` | Update `_state.status.metacommand_error` on success/failure |
99
+ | Parameter | Type | Default | Purpose |
100
+ | ------------------ | -------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------- |
101
+ | `matching_regexes` | `str` or `tuple[str, ...]` | required | One regex string, or a tuple of strings all mapped to the same handler |
102
+ | `exec_func` | callable | required | The handler function |
103
+ | `description` | `str \| None` | `None` | Human-readable keyword name (shown in `DEBUG WRITE METACOMMANDLIST` and `--dump-keywords`) |
104
+ | `run_in_batch` | `bool` | `False` | Allow execution inside an open `BEGIN_BATCH`/`END_BATCH` block |
105
+ | `run_when_false` | `bool` | `False` | Execute even when the `IF`-stack condition is false (needed for `ELSE`, `ENDIF`, etc.) |
106
+ | `set_error_flag` | `bool` | `True` | Update `_state.status.metacommand_error` on success/failure |
107
+ | `category` | `str \| None` | `None` | Keyword category for `--dump-keywords` and VS Code grammar generation (e.g., `"action"`, `"control"`, `"config"`) |
105
108
 
106
109
  All regexes are compiled with `re.I` (case-insensitive) automatically.
107
110
 
@@ -209,9 +212,10 @@ ______________________________________________________________________
209
212
 
210
213
  - [ ] Handler function added to the appropriate `src/execsql/metacommands/*.py` module
211
214
  - [ ] Handler imported in `src/execsql/metacommands/__init__.py`
212
- - [ ] `mcl.add(...)` call added in `build_dispatch_table()` at the right position
215
+ - [ ] `mcl.add(...)` call added in `build_dispatch_table()` with `description=` and `category=`
216
+ - [ ] Run `just generate-vscode-grammar` to update the VS Code grammar
213
217
  - [ ] Integration test added to `tests/test_metacommands.py` (or relevant file)
214
- - [ ] `pytest` passes locally
218
+ - [ ] `pytest` passes locally (including `tests/test_registry.py` keyword consistency checks)
215
219
 
216
220
  ______________________________________________________________________
217
221
 
@@ -8,17 +8,17 @@
8
8
 
9
9
  You can use *execsql* to:
10
10
 
11
- > - Import data from text files and spreadsheets into a database.
12
- > - Export tables and views to text files, OpenDocument spreadsheets, HTML, JSON, LaTeΧ, XML, or 17 other data formats.
13
- > - Copy data between different databases, even databases using different DBMSs.
14
- > - Display tables or views on the terminal or in a GUI dialog window.
15
- > - Export data using template processors to produce non-tabular output with customized format and contents.
16
- > - Conditionally execute different SQL commands and metacommands based on the DBMS in use, the database in use, data values, user input, and other conditions.
17
- > - Execute blocks of SQL statements and metacommands repeatedly, using any of three different looping methods.
18
- > - Prompt the user to select files or directories, answer questions, or enter data values.
19
- > - Allow the user to visually compare two tables or views.
20
- > - Serve data tables to a web application when used as a CGI script.
21
- > - Write messages to the console or to a file during the processing of a SQL script. These messages can be used to display the progress of the script or create a custom log of the operations that have been carried out or results obtained. Status messages and data exported in text format can be combined in a single text file. Data tables can be exported in a text format that is compatible with Markdown pipe tables, so that script output can be converted into a variety of document formats (see [Example 8](examples.md#example8) and [Example 11](examples.md#example11)).
11
+ - Import data from text files and spreadsheets into a database.
12
+ - Export tables and views to text files, OpenDocument spreadsheets, HTML, JSON, LaTeΧ, XML, or 17 other data formats.
13
+ - Copy data between different databases, even databases using different DBMSs.
14
+ - Display tables or views on the terminal or in a GUI dialog window.
15
+ - Export data using template processors to produce non-tabular output with customized format and contents.
16
+ - Conditionally execute different SQL commands and metacommands based on the DBMS in use, the database in use, data values, user input, and other conditions.
17
+ - Execute blocks of SQL statements and metacommands repeatedly, using any of three different looping methods.
18
+ - Prompt the user to select files or directories, answer questions, or enter data values.
19
+ - Allow the user to visually compare two tables or views.
20
+ - Serve data tables to a web application when used as a CGI script.
21
+ - Write messages to the console or to a file during the processing of a SQL script. These messages can be used to display the progress of the script or create a custom log of the operations that have been carried out or results obtained. Status messages and data exported in text format can be combined in a single text file. Data tables can be exported in a text format that is compatible with Markdown pipe tables, so that script output can be converted into a variety of document formats (see [Example 8](examples.md#example8) and [Example 11](examples.md#example11)).
22
22
 
23
23
  Different DBMSs and DBMS-specific client programs provide different and incompatible extensions to SQL, ordinarily to allow interactions with the file system and to allow conditional tests and looping. Some DBMSs do not have any native extensions of this sort. *execsql* provides these features, as well as features for user interaction, in an identical fashion for all supported DBMSs. This allows standardization of the SQL scripting language used for different types of database management systems.
24
24