execsql2 2.15.11__tar.gz → 2.16.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 (321) hide show
  1. {execsql2-2.15.11 → execsql2-2.16.1}/.gitignore +3 -2
  2. {execsql2-2.15.11 → execsql2-2.16.1}/CHANGELOG.md +78 -0
  3. {execsql2-2.15.11 → execsql2-2.16.1}/PKG-INFO +93 -27
  4. {execsql2-2.15.11 → execsql2-2.16.1}/README.md +92 -26
  5. {execsql2-2.15.11 → execsql2-2.16.1}/docs/about/contributors.md +3 -0
  6. {execsql2-2.15.11 → execsql2-2.16.1}/docs/about/divergence.md +83 -36
  7. {execsql2-2.15.11 → execsql2-2.16.1}/docs/api/exporters.md +6 -0
  8. {execsql2-2.15.11 → execsql2-2.16.1}/docs/api/importers.md +2 -0
  9. {execsql2-2.15.11 → execsql2-2.16.1}/docs/api/index.md +45 -1
  10. {execsql2-2.15.11 → execsql2-2.16.1}/docs/dev/adding_metacommands.md +25 -13
  11. {execsql2-2.15.11 → execsql2-2.16.1}/docs/dev/architecture.md +51 -8
  12. {execsql2-2.15.11 → execsql2-2.16.1}/docs/getting-started/installation.md +2 -0
  13. {execsql2-2.15.11 → execsql2-2.16.1}/docs/getting-started/syntax.md +18 -1
  14. {execsql2-2.15.11 → execsql2-2.16.1}/docs/guides/examples.md +303 -76
  15. execsql2-2.16.1/docs/guides/logging.md +106 -0
  16. {execsql2-2.15.11 → execsql2-2.16.1}/docs/reference/configuration.md +1 -1
  17. {execsql2-2.15.11 → execsql2-2.16.1}/docs/reference/substitution_vars.md +12 -8
  18. execsql2-2.16.1/extras/plugin-template/README.md +71 -0
  19. execsql2-2.16.1/extras/plugin-template/pyproject.toml +22 -0
  20. execsql2-2.16.1/extras/plugin-template/src/execsql_plugin_YOURNAME/__init__.py +96 -0
  21. execsql2-2.16.1/extras/plugin-template/tests/test_plugin.py.example +110 -0
  22. {execsql2-2.15.11 → execsql2-2.16.1}/pyproject.toml +5 -3
  23. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/__init__.py +4 -0
  24. execsql2-2.16.1/src/execsql/api.py +580 -0
  25. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/cli/__init__.py +106 -0
  26. execsql2-2.16.1/src/execsql/cli/lint_ast.py +439 -0
  27. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/cli/run.py +431 -263
  28. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/config.py +10 -1
  29. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/db/access.py +1 -0
  30. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/db/dsn.py +3 -2
  31. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/db/duckdb.py +1 -1
  32. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/db/factory.py +3 -0
  33. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/db/firebird.py +2 -1
  34. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/db/mysql.py +2 -1
  35. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/db/oracle.py +2 -1
  36. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/db/postgres.py +2 -1
  37. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/db/sqlite.py +1 -1
  38. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/db/sqlserver.py +3 -2
  39. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/exporters/base.py +6 -4
  40. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/exporters/delimited.py +11 -3
  41. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/exporters/pretty.py +9 -12
  42. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/metacommands/__init__.py +3 -0
  43. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/metacommands/connect.py +1 -1
  44. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/metacommands/control.py +8 -14
  45. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/metacommands/debug.py +6 -4
  46. execsql2-2.16.1/src/execsql/metacommands/io_export.py +325 -0
  47. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/metacommands/io_fileops.py +7 -13
  48. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/metacommands/io_write.py +1 -1
  49. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/metacommands/script_ext.py +8 -5
  50. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/metacommands/upsert.py +40 -0
  51. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/models.py +8 -12
  52. execsql2-2.16.1/src/execsql/plugins.py +414 -0
  53. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/script/__init__.py +36 -12
  54. execsql2-2.16.1/src/execsql/script/ast.py +562 -0
  55. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/script/engine.py +59 -368
  56. execsql2-2.16.1/src/execsql/script/executor.py +926 -0
  57. execsql2-2.16.1/src/execsql/script/parser.py +663 -0
  58. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/script/variables.py +11 -0
  59. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/state.py +118 -44
  60. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/utils/crypto.py +14 -10
  61. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/utils/errors.py +31 -8
  62. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/utils/mail.py +15 -12
  63. {execsql2-2.15.11 → execsql2-2.16.1}/tests/cli/test_cli.py +163 -0
  64. {execsql2-2.15.11 → execsql2-2.16.1}/tests/cli/test_cli_run.py +18 -151
  65. {execsql2-2.15.11 → execsql2-2.16.1}/tests/cli/test_profile.py +4 -4
  66. {execsql2-2.15.11 → execsql2-2.16.1}/tests/conftest.py +16 -1
  67. {execsql2-2.15.11 → execsql2-2.16.1}/tests/db/test_db_adapters_mocked.py +3 -4
  68. {execsql2-2.15.11 → execsql2-2.16.1}/tests/db/test_dsn.py +5 -6
  69. {execsql2-2.15.11 → execsql2-2.16.1}/tests/exporters/test_base.py +15 -13
  70. {execsql2-2.15.11 → execsql2-2.16.1}/tests/metacommands/test_metacommands.py +4 -3
  71. {execsql2-2.15.11 → execsql2-2.16.1}/tests/metacommands/test_metacommands_extended.py +8 -37
  72. {execsql2-2.15.11 → execsql2-2.16.1}/tests/metacommands/test_metacommands_fileops_extra.py +4 -20
  73. {execsql2-2.15.11 → execsql2-2.16.1}/tests/metacommands/test_metacommands_io.py +5 -21
  74. {execsql2-2.15.11 → execsql2-2.16.1}/tests/metacommands/test_metacommands_script_ext.py +5 -11
  75. {execsql2-2.15.11 → execsql2-2.16.1}/tests/scripts/fixtures/control_flow.sql +5 -0
  76. execsql2-2.16.1/tests/scripts/fixtures/parse_only/parse_tree.sql +541 -0
  77. execsql2-2.16.1/tests/test_api.py +303 -0
  78. execsql2-2.16.1/tests/test_ast.py +552 -0
  79. execsql2-2.16.1/tests/test_ast_parser.py +829 -0
  80. {execsql2-2.15.11 → execsql2-2.16.1}/tests/test_config_data.py +2 -2
  81. {execsql2-2.15.11 → execsql2-2.16.1}/tests/test_config_extended.py +1 -1
  82. {execsql2-2.15.11 → execsql2-2.16.1}/tests/test_engine.py +15 -630
  83. execsql2-2.16.1/tests/test_executor.py +1128 -0
  84. execsql2-2.16.1/tests/test_plugins.py +213 -0
  85. {execsql2-2.15.11 → execsql2-2.16.1}/tests/test_script.py +8 -0
  86. {execsql2-2.15.11 → execsql2-2.16.1}/tests/test_state.py +108 -1
  87. {execsql2-2.15.11 → execsql2-2.16.1}/tests/utils/test_errors_extra.py +4 -4
  88. {execsql2-2.15.11 → execsql2-2.16.1}/uv.lock +1 -1
  89. {execsql2-2.15.11 → execsql2-2.16.1}/zensical.toml +40 -2
  90. execsql2-2.15.11/docs/guides/logging.md +0 -103
  91. execsql2-2.15.11/src/execsql/metacommands/io_export.py +0 -523
  92. {execsql2-2.15.11 → execsql2-2.16.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  93. {execsql2-2.15.11 → execsql2-2.16.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  94. {execsql2-2.15.11 → execsql2-2.16.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  95. {execsql2-2.15.11 → execsql2-2.16.1}/.github/workflows/ci-cd.yml +0 -0
  96. {execsql2-2.15.11 → execsql2-2.16.1}/.pre-commit-config.yaml +0 -0
  97. {execsql2-2.15.11 → execsql2-2.16.1}/.pre-commit-hooks.yaml +0 -0
  98. {execsql2-2.15.11 → execsql2-2.16.1}/.python-version +0 -0
  99. {execsql2-2.15.11 → execsql2-2.16.1}/.readthedocs.yaml +0 -0
  100. {execsql2-2.15.11 → execsql2-2.16.1}/CONTRIBUTING.md +0 -0
  101. {execsql2-2.15.11 → execsql2-2.16.1}/LICENSE.txt +0 -0
  102. {execsql2-2.15.11 → execsql2-2.16.1}/NOTICE +0 -0
  103. {execsql2-2.15.11 → execsql2-2.16.1}/SECURITY.md +0 -0
  104. {execsql2-2.15.11 → execsql2-2.16.1}/docs/about/copyright.md +0 -0
  105. {execsql2-2.15.11 → execsql2-2.16.1}/docs/api/cli.md +0 -0
  106. {execsql2-2.15.11 → execsql2-2.16.1}/docs/api/db.md +0 -0
  107. {execsql2-2.15.11 → execsql2-2.16.1}/docs/api/metacommands.md +0 -0
  108. {execsql2-2.15.11 → execsql2-2.16.1}/docs/dev/adding_db_adapters.md +0 -0
  109. {execsql2-2.15.11 → execsql2-2.16.1}/docs/dev/adding_exporters.md +0 -0
  110. {execsql2-2.15.11 → execsql2-2.16.1}/docs/dev/adding_importers.md +0 -0
  111. {execsql2-2.15.11 → execsql2-2.16.1}/docs/getting-started/requirements.md +0 -0
  112. {execsql2-2.15.11 → execsql2-2.16.1}/docs/guides/debugging.md +0 -0
  113. {execsql2-2.15.11 → execsql2-2.16.1}/docs/guides/documentation.md +0 -0
  114. {execsql2-2.15.11 → execsql2-2.16.1}/docs/guides/encoding.md +0 -0
  115. {execsql2-2.15.11 → execsql2-2.16.1}/docs/guides/formatter.md +0 -0
  116. {execsql2-2.15.11 → execsql2-2.16.1}/docs/guides/sql_syntax.md +0 -0
  117. {execsql2-2.15.11 → execsql2-2.16.1}/docs/guides/usage.md +0 -0
  118. {execsql2-2.15.11 → execsql2-2.16.1}/docs/guides/using_scripts.md +0 -0
  119. {execsql2-2.15.11 → execsql2-2.16.1}/docs/images/Compare_planets.png +0 -0
  120. {execsql2-2.15.11 → execsql2-2.16.1}/docs/images/actions.png +0 -0
  121. {execsql2-2.15.11 → execsql2-2.16.1}/docs/images/actions2.png +0 -0
  122. {execsql2-2.15.11 → execsql2-2.16.1}/docs/images/checkboxes.png +0 -0
  123. {execsql2-2.15.11 → execsql2-2.16.1}/docs/images/connect.b64 +0 -0
  124. {execsql2-2.15.11 → execsql2-2.16.1}/docs/images/connect.png +0 -0
  125. {execsql2-2.15.11 → execsql2-2.16.1}/docs/images/create_conf.png +0 -0
  126. {execsql2-2.15.11 → execsql2-2.16.1}/docs/images/data_error1_screenshot.jpg +0 -0
  127. {execsql2-2.15.11 → execsql2-2.16.1}/docs/images/entry_form.png +0 -0
  128. {execsql2-2.15.11 → execsql2-2.16.1}/docs/images/execsql_console.png +0 -0
  129. {execsql2-2.15.11 → execsql2-2.16.1}/docs/images/execsql_logo_01.png +0 -0
  130. {execsql2-2.15.11 → execsql2-2.16.1}/docs/images/fatals.png +0 -0
  131. {execsql2-2.15.11 → execsql2-2.16.1}/docs/images/logo_small.png +0 -0
  132. {execsql2-2.15.11 → execsql2-2.16.1}/docs/images/pause_terminal.png +0 -0
  133. {execsql2-2.15.11 → execsql2-2.16.1}/docs/images/pause_terminal_sm.b64 +0 -0
  134. {execsql2-2.15.11 → execsql2-2.16.1}/docs/images/pause_terminal_sm.png +0 -0
  135. {execsql2-2.15.11 → execsql2-2.16.1}/docs/images/prompt_compare.png +0 -0
  136. {execsql2-2.15.11 → execsql2-2.16.1}/docs/images/set_build_commands.jpg +0 -0
  137. {execsql2-2.15.11 → execsql2-2.16.1}/docs/images/unit_conversions.b64 +0 -0
  138. {execsql2-2.15.11 → execsql2-2.16.1}/docs/images/unit_conversions_029.png +0 -0
  139. {execsql2-2.15.11 → execsql2-2.16.1}/docs/images/unmatched.png +0 -0
  140. {execsql2-2.15.11 → execsql2-2.16.1}/docs/images/vim_execsql_highlight.png +0 -0
  141. {execsql2-2.15.11 → execsql2-2.16.1}/docs/index.md +0 -0
  142. {execsql2-2.15.11 → execsql2-2.16.1}/docs/reference/metacommands.md +0 -0
  143. {execsql2-2.15.11 → execsql2-2.16.1}/docs/reference/security.md +0 -0
  144. {execsql2-2.15.11 → execsql2-2.16.1}/extras/vscode-execsql/README.md +0 -0
  145. {execsql2-2.15.11 → execsql2-2.16.1}/extras/vscode-execsql/package.json +0 -0
  146. {execsql2-2.15.11 → execsql2-2.16.1}/extras/vscode-execsql/syntaxes/execsql.tmLanguage.json +0 -0
  147. {execsql2-2.15.11 → execsql2-2.16.1}/justfile +0 -0
  148. {execsql2-2.15.11 → execsql2-2.16.1}/scripts/generate_vscode_grammar.py +0 -0
  149. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/__main__.py +0 -0
  150. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/cli/dsn.py +0 -0
  151. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/cli/help.py +0 -0
  152. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/cli/lint.py +0 -0
  153. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/db/__init__.py +0 -0
  154. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/db/base.py +0 -0
  155. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/debug/__init__.py +0 -0
  156. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/debug/repl.py +0 -0
  157. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/exceptions.py +0 -0
  158. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/exporters/__init__.py +0 -0
  159. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/exporters/duckdb.py +0 -0
  160. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/exporters/feather.py +0 -0
  161. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/exporters/html.py +0 -0
  162. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/exporters/json.py +0 -0
  163. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/exporters/latex.py +0 -0
  164. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/exporters/markdown.py +0 -0
  165. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/exporters/ods.py +0 -0
  166. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/exporters/parquet.py +0 -0
  167. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/exporters/protocol.py +0 -0
  168. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/exporters/raw.py +0 -0
  169. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/exporters/sqlite.py +0 -0
  170. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/exporters/templates.py +0 -0
  171. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/exporters/values.py +0 -0
  172. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/exporters/xls.py +0 -0
  173. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/exporters/xlsx.py +0 -0
  174. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/exporters/xml.py +0 -0
  175. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/exporters/yaml.py +0 -0
  176. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/exporters/zip.py +0 -0
  177. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/format.py +0 -0
  178. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/gui/__init__.py +0 -0
  179. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/gui/base.py +0 -0
  180. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/gui/console.py +0 -0
  181. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/gui/desktop.py +0 -0
  182. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/gui/tui.py +0 -0
  183. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/importers/__init__.py +0 -0
  184. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/importers/base.py +0 -0
  185. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/importers/csv.py +0 -0
  186. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/importers/feather.py +0 -0
  187. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/importers/json.py +0 -0
  188. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/importers/ods.py +0 -0
  189. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/importers/xls.py +0 -0
  190. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/metacommands/conditions.py +0 -0
  191. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/metacommands/data.py +0 -0
  192. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/metacommands/dispatch.py +0 -0
  193. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/metacommands/io.py +0 -0
  194. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/metacommands/io_import.py +0 -0
  195. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/metacommands/prompt.py +0 -0
  196. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/metacommands/system.py +0 -0
  197. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/parser.py +0 -0
  198. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/py.typed +0 -0
  199. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/script/control.py +0 -0
  200. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/types.py +0 -0
  201. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/utils/__init__.py +0 -0
  202. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/utils/auth.py +0 -0
  203. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/utils/datetime.py +0 -0
  204. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/utils/fileio.py +0 -0
  205. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/utils/gui.py +0 -0
  206. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/utils/numeric.py +0 -0
  207. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/utils/regex.py +0 -0
  208. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/utils/strings.py +0 -0
  209. {execsql2-2.15.11 → execsql2-2.16.1}/src/execsql/utils/timer.py +0 -0
  210. {execsql2-2.15.11 → execsql2-2.16.1}/templates/README.md +0 -0
  211. {execsql2-2.15.11 → execsql2-2.16.1}/templates/config_settings.sqlite +0 -0
  212. {execsql2-2.15.11 → execsql2-2.16.1}/templates/example_config_prompt.sql +0 -0
  213. {execsql2-2.15.11 → execsql2-2.16.1}/templates/execsql.conf +0 -0
  214. {execsql2-2.15.11 → execsql2-2.16.1}/templates/make_config_db.sql +0 -0
  215. {execsql2-2.15.11 → execsql2-2.16.1}/templates/md_compare.sql +0 -0
  216. {execsql2-2.15.11 → execsql2-2.16.1}/templates/md_glossary.sql +0 -0
  217. {execsql2-2.15.11 → execsql2-2.16.1}/templates/md_upsert.sql +0 -0
  218. {execsql2-2.15.11 → execsql2-2.16.1}/templates/pg_compare.sql +0 -0
  219. {execsql2-2.15.11 → execsql2-2.16.1}/templates/pg_glossary.sql +0 -0
  220. {execsql2-2.15.11 → execsql2-2.16.1}/templates/pg_upsert.sql +0 -0
  221. {execsql2-2.15.11 → execsql2-2.16.1}/templates/script_template.sql +0 -0
  222. {execsql2-2.15.11 → execsql2-2.16.1}/templates/ss_compare.sql +0 -0
  223. {execsql2-2.15.11 → execsql2-2.16.1}/templates/ss_glossary.sql +0 -0
  224. {execsql2-2.15.11 → execsql2-2.16.1}/templates/ss_upsert.sql +0 -0
  225. {execsql2-2.15.11 → execsql2-2.16.1}/tests/__init__.py +0 -0
  226. {execsql2-2.15.11 → execsql2-2.16.1}/tests/cli/__init__.py +0 -0
  227. {execsql2-2.15.11 → execsql2-2.16.1}/tests/cli/test_cli_e2e.py +0 -0
  228. {execsql2-2.15.11 → execsql2-2.16.1}/tests/cli/test_lint.py +0 -0
  229. {execsql2-2.15.11 → execsql2-2.16.1}/tests/cli/test_ping.py +0 -0
  230. {execsql2-2.15.11 → execsql2-2.16.1}/tests/db/__init__.py +0 -0
  231. {execsql2-2.15.11 → execsql2-2.16.1}/tests/db/test_base.py +0 -0
  232. {execsql2-2.15.11 → execsql2-2.16.1}/tests/db/test_duckdb.py +0 -0
  233. {execsql2-2.15.11 → execsql2-2.16.1}/tests/db/test_factory.py +0 -0
  234. {execsql2-2.15.11 → execsql2-2.16.1}/tests/db/test_postgres.py +0 -0
  235. {execsql2-2.15.11 → execsql2-2.16.1}/tests/db/test_sqlite.py +0 -0
  236. {execsql2-2.15.11 → execsql2-2.16.1}/tests/db/test_sqlite_extra.py +0 -0
  237. {execsql2-2.15.11 → execsql2-2.16.1}/tests/exporters/__init__.py +0 -0
  238. {execsql2-2.15.11 → execsql2-2.16.1}/tests/exporters/test_db.py +0 -0
  239. {execsql2-2.15.11 → execsql2-2.16.1}/tests/exporters/test_delimited.py +0 -0
  240. {execsql2-2.15.11 → execsql2-2.16.1}/tests/exporters/test_duckdb_exporter.py +0 -0
  241. {execsql2-2.15.11 → execsql2-2.16.1}/tests/exporters/test_exporters.py +0 -0
  242. {execsql2-2.15.11 → execsql2-2.16.1}/tests/exporters/test_feather.py +0 -0
  243. {execsql2-2.15.11 → execsql2-2.16.1}/tests/exporters/test_html_extended.py +0 -0
  244. {execsql2-2.15.11 → execsql2-2.16.1}/tests/exporters/test_html_latex.py +0 -0
  245. {execsql2-2.15.11 → execsql2-2.16.1}/tests/exporters/test_json.py +0 -0
  246. {execsql2-2.15.11 → execsql2-2.16.1}/tests/exporters/test_json_extended.py +0 -0
  247. {execsql2-2.15.11 → execsql2-2.16.1}/tests/exporters/test_latex_extended.py +0 -0
  248. {execsql2-2.15.11 → execsql2-2.16.1}/tests/exporters/test_markdown.py +0 -0
  249. {execsql2-2.15.11 → execsql2-2.16.1}/tests/exporters/test_ods.py +0 -0
  250. {execsql2-2.15.11 → execsql2-2.16.1}/tests/exporters/test_parquet.py +0 -0
  251. {execsql2-2.15.11 → execsql2-2.16.1}/tests/exporters/test_pretty_extended.py +0 -0
  252. {execsql2-2.15.11 → execsql2-2.16.1}/tests/exporters/test_raw_extended.py +0 -0
  253. {execsql2-2.15.11 → execsql2-2.16.1}/tests/exporters/test_sqlite_exporter.py +0 -0
  254. {execsql2-2.15.11 → execsql2-2.16.1}/tests/exporters/test_templates.py +0 -0
  255. {execsql2-2.15.11 → execsql2-2.16.1}/tests/exporters/test_templates_extended.py +0 -0
  256. {execsql2-2.15.11 → execsql2-2.16.1}/tests/exporters/test_values_extended.py +0 -0
  257. {execsql2-2.15.11 → execsql2-2.16.1}/tests/exporters/test_xls_xlsx.py +0 -0
  258. {execsql2-2.15.11 → execsql2-2.16.1}/tests/exporters/test_xlsx.py +0 -0
  259. {execsql2-2.15.11 → execsql2-2.16.1}/tests/exporters/test_xml.py +0 -0
  260. {execsql2-2.15.11 → execsql2-2.16.1}/tests/exporters/test_yaml.py +0 -0
  261. {execsql2-2.15.11 → execsql2-2.16.1}/tests/exporters/test_zip.py +0 -0
  262. {execsql2-2.15.11 → execsql2-2.16.1}/tests/gui/__init__.py +0 -0
  263. {execsql2-2.15.11 → execsql2-2.16.1}/tests/gui/test_backends.py +0 -0
  264. {execsql2-2.15.11 → execsql2-2.16.1}/tests/gui/test_compare_stats.py +0 -0
  265. {execsql2-2.15.11 → execsql2-2.16.1}/tests/gui/test_compute_row_diffs.py +0 -0
  266. {execsql2-2.15.11 → execsql2-2.16.1}/tests/importers/__init__.py +0 -0
  267. {execsql2-2.15.11 → execsql2-2.16.1}/tests/importers/test_base_extended.py +0 -0
  268. {execsql2-2.15.11 → execsql2-2.16.1}/tests/importers/test_csv_edge_cases.py +0 -0
  269. {execsql2-2.15.11 → execsql2-2.16.1}/tests/importers/test_csv_importer.py +0 -0
  270. {execsql2-2.15.11 → execsql2-2.16.1}/tests/importers/test_feather_importer.py +0 -0
  271. {execsql2-2.15.11 → execsql2-2.16.1}/tests/importers/test_json_importer.py +0 -0
  272. {execsql2-2.15.11 → execsql2-2.16.1}/tests/importers/test_ods_importer.py +0 -0
  273. {execsql2-2.15.11 → execsql2-2.16.1}/tests/importers/test_xls_importer.py +0 -0
  274. {execsql2-2.15.11 → execsql2-2.16.1}/tests/integration/__init__.py +0 -0
  275. {execsql2-2.15.11 → execsql2-2.16.1}/tests/integration/conftest.py +0 -0
  276. {execsql2-2.15.11 → execsql2-2.16.1}/tests/integration/test_dsn.py +0 -0
  277. {execsql2-2.15.11 → execsql2-2.16.1}/tests/integration/test_duckdb.py +0 -0
  278. {execsql2-2.15.11 → execsql2-2.16.1}/tests/integration/test_mysql.py +0 -0
  279. {execsql2-2.15.11 → execsql2-2.16.1}/tests/integration/test_postgres.py +0 -0
  280. {execsql2-2.15.11 → execsql2-2.16.1}/tests/integration/test_sqlite.py +0 -0
  281. {execsql2-2.15.11 → execsql2-2.16.1}/tests/metacommands/__init__.py +0 -0
  282. {execsql2-2.15.11 → execsql2-2.16.1}/tests/metacommands/test_assert.py +0 -0
  283. {execsql2-2.15.11 → execsql2-2.16.1}/tests/metacommands/test_breakpoint.py +0 -0
  284. {execsql2-2.15.11 → execsql2-2.16.1}/tests/metacommands/test_connect.py +0 -0
  285. {execsql2-2.15.11 → execsql2-2.16.1}/tests/metacommands/test_io_export.py +0 -0
  286. {execsql2-2.15.11 → execsql2-2.16.1}/tests/metacommands/test_io_import.py +0 -0
  287. {execsql2-2.15.11 → execsql2-2.16.1}/tests/metacommands/test_metacommands_connect.py +0 -0
  288. {execsql2-2.15.11 → execsql2-2.16.1}/tests/metacommands/test_metacommands_data.py +0 -0
  289. {execsql2-2.15.11 → execsql2-2.16.1}/tests/metacommands/test_metacommands_io_write_extra.py +0 -0
  290. {execsql2-2.15.11 → execsql2-2.16.1}/tests/metacommands/test_metacommands_system.py +0 -0
  291. {execsql2-2.15.11 → execsql2-2.16.1}/tests/metacommands/test_metacommands_system_extra.py +0 -0
  292. {execsql2-2.15.11 → execsql2-2.16.1}/tests/metacommands/test_pg_upsert.py +0 -0
  293. {execsql2-2.15.11 → execsql2-2.16.1}/tests/metacommands/test_row_count.py +0 -0
  294. {execsql2-2.15.11 → execsql2-2.16.1}/tests/scripts/__init__.py +0 -0
  295. {execsql2-2.15.11 → execsql2-2.16.1}/tests/scripts/fixtures/io_roundtrip.sql +0 -0
  296. {execsql2-2.15.11 → execsql2-2.16.1}/tests/scripts/fixtures/smoke.sql +0 -0
  297. {execsql2-2.15.11 → execsql2-2.16.1}/tests/scripts/test_sql_scripts.py +0 -0
  298. {execsql2-2.15.11 → execsql2-2.16.1}/tests/test_config.py +0 -0
  299. {execsql2-2.15.11 → execsql2-2.16.1}/tests/test_debug_repl.py +0 -0
  300. {execsql2-2.15.11 → execsql2-2.16.1}/tests/test_error_messages.py +0 -0
  301. {execsql2-2.15.11 → execsql2-2.16.1}/tests/test_exceptions.py +0 -0
  302. {execsql2-2.15.11 → execsql2-2.16.1}/tests/test_format.py +0 -0
  303. {execsql2-2.15.11 → execsql2-2.16.1}/tests/test_mail.py +0 -0
  304. {execsql2-2.15.11 → execsql2-2.16.1}/tests/test_models.py +0 -0
  305. {execsql2-2.15.11 → execsql2-2.16.1}/tests/test_package.py +0 -0
  306. {execsql2-2.15.11 → execsql2-2.16.1}/tests/test_parser.py +0 -0
  307. {execsql2-2.15.11 → execsql2-2.16.1}/tests/test_registry.py +0 -0
  308. {execsql2-2.15.11 → execsql2-2.16.1}/tests/test_types.py +0 -0
  309. {execsql2-2.15.11 → execsql2-2.16.1}/tests/utils/__init__.py +0 -0
  310. {execsql2-2.15.11 → execsql2-2.16.1}/tests/utils/test_auth.py +0 -0
  311. {execsql2-2.15.11 → execsql2-2.16.1}/tests/utils/test_auth_extra.py +0 -0
  312. {execsql2-2.15.11 → execsql2-2.16.1}/tests/utils/test_crypto.py +0 -0
  313. {execsql2-2.15.11 → execsql2-2.16.1}/tests/utils/test_datetime.py +0 -0
  314. {execsql2-2.15.11 → execsql2-2.16.1}/tests/utils/test_errors.py +0 -0
  315. {execsql2-2.15.11 → execsql2-2.16.1}/tests/utils/test_fileio.py +0 -0
  316. {execsql2-2.15.11 → execsql2-2.16.1}/tests/utils/test_fileio_extra.py +0 -0
  317. {execsql2-2.15.11 → execsql2-2.16.1}/tests/utils/test_numeric.py +0 -0
  318. {execsql2-2.15.11 → execsql2-2.16.1}/tests/utils/test_regex.py +0 -0
  319. {execsql2-2.15.11 → execsql2-2.16.1}/tests/utils/test_strings.py +0 -0
  320. {execsql2-2.15.11 → execsql2-2.16.1}/tests/utils/test_timer.py +0 -0
  321. {execsql2-2.15.11 → execsql2-2.16.1}/tests/utils/test_timer_extra.py +0 -0
@@ -46,6 +46,7 @@ _execsql/
46
46
 
47
47
  # Claude
48
48
  .claude/
49
- ANALYSIS.md
50
- FINDINGS.md
51
49
  CLAUDE.md
50
+ AUDIT.md
51
+ DOC_AUDIT.md
52
+ PROMPTS.md
@@ -13,6 +13,84 @@ ______________________________________________________________________
13
13
 
14
14
  ______________________________________________________________________
15
15
 
16
+ ## [2.16.1] - 2026-04-30
17
+
18
+ ### Fixed
19
+
20
+ - AST executor now correctly handles `~` (local) and `+` (outer-scope) substitution variables inside SCRIPT blocks. Previously, `-- !x! SUB ~var value` inside a SCRIPT body would write to a disconnected scope, causing the variable to be invisible to subsequent SQL statements and producing spurious "potential un-substituted variable" warnings. The fix pushes proper `CommandList` frames onto `commandliststack` at script and top-level boundaries, bridging the AST executor with the legacy metacommand handlers (`x_sub`, `x_rm_sub`, `xf_sub_defined`, `SUB_LOCAL`, prompt handlers, REPL `.vars`/`.stack`, etc.).
21
+ - EXECUTE SCRIPT argument expressions (e.g., `val=!!#parent_param!!`) are now expanded in the caller's scope before the child script frame is created, fixing nested script calls that pass `~` or `#` variables as arguments.
22
+
23
+ ### Changed
24
+
25
+ - `RuntimeContext` is now stored in `threading.local()` instead of a module-level global, making `_state.foo` access thread-safe. Each thread gets its own isolated context via lazy initialization. The `active_context` context manager is now safe for concurrent use across threads. Enables future PARALLEL blocks and concurrent `from execsql import run` calls.
26
+ - `_run()` in `cli/run.py` decomposed into 8 standalone functions: `_seed_early_subvars()`, `_load_config()`, `_seed_script_subvars()`, `_load_script()`, `_apply_dsn()`, `_apply_cli_options()`, `_route_positionals()`, and `_setup_logging()`. Reduces `_run()` from ~380 lines to ~150 lines of orchestration with zero behavioral change.
27
+
28
+ ______________________________________________________________________
29
+
30
+ ## [2.16.0] - 2026-04-29
31
+
32
+ ### Added
33
+
34
+ - `--parse-tree` CLI flag: parse a script into an Abstract Syntax Tree and print a visual tree structure showing block nesting (IF/LOOP/BATCH/SCRIPT), source line ranges, compound conditions (ANDIF/ORIF), and all metacommands. Requires no database connection or configuration.
35
+ - AST parser module (`execsql.script.parser`) with `parse_script()` and `parse_string()` entry points. Produces a structured `Script` tree with typed nodes for all block constructs (IfBlock, LoopBlock, BatchBlock, ScriptBlock, SqlBlock, IncludeDirective).
36
+ - AST node definitions (`execsql.script.ast`) with `format_tree()` for human-readable tree output.
37
+ - AST-based execution engine is now the default (and only) engine. Scripts are parsed into a tree of typed nodes, then walked for execution. INCLUDE'd files are parsed and executed natively with circular-include detection. Control flow (IF/LOOP/BATCH) is driven by tree structure.
38
+ - `active_context()` context manager in `execsql.state` for installing an isolated `RuntimeContext` as the active global context within a `with` block.
39
+ - Plugin system (`execsql.plugins`) for extending execsql with custom metacommands, export formats, and import formats via Python entry points. Entry point groups: `execsql.metacommands`, `execsql.exporters`, `execsql.importers`. Plugins are discovered automatically at startup.
40
+ - `--list-plugins` CLI flag to show all discovered plugins and exit.
41
+ - Python library API: `from execsql import run` for programmatic script execution from notebooks, pipelines, and applications. Returns a `ScriptResult` with success/failure, command count, timing, errors, and final variable state. Supports DSN connection strings, pre-existing connections, substitution variables, and error control. Full RuntimeContext isolation between calls.
42
+ - AST `Comment` node: the parser now preserves SQL comments in the tree. Consecutive single-line `--` comments are grouped into one node; `/* */` block comments are captured as single nodes. The `--parse-tree` output includes `<CMT>` tagged comment nodes.
43
+ - `--parse-tree` visual improvements: color-coded type tags (`<SQL>`, `<CMD>`, `<CMT>`, `<IF>`, `<LOOP>`, etc.), dimmed line numbers, and content truncation for cleaner output.
44
+ - Deprecation warning emitted when `enc_password` is used in config files, advising users to switch to keyring or environment variables.
45
+ - Sensitive environment variables (`*SECRET*`, `*TOKEN*`, `*PASSWORD*`, etc.) are now filtered from automatic substitution variable exposure.
46
+
47
+ ### Changed
48
+
49
+ - **Execution engine replaced.** The legacy flat command-list engine has been replaced by the AST-based executor. Scripts are now parsed into a tree of typed nodes and executed by walking the tree. INCLUDE'd files are parsed and executed natively with circular-include detection. All metacommands, SQL, and control flow work identically. This change is transparent to users.
50
+ - **BREAK outside LOOP is now an error.** `BREAK` outside a loop block now raises an error (exit 1) instead of being silently ignored. This catches script bugs that were previously unreported.
51
+ - `--lint` now uses the AST parser for structural validation. Unmatched IF/LOOP/BATCH/SCRIPT blocks are caught at parse time with precise source line ranges. No database connection or runtime state initialization is required. All prior lint checks (variable analysis, INCLUDE file existence, EXECUTE SCRIPT resolution, SUB_INI reading) are preserved.
52
+ - Export format dispatch logic (`EXPORT` and `EXPORT QUERY` metacommands) refactored from duplicated ~180-line if/elif chains into shared `_dispatch_format()` function, eliminating code duplication and fixing missing zip-compatibility checks for `EXPORT QUERY`.
53
+ - `MailSpec.send()` refactored: extracted `_expand()` helper to replace 12 repetitive substitution lines.
54
+ - Default database type changed from Access (`-t a`) to SQLite (`-t l`). Upstream defaulted to Access, which requires Windows and pyodbc. Users targeting Access databases should pass `-t a` explicitly.
55
+
56
+ ### Fixed
57
+
58
+ - **[Critical]** `WriteSpec.write()` and `MailSpec.send()` error-recovery paths crashed because `SubVarSet.substitute_all()` returns `(str, bool)` but callers treated the return as a plain string. All 14 call sites now unpack the tuple correctly.
59
+ - **[Critical]** Error-recovery fallback in `WriteSpec.write()` and `io_write` called `.encode()` producing bytes passed to `sys.stdout.write()` which expects `str`. Removed the `.encode()` calls.
60
+ - `WriteSpec.write()` no longer crashes with `IndexError` when `commandliststack` is empty during early initialization errors.
61
+ - SQL injection vector in `exec_cmd()` across all 8 database adapters — stored procedure/function/view names are now quoted with `quote_identifier()`.
62
+ - `DSN` and `SQL Server` adapters no longer encode SQL strings to bytes before execution.
63
+ - Duplicate tuple entries in export format checks (`"txt-and"` and `"text-and"` each appeared twice).
64
+ - Database adapters now clear `self.password` after successful connection, reducing credential exposure window.
65
+ - Removed unused `_DEFAULT_CTX = RuntimeContext()` allocation in `state.py`.
66
+ - Version bump commits no longer skip pre-commit hooks (`--no-verify` removed from bumpversion config).
67
+ - `SubVarSet.substitute_all()` now enforces a 100-iteration depth limit to prevent infinite loops from cyclic variable references. The per-statement guard in the executor already had this protection, but direct callers (e.g. config loading) did not.
68
+ - `ConfigData.export_output_dir` is now declared in `__init__` with a default of `None` instead of being dynamically added in the CLI entry point.
69
+ - `Encrypt.ky` key table is now an immutable `MappingProxyType` instead of a mutable class-level dict.
70
+ - `JsonDatatype` attributes are now declared as class variables in the class body instead of assigned externally after class definition.
71
+ - `minimal_conf` test fixture expanded with commonly needed attributes (`import_encoding`, `script_encoding`, `export_output_dir`, `write_prefix`, `write_suffix`, `fold_col_hdrs`, `trim_col_hdrs`, etc.) to reduce ad-hoc attribute additions in individual tests.
72
+
73
+ ### Documentation
74
+
75
+ - Fixed false `$ENV:` prefix claim in substitution variables reference — feature does not exist.
76
+ - Documented environment variable filtering (SECRET, TOKEN, PASSWORD, etc.) in substitution variables reference.
77
+ - Added missing exporter API docs (markdown, yaml, xlsx) and importer API docs (json).
78
+ - Added 8 missing CLI flags to README Options table (-b, -e, -g, -i, -o, -s, -y, -z).
79
+ - Added missing installation extras ([upsert], [firebird], [oracle]) to README and installation guide.
80
+ - Fixed broken `PROMPT.md` link in logging guide.
81
+ - Added explicit `{ #exampleN }` anchors to all 34 examples for reliable cross-referencing.
82
+ - Updated architecture doc: corrected metacommand count (~225), export format count (20+), added debug/notebook/server/lsp packages to module map.
83
+ - Updated metacommand developer guide to reflect io.py split into io_export.py, io_import.py, io_write.py, io_fileops.py.
84
+ - Noted SQLite as the default database type in syntax reference.
85
+
86
+ ### Removed
87
+
88
+ - `--ast` / `--no-ast` CLI flag — the AST executor is now the only execution engine; no opt-out.
89
+ - Legacy flat command-list execution engine (`_parse_script_lines`, `read_sqlfile`, `read_sqlstring`, `runscripts`, `ScriptFile`, `CommandListWhileLoop`, `CommandListUntilLoop`, `ScriptExecSpec.execute()`).
90
+ - Legacy `_execute_script_direct()` function and `_execute_include_legacy()` fallback path.
91
+
92
+ ______________________________________________________________________
93
+
16
94
  ## [2.15.11] - 2026-04-27
17
95
 
18
96
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: execsql2
3
- Version: 2.15.11
3
+ Version: 2.16.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: Homepage, https://execsql2.readthedocs.io
6
6
  Project-URL: Repository, https://github.com/geocoug/execsql
@@ -173,7 +173,8 @@ pip install execsql2[oracle] # Oracle (oracledb)
173
173
  pip install execsql2[odbc] # ODBC DSN (pyodbc)
174
174
 
175
175
  # Feature bundles
176
- pip install execsql2[formats] # ODS, Excel, Jinja2, Feather, Parquet, HDF5
176
+ pip install execsql2[formats] # ODS, Excel, Jinja2, Feather, Parquet, HDF5
177
+ pip install execsql2[upsert] # pg-upsert for PostgreSQL upsert operations
177
178
  pip install execsql2[auth] # OS keyring integration
178
179
  pip install execsql2[auth-plaintext] # Keyring + plaintext file backend (headless Linux)
179
180
  pip install execsql2[auth-encrypted] # Keyring + encrypted file backend (headless Linux)
@@ -219,31 +220,41 @@ execsql script.sql # read connection from config file
219
220
 
220
221
  ## Options
221
222
 
222
- | Flag | Description |
223
- | ----------------------------------- | --------------------------------------------------------------- |
224
- | `-t {p,m,s,l,k,a,f,o,d}` | Database type |
225
- | `-u USER` | Database username |
226
- | `-p PORT` | Server port |
227
- | `-a VALUE` | Set substitution variable `$ARG_x` |
228
- | `-c SCRIPT` | Execute inline SQL or metacommand string |
229
- | `-d` | Auto-create export directories |
230
- | `-f ENCODING` | Script file encoding (default: UTF-8) |
231
- | `-l` | Write run log to `~/execsql.log` |
232
- | `-m` | List metacommands and exit |
233
- | `-n` | Create a new SQLite or PostgreSQL database if it does not exist |
234
- | `-v {0,1,2,3}` | GUI level (0=none, 1=password, 2=selection, 3=full) |
235
- | `-w` | Skip password prompt when a username is supplied |
236
- | `--dsn URL` | Connection string (e.g. `postgresql://user:pass@host/db`) |
237
- | `--output-dir DIR` | Default base directory for EXPORT output files |
238
- | `--dry-run` | Parse the script and report commands without executing |
239
- | `--lint` | Static analysis: check structure and warn on issues (no DB) |
240
- | `--ping` | Test database connectivity and exit |
241
- | `--profile` | Show per-statement timing summary after execution |
242
- | `--progress` | Show a progress bar for long-running IMPORT operations |
243
- | `--config FILE` | Load an explicit config file (highest priority after CLI args) |
244
- | `--debug` | Start in step-through debug mode (REPL pauses before each stmt) |
245
- | `--dump-keywords` | Print metacommand keywords as JSON and exit |
246
- | `--gui-framework {tkinter,textual}` | GUI framework for interactive prompts |
223
+ | Flag | Description |
224
+ | ------------------------------------- | ---------------------------------------------------------------- |
225
+ | `-t {p,m,s,l,k,a,f,o,d}` | Database type |
226
+ | `-u USER` | Database username |
227
+ | `-p PORT` | Server port |
228
+ | `-a VALUE` | Set substitution variable `$ARG_x` |
229
+ | `-b` / `--boolean-int` | Treat integers 0 and 1 as boolean values |
230
+ | `-c SCRIPT` | Execute inline SQL or metacommand string |
231
+ | `-d` | Auto-create export directories |
232
+ | `-e ENCODING` / `--database-encoding` | Character encoding used in the database |
233
+ | `-f ENCODING` | Script file encoding (default: UTF-8) |
234
+ | `-g ENCODING` / `--output-encoding` | Encoding for WRITE and EXPORT output |
235
+ | `-i ENCODING` / `--import-encoding` | Encoding for data files used with IMPORT |
236
+ | `-l` | Write run log to `~/execsql.log` |
237
+ | `-m` | List metacommands and exit |
238
+ | `-n` | Create a new SQLite or PostgreSQL database if it does not exist |
239
+ | `-o` / `--online-help` | Open the online documentation in the default browser |
240
+ | `-s N` / `--scan-lines` | Lines to scan for IMPORT format detection (0 = scan entire file) |
241
+ | `-v {0,1,2,3}` | GUI level (0=none, 1=password, 2=selection, 3=full) |
242
+ | `-w` | Skip password prompt when a username is supplied |
243
+ | `-y` / `--encodings` | List available encoding names and exit |
244
+ | `-z KB` / `--import-buffer` | Import buffer size in KB (default: 32) |
245
+ | `--dsn URL` | Connection string (e.g. `postgresql://user:pass@host/db`) |
246
+ | `--output-dir DIR` | Default base directory for EXPORT output files |
247
+ | `--dry-run` | Parse the script and report commands without executing |
248
+ | `--lint` | Static analysis: check structure and warn on issues (no DB) |
249
+ | `--parse-tree` | Print the script's AST structure and exit (no DB) |
250
+ | `--list-plugins` | List discovered plugins and exit |
251
+ | `--ping` | Test database connectivity and exit |
252
+ | `--profile` | Show per-statement timing summary after execution |
253
+ | `--progress` | Show a progress bar for long-running IMPORT operations |
254
+ | `--config FILE` | Load an explicit config file (highest priority after CLI args) |
255
+ | `--debug` | Start in step-through debug mode (REPL pauses before each stmt) |
256
+ | `--dump-keywords` | Print metacommand keywords as JSON and exit |
257
+ | `--gui-framework {tkinter,textual}` | GUI framework for interactive prompts |
247
258
 
248
259
  Run `execsql --help` for the full option list, or `execsql -m` to list all metacommands.
249
260
 
@@ -260,6 +271,61 @@ Run `execsql --help` for the full option list, or `execsql -m` to list all metac
260
271
  - Display query results in a GUI dialog; optionally prompt the user to select a row, enter a value, or submit a form.
261
272
  - Write status messages or tabular output to the console or a file during execution.
262
273
  - Automatically log each run, recording databases used, scripts executed, and user responses.
274
+ - Extend with custom metacommands, exporters, and importers via the plugin system.
275
+
276
+ # Library API
277
+
278
+ execsql can be used as a Python library for programmatic script execution:
279
+
280
+ ```python
281
+ from execsql import run
282
+
283
+ # Execute a script file
284
+ result = run(script="pipeline.sql", dsn="postgresql://user:pass@host/db")
285
+
286
+ # Execute inline SQL
287
+ result = run(
288
+ sql="CREATE TABLE t (id INT);\nINSERT INTO t VALUES (1);",
289
+ dsn="sqlite:///my.db",
290
+ new_db=True,
291
+ )
292
+
293
+ # With substitution variables
294
+ result = run(
295
+ script="etl.sql",
296
+ dsn="sqlite:///data.db",
297
+ variables={"SCHEMA": "public", "DATE": "2026-01-01"},
298
+ )
299
+
300
+ # Check results
301
+ print(result.success) # True
302
+ print(result.commands_run) # 2
303
+ print(result.elapsed) # 0.003 (seconds)
304
+ print(result.variables) # {"SCHEMA": "public", ...}
305
+ ```
306
+
307
+ Error handling:
308
+
309
+ ```python
310
+ result = run(sql="SELECT * FROM nonexistent;", dsn="sqlite:///:memory:")
311
+ if not result.success:
312
+ for err in result.errors:
313
+ print(f"{err.source}:{err.line}: {err.message}")
314
+
315
+ # Or raise on failure
316
+ result.raise_on_error() # raises ExecSqlError
317
+ ```
318
+
319
+ Use a pre-existing database connection instead of a DSN:
320
+
321
+ ```python
322
+ from execsql.db.factory import db_SQLite
323
+ conn = db_SQLite("my.db", new_db=True)
324
+ result = run(sql="SELECT 1;", connection=conn)
325
+ # run() does NOT close this connection — you manage its lifecycle
326
+ ```
327
+
328
+ Each call to `run()` uses an isolated `RuntimeContext`, so multiple calls do not share state.
263
329
 
264
330
  # An Illustration
265
331
 
@@ -51,7 +51,8 @@ pip install execsql2[oracle] # Oracle (oracledb)
51
51
  pip install execsql2[odbc] # ODBC DSN (pyodbc)
52
52
 
53
53
  # Feature bundles
54
- pip install execsql2[formats] # ODS, Excel, Jinja2, Feather, Parquet, HDF5
54
+ pip install execsql2[formats] # ODS, Excel, Jinja2, Feather, Parquet, HDF5
55
+ pip install execsql2[upsert] # pg-upsert for PostgreSQL upsert operations
55
56
  pip install execsql2[auth] # OS keyring integration
56
57
  pip install execsql2[auth-plaintext] # Keyring + plaintext file backend (headless Linux)
57
58
  pip install execsql2[auth-encrypted] # Keyring + encrypted file backend (headless Linux)
@@ -97,31 +98,41 @@ execsql script.sql # read connection from config file
97
98
 
98
99
  ## Options
99
100
 
100
- | Flag | Description |
101
- | ----------------------------------- | --------------------------------------------------------------- |
102
- | `-t {p,m,s,l,k,a,f,o,d}` | Database type |
103
- | `-u USER` | Database username |
104
- | `-p PORT` | Server port |
105
- | `-a VALUE` | Set substitution variable `$ARG_x` |
106
- | `-c SCRIPT` | Execute inline SQL or metacommand string |
107
- | `-d` | Auto-create export directories |
108
- | `-f ENCODING` | Script file encoding (default: UTF-8) |
109
- | `-l` | Write run log to `~/execsql.log` |
110
- | `-m` | List metacommands and exit |
111
- | `-n` | Create a new SQLite or PostgreSQL database if it does not exist |
112
- | `-v {0,1,2,3}` | GUI level (0=none, 1=password, 2=selection, 3=full) |
113
- | `-w` | Skip password prompt when a username is supplied |
114
- | `--dsn URL` | Connection string (e.g. `postgresql://user:pass@host/db`) |
115
- | `--output-dir DIR` | Default base directory for EXPORT output files |
116
- | `--dry-run` | Parse the script and report commands without executing |
117
- | `--lint` | Static analysis: check structure and warn on issues (no DB) |
118
- | `--ping` | Test database connectivity and exit |
119
- | `--profile` | Show per-statement timing summary after execution |
120
- | `--progress` | Show a progress bar for long-running IMPORT operations |
121
- | `--config FILE` | Load an explicit config file (highest priority after CLI args) |
122
- | `--debug` | Start in step-through debug mode (REPL pauses before each stmt) |
123
- | `--dump-keywords` | Print metacommand keywords as JSON and exit |
124
- | `--gui-framework {tkinter,textual}` | GUI framework for interactive prompts |
101
+ | Flag | Description |
102
+ | ------------------------------------- | ---------------------------------------------------------------- |
103
+ | `-t {p,m,s,l,k,a,f,o,d}` | Database type |
104
+ | `-u USER` | Database username |
105
+ | `-p PORT` | Server port |
106
+ | `-a VALUE` | Set substitution variable `$ARG_x` |
107
+ | `-b` / `--boolean-int` | Treat integers 0 and 1 as boolean values |
108
+ | `-c SCRIPT` | Execute inline SQL or metacommand string |
109
+ | `-d` | Auto-create export directories |
110
+ | `-e ENCODING` / `--database-encoding` | Character encoding used in the database |
111
+ | `-f ENCODING` | Script file encoding (default: UTF-8) |
112
+ | `-g ENCODING` / `--output-encoding` | Encoding for WRITE and EXPORT output |
113
+ | `-i ENCODING` / `--import-encoding` | Encoding for data files used with IMPORT |
114
+ | `-l` | Write run log to `~/execsql.log` |
115
+ | `-m` | List metacommands and exit |
116
+ | `-n` | Create a new SQLite or PostgreSQL database if it does not exist |
117
+ | `-o` / `--online-help` | Open the online documentation in the default browser |
118
+ | `-s N` / `--scan-lines` | Lines to scan for IMPORT format detection (0 = scan entire file) |
119
+ | `-v {0,1,2,3}` | GUI level (0=none, 1=password, 2=selection, 3=full) |
120
+ | `-w` | Skip password prompt when a username is supplied |
121
+ | `-y` / `--encodings` | List available encoding names and exit |
122
+ | `-z KB` / `--import-buffer` | Import buffer size in KB (default: 32) |
123
+ | `--dsn URL` | Connection string (e.g. `postgresql://user:pass@host/db`) |
124
+ | `--output-dir DIR` | Default base directory for EXPORT output files |
125
+ | `--dry-run` | Parse the script and report commands without executing |
126
+ | `--lint` | Static analysis: check structure and warn on issues (no DB) |
127
+ | `--parse-tree` | Print the script's AST structure and exit (no DB) |
128
+ | `--list-plugins` | List discovered plugins and exit |
129
+ | `--ping` | Test database connectivity and exit |
130
+ | `--profile` | Show per-statement timing summary after execution |
131
+ | `--progress` | Show a progress bar for long-running IMPORT operations |
132
+ | `--config FILE` | Load an explicit config file (highest priority after CLI args) |
133
+ | `--debug` | Start in step-through debug mode (REPL pauses before each stmt) |
134
+ | `--dump-keywords` | Print metacommand keywords as JSON and exit |
135
+ | `--gui-framework {tkinter,textual}` | GUI framework for interactive prompts |
125
136
 
126
137
  Run `execsql --help` for the full option list, or `execsql -m` to list all metacommands.
127
138
 
@@ -138,6 +149,61 @@ Run `execsql --help` for the full option list, or `execsql -m` to list all metac
138
149
  - Display query results in a GUI dialog; optionally prompt the user to select a row, enter a value, or submit a form.
139
150
  - Write status messages or tabular output to the console or a file during execution.
140
151
  - Automatically log each run, recording databases used, scripts executed, and user responses.
152
+ - Extend with custom metacommands, exporters, and importers via the plugin system.
153
+
154
+ # Library API
155
+
156
+ execsql can be used as a Python library for programmatic script execution:
157
+
158
+ ```python
159
+ from execsql import run
160
+
161
+ # Execute a script file
162
+ result = run(script="pipeline.sql", dsn="postgresql://user:pass@host/db")
163
+
164
+ # Execute inline SQL
165
+ result = run(
166
+ sql="CREATE TABLE t (id INT);\nINSERT INTO t VALUES (1);",
167
+ dsn="sqlite:///my.db",
168
+ new_db=True,
169
+ )
170
+
171
+ # With substitution variables
172
+ result = run(
173
+ script="etl.sql",
174
+ dsn="sqlite:///data.db",
175
+ variables={"SCHEMA": "public", "DATE": "2026-01-01"},
176
+ )
177
+
178
+ # Check results
179
+ print(result.success) # True
180
+ print(result.commands_run) # 2
181
+ print(result.elapsed) # 0.003 (seconds)
182
+ print(result.variables) # {"SCHEMA": "public", ...}
183
+ ```
184
+
185
+ Error handling:
186
+
187
+ ```python
188
+ result = run(sql="SELECT * FROM nonexistent;", dsn="sqlite:///:memory:")
189
+ if not result.success:
190
+ for err in result.errors:
191
+ print(f"{err.source}:{err.line}: {err.message}")
192
+
193
+ # Or raise on failure
194
+ result.raise_on_error() # raises ExecSqlError
195
+ ```
196
+
197
+ Use a pre-existing database connection instead of a DSN:
198
+
199
+ ```python
200
+ from execsql.db.factory import db_SQLite
201
+ conn = db_SQLite("my.db", new_db=True)
202
+ result = run(sql="SELECT 1;", connection=conn)
203
+ # run() does NOT close this connection — you manage its lifecycle
204
+ ```
205
+
206
+ Each call to `run()` uses an isolated `RuntimeContext`, so multiple calls do not share state.
141
207
 
142
208
  # An Illustration
143
209
 
@@ -1,4 +1,7 @@
1
1
  # Contributors
2
2
 
3
+ R. Dreas Nielsen
4
+ : Original author of [execsql](https://execsql.readthedocs.io/) (v1.0 through v1.130.1). Designed and implemented the metacommand language, substitution variable system, database adapters, export/import framework, GUI prompt system, and all core functionality that execsql2 is built on.
5
+
3
6
  Elizabeth Shea
4
7
  : Brainstorming, testing, coding of the conditional expression parser, coding of the 'with arguments' and 'with parameters' clauses of the [SCRIPT](../reference/metacommands.md#beginscript) metacommand, coding of the WHILE and UNTIL clauses of the [EXECUTE SCRIPT](../reference/metacommands.md#executescript) metacommand, coding of [deferred variable substitution](../reference/substitution_vars.md#deferred_substitution), corrections to the [PROMPT ENTRY_FORM](../reference/metacommands.md#prompt_entry) metacommand, examples, implementation of the ["+" prefix](../reference/substitution_vars.md#outer_scope_referent) for substitution variables, and other minor corrections.