execsql2 2.16.17__tar.gz → 2.16.18__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 (323) hide show
  1. {execsql2-2.16.17 → execsql2-2.16.18}/CHANGELOG.md +13 -0
  2. {execsql2-2.16.17 → execsql2-2.16.18}/PKG-INFO +1 -1
  3. {execsql2-2.16.17 → execsql2-2.16.18}/docs/about/divergence.md +9 -7
  4. {execsql2-2.16.17 → execsql2-2.16.18}/docs/reference/metacommands.md +22 -2
  5. {execsql2-2.16.17 → execsql2-2.16.18}/pyproject.toml +2 -2
  6. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/debug/repl.py +1 -1
  7. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/metacommands/debug.py +14 -4
  8. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/script/parser.py +45 -5
  9. {execsql2-2.16.17 → execsql2-2.16.18}/tests/metacommands/test_show_scripts.py +64 -0
  10. {execsql2-2.16.17 → execsql2-2.16.18}/tests/test_debug_repl.py +50 -0
  11. {execsql2-2.16.17 → execsql2-2.16.18}/tests/test_executor.py +66 -0
  12. execsql2-2.16.18/tests/test_parser_params.py +124 -0
  13. {execsql2-2.16.17 → execsql2-2.16.18}/uv.lock +1 -1
  14. execsql2-2.16.17/tests/test_parser_params.py +0 -56
  15. {execsql2-2.16.17 → execsql2-2.16.18}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  16. {execsql2-2.16.17 → execsql2-2.16.18}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  17. {execsql2-2.16.17 → execsql2-2.16.18}/.github/workflows/ci-cd.yml +0 -0
  18. {execsql2-2.16.17 → execsql2-2.16.18}/.gitignore +0 -0
  19. {execsql2-2.16.17 → execsql2-2.16.18}/.pre-commit-config.yaml +0 -0
  20. {execsql2-2.16.17 → execsql2-2.16.18}/.pre-commit-hooks.yaml +0 -0
  21. {execsql2-2.16.17 → execsql2-2.16.18}/.python-version +0 -0
  22. {execsql2-2.16.17 → execsql2-2.16.18}/.readthedocs.yaml +0 -0
  23. {execsql2-2.16.17 → execsql2-2.16.18}/CONTRIBUTING.md +0 -0
  24. {execsql2-2.16.17 → execsql2-2.16.18}/LICENSE.txt +0 -0
  25. {execsql2-2.16.17 → execsql2-2.16.18}/NOTICE +0 -0
  26. {execsql2-2.16.17 → execsql2-2.16.18}/README.md +0 -0
  27. {execsql2-2.16.17 → execsql2-2.16.18}/SECURITY.md +0 -0
  28. {execsql2-2.16.17 → execsql2-2.16.18}/docs/about/contributors.md +0 -0
  29. {execsql2-2.16.17 → execsql2-2.16.18}/docs/about/copyright.md +0 -0
  30. {execsql2-2.16.17 → execsql2-2.16.18}/docs/api/cli.md +0 -0
  31. {execsql2-2.16.17 → execsql2-2.16.18}/docs/api/db.md +0 -0
  32. {execsql2-2.16.17 → execsql2-2.16.18}/docs/api/exporters.md +0 -0
  33. {execsql2-2.16.17 → execsql2-2.16.18}/docs/api/importers.md +0 -0
  34. {execsql2-2.16.17 → execsql2-2.16.18}/docs/api/index.md +0 -0
  35. {execsql2-2.16.17 → execsql2-2.16.18}/docs/api/metacommands.md +0 -0
  36. {execsql2-2.16.17 → execsql2-2.16.18}/docs/dev/adding_db_adapters.md +0 -0
  37. {execsql2-2.16.17 → execsql2-2.16.18}/docs/dev/adding_exporters.md +0 -0
  38. {execsql2-2.16.17 → execsql2-2.16.18}/docs/dev/adding_importers.md +0 -0
  39. {execsql2-2.16.17 → execsql2-2.16.18}/docs/dev/adding_metacommands.md +0 -0
  40. {execsql2-2.16.17 → execsql2-2.16.18}/docs/dev/architecture.md +0 -0
  41. {execsql2-2.16.17 → execsql2-2.16.18}/docs/getting-started/installation.md +0 -0
  42. {execsql2-2.16.17 → execsql2-2.16.18}/docs/getting-started/requirements.md +0 -0
  43. {execsql2-2.16.17 → execsql2-2.16.18}/docs/getting-started/syntax.md +0 -0
  44. {execsql2-2.16.17 → execsql2-2.16.18}/docs/guides/debugging.md +0 -0
  45. {execsql2-2.16.17 → execsql2-2.16.18}/docs/guides/documentation.md +0 -0
  46. {execsql2-2.16.17 → execsql2-2.16.18}/docs/guides/encoding.md +0 -0
  47. {execsql2-2.16.17 → execsql2-2.16.18}/docs/guides/examples.md +0 -0
  48. {execsql2-2.16.17 → execsql2-2.16.18}/docs/guides/formatter.md +0 -0
  49. {execsql2-2.16.17 → execsql2-2.16.18}/docs/guides/logging.md +0 -0
  50. {execsql2-2.16.17 → execsql2-2.16.18}/docs/guides/sql_syntax.md +0 -0
  51. {execsql2-2.16.17 → execsql2-2.16.18}/docs/guides/usage.md +0 -0
  52. {execsql2-2.16.17 → execsql2-2.16.18}/docs/guides/using_scripts.md +0 -0
  53. {execsql2-2.16.17 → execsql2-2.16.18}/docs/images/Compare_planets.png +0 -0
  54. {execsql2-2.16.17 → execsql2-2.16.18}/docs/images/actions.png +0 -0
  55. {execsql2-2.16.17 → execsql2-2.16.18}/docs/images/actions2.png +0 -0
  56. {execsql2-2.16.17 → execsql2-2.16.18}/docs/images/checkboxes.png +0 -0
  57. {execsql2-2.16.17 → execsql2-2.16.18}/docs/images/connect.b64 +0 -0
  58. {execsql2-2.16.17 → execsql2-2.16.18}/docs/images/connect.png +0 -0
  59. {execsql2-2.16.17 → execsql2-2.16.18}/docs/images/create_conf.png +0 -0
  60. {execsql2-2.16.17 → execsql2-2.16.18}/docs/images/data_error1_screenshot.jpg +0 -0
  61. {execsql2-2.16.17 → execsql2-2.16.18}/docs/images/entry_form.png +0 -0
  62. {execsql2-2.16.17 → execsql2-2.16.18}/docs/images/execsql_console.png +0 -0
  63. {execsql2-2.16.17 → execsql2-2.16.18}/docs/images/execsql_logo_01.png +0 -0
  64. {execsql2-2.16.17 → execsql2-2.16.18}/docs/images/fatals.png +0 -0
  65. {execsql2-2.16.17 → execsql2-2.16.18}/docs/images/logo_small.png +0 -0
  66. {execsql2-2.16.17 → execsql2-2.16.18}/docs/images/pause_terminal.png +0 -0
  67. {execsql2-2.16.17 → execsql2-2.16.18}/docs/images/pause_terminal_sm.b64 +0 -0
  68. {execsql2-2.16.17 → execsql2-2.16.18}/docs/images/pause_terminal_sm.png +0 -0
  69. {execsql2-2.16.17 → execsql2-2.16.18}/docs/images/prompt_compare.png +0 -0
  70. {execsql2-2.16.17 → execsql2-2.16.18}/docs/images/set_build_commands.jpg +0 -0
  71. {execsql2-2.16.17 → execsql2-2.16.18}/docs/images/unit_conversions.b64 +0 -0
  72. {execsql2-2.16.17 → execsql2-2.16.18}/docs/images/unit_conversions_029.png +0 -0
  73. {execsql2-2.16.17 → execsql2-2.16.18}/docs/images/unmatched.png +0 -0
  74. {execsql2-2.16.17 → execsql2-2.16.18}/docs/images/vim_execsql_highlight.png +0 -0
  75. {execsql2-2.16.17 → execsql2-2.16.18}/docs/index.md +0 -0
  76. {execsql2-2.16.17 → execsql2-2.16.18}/docs/reference/configuration.md +0 -0
  77. {execsql2-2.16.17 → execsql2-2.16.18}/docs/reference/security.md +0 -0
  78. {execsql2-2.16.17 → execsql2-2.16.18}/docs/reference/substitution_vars.md +0 -0
  79. {execsql2-2.16.17 → execsql2-2.16.18}/extras/plugin-template/README.md +0 -0
  80. {execsql2-2.16.17 → execsql2-2.16.18}/extras/plugin-template/pyproject.toml +0 -0
  81. {execsql2-2.16.17 → execsql2-2.16.18}/extras/plugin-template/src/execsql_plugin_YOURNAME/__init__.py +0 -0
  82. {execsql2-2.16.17 → execsql2-2.16.18}/extras/plugin-template/tests/test_plugin.py.example +0 -0
  83. {execsql2-2.16.17 → execsql2-2.16.18}/extras/vscode-execsql/README.md +0 -0
  84. {execsql2-2.16.17 → execsql2-2.16.18}/extras/vscode-execsql/package.json +0 -0
  85. {execsql2-2.16.17 → execsql2-2.16.18}/extras/vscode-execsql/syntaxes/execsql.tmLanguage.json +0 -0
  86. {execsql2-2.16.17 → execsql2-2.16.18}/justfile +0 -0
  87. {execsql2-2.16.17 → execsql2-2.16.18}/scripts/generate_vscode_grammar.py +0 -0
  88. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/__init__.py +0 -0
  89. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/__main__.py +0 -0
  90. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/api.py +0 -0
  91. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/cli/__init__.py +0 -0
  92. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/cli/dsn.py +0 -0
  93. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/cli/help.py +0 -0
  94. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/cli/lint.py +0 -0
  95. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/cli/lint_ast.py +0 -0
  96. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/cli/run.py +0 -0
  97. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/config.py +0 -0
  98. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/data/__init__.py +0 -0
  99. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/data/execsql.conf.template +0 -0
  100. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/db/__init__.py +0 -0
  101. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/db/access.py +0 -0
  102. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/db/base.py +0 -0
  103. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/db/dsn.py +0 -0
  104. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/db/duckdb.py +0 -0
  105. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/db/factory.py +0 -0
  106. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/db/firebird.py +0 -0
  107. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/db/mysql.py +0 -0
  108. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/db/oracle.py +0 -0
  109. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/db/postgres.py +0 -0
  110. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/db/sqlite.py +0 -0
  111. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/db/sqlserver.py +0 -0
  112. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/debug/__init__.py +0 -0
  113. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/exceptions.py +0 -0
  114. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/exporters/__init__.py +0 -0
  115. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/exporters/base.py +0 -0
  116. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/exporters/delimited.py +0 -0
  117. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/exporters/duckdb.py +0 -0
  118. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/exporters/feather.py +0 -0
  119. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/exporters/html.py +0 -0
  120. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/exporters/json.py +0 -0
  121. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/exporters/latex.py +0 -0
  122. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/exporters/markdown.py +0 -0
  123. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/exporters/ods.py +0 -0
  124. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/exporters/parquet.py +0 -0
  125. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/exporters/pretty.py +0 -0
  126. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/exporters/protocol.py +0 -0
  127. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/exporters/raw.py +0 -0
  128. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/exporters/sqlite.py +0 -0
  129. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/exporters/templates.py +0 -0
  130. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/exporters/values.py +0 -0
  131. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/exporters/xls.py +0 -0
  132. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/exporters/xlsx.py +0 -0
  133. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/exporters/xml.py +0 -0
  134. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/exporters/yaml.py +0 -0
  135. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/exporters/zip.py +0 -0
  136. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/format.py +0 -0
  137. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/gui/__init__.py +0 -0
  138. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/gui/base.py +0 -0
  139. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/gui/console.py +0 -0
  140. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/gui/desktop.py +0 -0
  141. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/gui/tui.py +0 -0
  142. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/importers/__init__.py +0 -0
  143. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/importers/base.py +0 -0
  144. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/importers/csv.py +0 -0
  145. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/importers/feather.py +0 -0
  146. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/importers/json.py +0 -0
  147. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/importers/ods.py +0 -0
  148. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/importers/xls.py +0 -0
  149. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/metacommands/__init__.py +0 -0
  150. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/metacommands/conditions.py +0 -0
  151. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/metacommands/connect.py +0 -0
  152. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/metacommands/control.py +0 -0
  153. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/metacommands/data.py +0 -0
  154. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/metacommands/dispatch.py +0 -0
  155. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/metacommands/io.py +0 -0
  156. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/metacommands/io_export.py +0 -0
  157. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/metacommands/io_fileops.py +0 -0
  158. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/metacommands/io_import.py +0 -0
  159. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/metacommands/io_write.py +0 -0
  160. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/metacommands/prompt.py +0 -0
  161. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/metacommands/script_ext.py +0 -0
  162. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/metacommands/system.py +0 -0
  163. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/metacommands/upsert.py +0 -0
  164. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/models.py +0 -0
  165. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/parser.py +0 -0
  166. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/plugins.py +0 -0
  167. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/py.typed +0 -0
  168. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/script/__init__.py +0 -0
  169. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/script/ast.py +0 -0
  170. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/script/control.py +0 -0
  171. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/script/engine.py +0 -0
  172. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/script/executor.py +0 -0
  173. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/script/variables.py +0 -0
  174. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/state.py +0 -0
  175. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/types.py +0 -0
  176. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/utils/__init__.py +0 -0
  177. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/utils/auth.py +0 -0
  178. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/utils/crypto.py +0 -0
  179. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/utils/datetime.py +0 -0
  180. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/utils/errors.py +0 -0
  181. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/utils/fileio.py +0 -0
  182. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/utils/gui.py +0 -0
  183. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/utils/mail.py +0 -0
  184. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/utils/numeric.py +0 -0
  185. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/utils/regex.py +0 -0
  186. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/utils/strings.py +0 -0
  187. {execsql2-2.16.17 → execsql2-2.16.18}/src/execsql/utils/timer.py +0 -0
  188. {execsql2-2.16.17 → execsql2-2.16.18}/templates/README.md +0 -0
  189. {execsql2-2.16.17 → execsql2-2.16.18}/templates/config_settings.sqlite +0 -0
  190. {execsql2-2.16.17 → execsql2-2.16.18}/templates/example_config_prompt.sql +0 -0
  191. {execsql2-2.16.17 → execsql2-2.16.18}/templates/execsql.conf +0 -0
  192. {execsql2-2.16.17 → execsql2-2.16.18}/templates/make_config_db.sql +0 -0
  193. {execsql2-2.16.17 → execsql2-2.16.18}/templates/md_compare.sql +0 -0
  194. {execsql2-2.16.17 → execsql2-2.16.18}/templates/md_glossary.sql +0 -0
  195. {execsql2-2.16.17 → execsql2-2.16.18}/templates/md_upsert.sql +0 -0
  196. {execsql2-2.16.17 → execsql2-2.16.18}/templates/pg_compare.sql +0 -0
  197. {execsql2-2.16.17 → execsql2-2.16.18}/templates/pg_glossary.sql +0 -0
  198. {execsql2-2.16.17 → execsql2-2.16.18}/templates/pg_upsert.sql +0 -0
  199. {execsql2-2.16.17 → execsql2-2.16.18}/templates/script_template.sql +0 -0
  200. {execsql2-2.16.17 → execsql2-2.16.18}/templates/ss_compare.sql +0 -0
  201. {execsql2-2.16.17 → execsql2-2.16.18}/templates/ss_glossary.sql +0 -0
  202. {execsql2-2.16.17 → execsql2-2.16.18}/templates/ss_upsert.sql +0 -0
  203. {execsql2-2.16.17 → execsql2-2.16.18}/tests/__init__.py +0 -0
  204. {execsql2-2.16.17 → execsql2-2.16.18}/tests/cli/__init__.py +0 -0
  205. {execsql2-2.16.17 → execsql2-2.16.18}/tests/cli/test_cli.py +0 -0
  206. {execsql2-2.16.17 → execsql2-2.16.18}/tests/cli/test_cli_e2e.py +0 -0
  207. {execsql2-2.16.17 → execsql2-2.16.18}/tests/cli/test_cli_run.py +0 -0
  208. {execsql2-2.16.17 → execsql2-2.16.18}/tests/cli/test_lint.py +0 -0
  209. {execsql2-2.16.17 → execsql2-2.16.18}/tests/cli/test_ping.py +0 -0
  210. {execsql2-2.16.17 → execsql2-2.16.18}/tests/cli/test_profile.py +0 -0
  211. {execsql2-2.16.17 → execsql2-2.16.18}/tests/conftest.py +0 -0
  212. {execsql2-2.16.17 → execsql2-2.16.18}/tests/db/__init__.py +0 -0
  213. {execsql2-2.16.17 → execsql2-2.16.18}/tests/db/test_base.py +0 -0
  214. {execsql2-2.16.17 → execsql2-2.16.18}/tests/db/test_db_adapters_mocked.py +0 -0
  215. {execsql2-2.16.17 → execsql2-2.16.18}/tests/db/test_dsn.py +0 -0
  216. {execsql2-2.16.17 → execsql2-2.16.18}/tests/db/test_duckdb.py +0 -0
  217. {execsql2-2.16.17 → execsql2-2.16.18}/tests/db/test_factory.py +0 -0
  218. {execsql2-2.16.17 → execsql2-2.16.18}/tests/db/test_postgres.py +0 -0
  219. {execsql2-2.16.17 → execsql2-2.16.18}/tests/db/test_sqlite.py +0 -0
  220. {execsql2-2.16.17 → execsql2-2.16.18}/tests/db/test_sqlite_extra.py +0 -0
  221. {execsql2-2.16.17 → execsql2-2.16.18}/tests/exporters/__init__.py +0 -0
  222. {execsql2-2.16.17 → execsql2-2.16.18}/tests/exporters/test_base.py +0 -0
  223. {execsql2-2.16.17 → execsql2-2.16.18}/tests/exporters/test_db.py +0 -0
  224. {execsql2-2.16.17 → execsql2-2.16.18}/tests/exporters/test_delimited.py +0 -0
  225. {execsql2-2.16.17 → execsql2-2.16.18}/tests/exporters/test_duckdb_exporter.py +0 -0
  226. {execsql2-2.16.17 → execsql2-2.16.18}/tests/exporters/test_exporters.py +0 -0
  227. {execsql2-2.16.17 → execsql2-2.16.18}/tests/exporters/test_feather.py +0 -0
  228. {execsql2-2.16.17 → execsql2-2.16.18}/tests/exporters/test_html_extended.py +0 -0
  229. {execsql2-2.16.17 → execsql2-2.16.18}/tests/exporters/test_html_latex.py +0 -0
  230. {execsql2-2.16.17 → execsql2-2.16.18}/tests/exporters/test_json.py +0 -0
  231. {execsql2-2.16.17 → execsql2-2.16.18}/tests/exporters/test_json_extended.py +0 -0
  232. {execsql2-2.16.17 → execsql2-2.16.18}/tests/exporters/test_latex_extended.py +0 -0
  233. {execsql2-2.16.17 → execsql2-2.16.18}/tests/exporters/test_markdown.py +0 -0
  234. {execsql2-2.16.17 → execsql2-2.16.18}/tests/exporters/test_ods.py +0 -0
  235. {execsql2-2.16.17 → execsql2-2.16.18}/tests/exporters/test_parquet.py +0 -0
  236. {execsql2-2.16.17 → execsql2-2.16.18}/tests/exporters/test_pretty_extended.py +0 -0
  237. {execsql2-2.16.17 → execsql2-2.16.18}/tests/exporters/test_raw_extended.py +0 -0
  238. {execsql2-2.16.17 → execsql2-2.16.18}/tests/exporters/test_sqlite_exporter.py +0 -0
  239. {execsql2-2.16.17 → execsql2-2.16.18}/tests/exporters/test_templates.py +0 -0
  240. {execsql2-2.16.17 → execsql2-2.16.18}/tests/exporters/test_templates_extended.py +0 -0
  241. {execsql2-2.16.17 → execsql2-2.16.18}/tests/exporters/test_values_extended.py +0 -0
  242. {execsql2-2.16.17 → execsql2-2.16.18}/tests/exporters/test_xls_xlsx.py +0 -0
  243. {execsql2-2.16.17 → execsql2-2.16.18}/tests/exporters/test_xlsx.py +0 -0
  244. {execsql2-2.16.17 → execsql2-2.16.18}/tests/exporters/test_xml.py +0 -0
  245. {execsql2-2.16.17 → execsql2-2.16.18}/tests/exporters/test_yaml.py +0 -0
  246. {execsql2-2.16.17 → execsql2-2.16.18}/tests/exporters/test_zip.py +0 -0
  247. {execsql2-2.16.17 → execsql2-2.16.18}/tests/gui/__init__.py +0 -0
  248. {execsql2-2.16.17 → execsql2-2.16.18}/tests/gui/test_backends.py +0 -0
  249. {execsql2-2.16.17 → execsql2-2.16.18}/tests/gui/test_compare_stats.py +0 -0
  250. {execsql2-2.16.17 → execsql2-2.16.18}/tests/gui/test_compute_row_diffs.py +0 -0
  251. {execsql2-2.16.17 → execsql2-2.16.18}/tests/importers/__init__.py +0 -0
  252. {execsql2-2.16.17 → execsql2-2.16.18}/tests/importers/test_base_extended.py +0 -0
  253. {execsql2-2.16.17 → execsql2-2.16.18}/tests/importers/test_csv_edge_cases.py +0 -0
  254. {execsql2-2.16.17 → execsql2-2.16.18}/tests/importers/test_csv_importer.py +0 -0
  255. {execsql2-2.16.17 → execsql2-2.16.18}/tests/importers/test_feather_importer.py +0 -0
  256. {execsql2-2.16.17 → execsql2-2.16.18}/tests/importers/test_json_importer.py +0 -0
  257. {execsql2-2.16.17 → execsql2-2.16.18}/tests/importers/test_ods_importer.py +0 -0
  258. {execsql2-2.16.17 → execsql2-2.16.18}/tests/importers/test_xls_importer.py +0 -0
  259. {execsql2-2.16.17 → execsql2-2.16.18}/tests/integration/__init__.py +0 -0
  260. {execsql2-2.16.17 → execsql2-2.16.18}/tests/integration/conftest.py +0 -0
  261. {execsql2-2.16.17 → execsql2-2.16.18}/tests/integration/test_dsn.py +0 -0
  262. {execsql2-2.16.17 → execsql2-2.16.18}/tests/integration/test_duckdb.py +0 -0
  263. {execsql2-2.16.17 → execsql2-2.16.18}/tests/integration/test_mysql.py +0 -0
  264. {execsql2-2.16.17 → execsql2-2.16.18}/tests/integration/test_postgres.py +0 -0
  265. {execsql2-2.16.17 → execsql2-2.16.18}/tests/integration/test_sqlite.py +0 -0
  266. {execsql2-2.16.17 → execsql2-2.16.18}/tests/metacommands/__init__.py +0 -0
  267. {execsql2-2.16.17 → execsql2-2.16.18}/tests/metacommands/test_assert.py +0 -0
  268. {execsql2-2.16.17 → execsql2-2.16.18}/tests/metacommands/test_breakpoint.py +0 -0
  269. {execsql2-2.16.17 → execsql2-2.16.18}/tests/metacommands/test_connect.py +0 -0
  270. {execsql2-2.16.17 → execsql2-2.16.18}/tests/metacommands/test_io_export.py +0 -0
  271. {execsql2-2.16.17 → execsql2-2.16.18}/tests/metacommands/test_io_import.py +0 -0
  272. {execsql2-2.16.17 → execsql2-2.16.18}/tests/metacommands/test_metacommands.py +0 -0
  273. {execsql2-2.16.17 → execsql2-2.16.18}/tests/metacommands/test_metacommands_connect.py +0 -0
  274. {execsql2-2.16.17 → execsql2-2.16.18}/tests/metacommands/test_metacommands_data.py +0 -0
  275. {execsql2-2.16.17 → execsql2-2.16.18}/tests/metacommands/test_metacommands_extended.py +0 -0
  276. {execsql2-2.16.17 → execsql2-2.16.18}/tests/metacommands/test_metacommands_fileops_extra.py +0 -0
  277. {execsql2-2.16.17 → execsql2-2.16.18}/tests/metacommands/test_metacommands_io.py +0 -0
  278. {execsql2-2.16.17 → execsql2-2.16.18}/tests/metacommands/test_metacommands_io_write_extra.py +0 -0
  279. {execsql2-2.16.17 → execsql2-2.16.18}/tests/metacommands/test_metacommands_script_ext.py +0 -0
  280. {execsql2-2.16.17 → execsql2-2.16.18}/tests/metacommands/test_metacommands_system.py +0 -0
  281. {execsql2-2.16.17 → execsql2-2.16.18}/tests/metacommands/test_metacommands_system_extra.py +0 -0
  282. {execsql2-2.16.17 → execsql2-2.16.18}/tests/metacommands/test_pg_upsert.py +0 -0
  283. {execsql2-2.16.17 → execsql2-2.16.18}/tests/metacommands/test_row_count.py +0 -0
  284. {execsql2-2.16.17 → execsql2-2.16.18}/tests/scripts/__init__.py +0 -0
  285. {execsql2-2.16.17 → execsql2-2.16.18}/tests/scripts/fixtures/control_flow.sql +0 -0
  286. {execsql2-2.16.17 → execsql2-2.16.18}/tests/scripts/fixtures/io_roundtrip.sql +0 -0
  287. {execsql2-2.16.17 → execsql2-2.16.18}/tests/scripts/fixtures/parse_only/parse_tree.sql +0 -0
  288. {execsql2-2.16.17 → execsql2-2.16.18}/tests/scripts/fixtures/smoke.sql +0 -0
  289. {execsql2-2.16.17 → execsql2-2.16.18}/tests/scripts/test_sql_scripts.py +0 -0
  290. {execsql2-2.16.17 → execsql2-2.16.18}/tests/test_api.py +0 -0
  291. {execsql2-2.16.17 → execsql2-2.16.18}/tests/test_ast.py +0 -0
  292. {execsql2-2.16.17 → execsql2-2.16.18}/tests/test_ast_parser.py +0 -0
  293. {execsql2-2.16.17 → execsql2-2.16.18}/tests/test_config.py +0 -0
  294. {execsql2-2.16.17 → execsql2-2.16.18}/tests/test_config_data.py +0 -0
  295. {execsql2-2.16.17 → execsql2-2.16.18}/tests/test_config_extended.py +0 -0
  296. {execsql2-2.16.17 → execsql2-2.16.18}/tests/test_engine.py +0 -0
  297. {execsql2-2.16.17 → execsql2-2.16.18}/tests/test_error_messages.py +0 -0
  298. {execsql2-2.16.17 → execsql2-2.16.18}/tests/test_exceptions.py +0 -0
  299. {execsql2-2.16.17 → execsql2-2.16.18}/tests/test_format.py +0 -0
  300. {execsql2-2.16.17 → execsql2-2.16.18}/tests/test_mail.py +0 -0
  301. {execsql2-2.16.17 → execsql2-2.16.18}/tests/test_models.py +0 -0
  302. {execsql2-2.16.17 → execsql2-2.16.18}/tests/test_package.py +0 -0
  303. {execsql2-2.16.17 → execsql2-2.16.18}/tests/test_parser.py +0 -0
  304. {execsql2-2.16.17 → execsql2-2.16.18}/tests/test_plugins.py +0 -0
  305. {execsql2-2.16.17 → execsql2-2.16.18}/tests/test_registry.py +0 -0
  306. {execsql2-2.16.17 → execsql2-2.16.18}/tests/test_script.py +0 -0
  307. {execsql2-2.16.17 → execsql2-2.16.18}/tests/test_state.py +0 -0
  308. {execsql2-2.16.17 → execsql2-2.16.18}/tests/test_types.py +0 -0
  309. {execsql2-2.16.17 → execsql2-2.16.18}/tests/utils/__init__.py +0 -0
  310. {execsql2-2.16.17 → execsql2-2.16.18}/tests/utils/test_auth.py +0 -0
  311. {execsql2-2.16.17 → execsql2-2.16.18}/tests/utils/test_auth_extra.py +0 -0
  312. {execsql2-2.16.17 → execsql2-2.16.18}/tests/utils/test_crypto.py +0 -0
  313. {execsql2-2.16.17 → execsql2-2.16.18}/tests/utils/test_datetime.py +0 -0
  314. {execsql2-2.16.17 → execsql2-2.16.18}/tests/utils/test_errors.py +0 -0
  315. {execsql2-2.16.17 → execsql2-2.16.18}/tests/utils/test_errors_extra.py +0 -0
  316. {execsql2-2.16.17 → execsql2-2.16.18}/tests/utils/test_fileio.py +0 -0
  317. {execsql2-2.16.17 → execsql2-2.16.18}/tests/utils/test_fileio_extra.py +0 -0
  318. {execsql2-2.16.17 → execsql2-2.16.18}/tests/utils/test_numeric.py +0 -0
  319. {execsql2-2.16.17 → execsql2-2.16.18}/tests/utils/test_regex.py +0 -0
  320. {execsql2-2.16.17 → execsql2-2.16.18}/tests/utils/test_strings.py +0 -0
  321. {execsql2-2.16.17 → execsql2-2.16.18}/tests/utils/test_timer.py +0 -0
  322. {execsql2-2.16.17 → execsql2-2.16.18}/tests/utils/test_timer_extra.py +0 -0
  323. {execsql2-2.16.17 → execsql2-2.16.18}/zensical.toml +0 -0
@@ -13,6 +13,19 @@ ______________________________________________________________________
13
13
 
14
14
  ______________________________________________________________________
15
15
 
16
+ ## [2.16.18] - 2026-05-05
17
+
18
+ ### Fixed
19
+
20
+ - `BEGIN SCRIPT` parameter defaults now correctly strip surrounding quotes when stored, mirroring the quote-handling already applied to passed arguments at the call site. Previously a default written as `default_unit_set="Default"` bound the literal string `"Default"` (with quotes intact), so a body like `WRITE "!!#default_unit_set!!"` produced `WRITE ""Default""` and failed. Defaults and passed arguments now resolve to the same value for the same source token.
21
+
22
+ ### Changed
23
+
24
+ - `SHOW SCRIPTS <name>` metacommand and `.scripts <name>` debug REPL command now display the full source path to the script's source file (including `<inline>` for scripts loaded via `execsql -c`). The list views (`SHOW SCRIPTS` and `.scripts` without a name) continue to show the basename for compact column-aligned output.
25
+ - `BEGIN SCRIPT WITH PARAMETERS (...)` now accepts quoted default values containing spaces, commas, and other special characters — e.g. `proc(msg="hello, world", path="/var/log/app.log")`. Both single and double quotes are supported. Unquoted values continue to be accepted but cannot start with a quote character; an unterminated quoted value is now rejected as malformed instead of being silently stored as an unquoted literal.
26
+
27
+ ______________________________________________________________________
28
+
16
29
  ## [2.16.17] - 2026-05-04
17
30
 
18
31
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: execsql2
3
- Version: 2.16.17
3
+ Version: 2.16.18
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
@@ -58,16 +58,18 @@ ______________________________________________________________________
58
58
 
59
59
  ### SCRIPT Enhancements
60
60
 
61
- | Feature | Description |
62
- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
63
- | Default parameters | `BEGIN SCRIPT load(schema, table, batch=1000)` — parameters with defaults can be omitted at call site. Required parameters must precede optional parameters. |
64
- | Docstrings | Comments (`--` or `/* */`) immediately following `BEGIN SCRIPT` are captured as documentation. A blank line terminates the docstring. Displayed by `SHOW SCRIPTS <name>` and `.scripts <name>` REPL command. |
61
+ | Feature | Description |
62
+ | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
63
+ | Default parameters | `BEGIN SCRIPT load(schema, table, batch=1000)` — parameters with defaults can be omitted at call site. Required parameters must precede optional parameters. |
64
+ | Quoted defaults | Default values may be quoted with single or double quotes to embed spaces, commas, or other special characters: `BEGIN SCRIPT proc(msg="hello, world", path="/var/log/app.log")`. Surrounding quotes are stripped at parse time so the bound substitution variable holds the value itself, matching the call-site quote-handling for passed arguments. |
65
+ | Docstrings | Comments (`--` or `/* */`) immediately following `BEGIN SCRIPT` are captured as documentation. A blank line terminates the docstring. Displayed by `SHOW SCRIPTS <name>` and `.scripts <name>` REPL command. |
65
66
 
66
67
  ### Bug Fixes
67
68
 
68
- | Fix | Description |
69
- | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
70
- | Variable EXECUTE SCRIPT targets | `EXECUTE SCRIPT !!#var!!` now works — the parser accepts substitution variable patterns as script identifiers, and the executor resolves them at runtime. |
69
+ | Fix | Description |
70
+ | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
71
+ | Variable EXECUTE SCRIPT targets | `EXECUTE SCRIPT !!#var!!` now works — the parser accepts substitution variable patterns as script identifiers, and the executor resolves them at runtime. |
72
+ | Quoted parameter defaults | `BEGIN SCRIPT proc(name="value")` previously stored the literal `"value"` (with surrounding quotes), causing substitutions like `WRITE "!!#name!!"` to expand to `WRITE ""value""` and fail. Defaults now strip surrounding quotes at parse time, matching the `wo_quotes` handling already applied to passed arguments. |
71
73
 
72
74
  ### Conditional Tests
73
75
 
@@ -260,6 +260,24 @@ A required parameter after an optional parameter is a parse error:
260
260
  -- !x! BEGIN SCRIPT bad(schema, batch=1000, table)
261
261
  ```
262
262
 
263
+ Default values may be quoted with single or double quotes when they contain spaces, commas, or other special characters. Surrounding quotes are stripped at parse time, so the bound substitution variable holds the value itself, not the quoted source token. This matches the quote-handling already applied to passed arguments at the call site:
264
+
265
+ ```sql
266
+ -- !x! BEGIN SCRIPT std_chem_units (
267
+ -- selected_lr_rows,
268
+ -- output_table="std_chem",
269
+ -- default_unit_set="Default",
270
+ -- logfile="/tmp/run.log",
271
+ -- description="hello, world"
272
+ -- )
273
+ -- !!#default_unit_set!! resolves to: Default (no surrounding quotes)
274
+ -- !!#description!! resolves to: hello, world
275
+ -- !x! WRITE "Unit set: !!#default_unit_set!!"
276
+ -- !x! END SCRIPT
277
+ ```
278
+
279
+ Unquoted default values cannot contain whitespace and must not begin with a quote character. An unterminated quoted value (e.g. `name="value`) is rejected as a parse error rather than being silently stored as a literal.
280
+
263
281
  ### Docstrings
264
282
 
265
283
  Comments (`--` or `/* */`) immediately following the BEGIN SCRIPT line are captured as the script's docstring. A blank line terminates the docstring. Docstrings are displayed by [SHOW SCRIPT](#show_script), [SHOW SCRIPTS](#show_scripts), and the `.scripts` REPL command.
@@ -2770,7 +2788,7 @@ The numeric expression may consist of the simple algebraic operations of additio
2770
2788
  SHOW SCRIPTS [<name>]
2771
2789
  ```
2772
2790
 
2773
- Without a name, lists all registered SCRIPT definitions with their parameter signatures and source locations. With a name, shows detail for that script including parameters, source file/line range, and docstring.
2791
+ Without a name, lists all registered SCRIPT definitions with their parameter signatures and source locations (basename only, for compact column-aligned output). With a name, shows detail for that script including parameters, full source path, line range, and docstring.
2774
2792
 
2775
2793
  This is useful for discovering what scripts are available at runtime, especially when scripts are loaded from INCLUDEEd files whose paths are determined dynamically.
2776
2794
 
@@ -2796,7 +2814,7 @@ Registered scripts (3):
2796
2814
 
2797
2815
  ```
2798
2816
  Script: load_data(schema, table, batch_size=1000)
2799
- Source: pipeline.sql:15-42
2817
+ Source: /home/user/etl/pipeline.sql:15-42
2800
2818
  Parameters:
2801
2819
  schema (required)
2802
2820
  table (required)
@@ -2805,6 +2823,8 @@ Parameters:
2805
2823
  Load data from staging into the target table.
2806
2824
  ```
2807
2825
 
2826
+ The detail view (`SHOW SCRIPTS <name>`) shows the full source path so the file can be located unambiguously when multiple scripts share a basename. Scripts loaded inline via `execsql -c '<command>'` show `<inline>` as the source.
2827
+
2808
2828
  If no scripts are registered, prints `No scripts registered.` If the named script is not found, prints `No script named '<name>' is registered.`
2809
2829
 
2810
2830
  !!! tip
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "execsql2"
7
- version = "2.16.17"
7
+ version = "2.16.18"
8
8
  description = "Runs a SQL script against a PostgreSQL, SQLite, MariaDB/MySQL, DuckDB, Firebird, MS-Access, MS-SQL-Server, or Oracle database, or an ODBC DSN. Provides metacommands to import and export data, copy data between databases, conditionally execute SQL and metacommands, and dynamically alter SQL and metacommands with substitution variables."
9
9
  readme = { file = "README.md", content-type = "text/markdown" }
10
10
  license = { file = "LICENSE.txt" }
@@ -165,7 +165,7 @@ skip-magic-trailing-comma = false
165
165
  line-ending = "auto"
166
166
 
167
167
  [tool.bumpversion]
168
- current_version = "2.16.17"
168
+ current_version = "2.16.18"
169
169
  commit = true
170
170
  tag = true
171
171
  tag_name = "v{new_version}"
@@ -546,7 +546,7 @@ def _print_script_detail(name: str) -> None:
546
546
  return
547
547
  block = scripts[script_name]
548
548
  sig = _format_script_signature(block.name, block.param_defs)
549
- src = _format_script_source(block.span)
549
+ src = _format_script_source(block.span, full_path=True)
550
550
  _write_rule(f" {_c(_BOLD + _YELLOW, 'Script')} {_c(_DIM, '──')} {_c(_CYAN, sig)} ")
551
551
  _write(f" {_c(_BOLD, 'Source:')} {src}\n")
552
552
  if block.param_defs:
@@ -203,9 +203,19 @@ def _format_script_signature(name: str, param_defs: Any) -> str:
203
203
  return f"{name}({', '.join(parts)})"
204
204
 
205
205
 
206
- def _format_script_source(span: Any) -> str:
207
- """Return ``file:start-end`` from a SourceSpan."""
208
- filename = Path(span.file).name if span and span.file else "<unknown>"
206
+ def _format_script_source(span: Any, *, full_path: bool = False) -> str:
207
+ """Return ``file:start-end`` from a SourceSpan.
208
+
209
+ By default the filename is the basename, suitable for compact list-view
210
+ output. Pass ``full_path=True`` to retain the full source path (used by
211
+ detail views like ``SHOW SCRIPTS <name>`` and ``.scripts <name>``).
212
+ """
213
+ if not span or not span.file:
214
+ filename = "<unknown>"
215
+ elif full_path:
216
+ filename = span.file
217
+ else:
218
+ filename = Path(span.file).name
209
219
  if span and span.start_line is not None:
210
220
  if span.end_line is not None and span.end_line != span.start_line:
211
221
  return f"{filename}:{span.start_line}-{span.end_line}"
@@ -235,7 +245,7 @@ def x_show_scripts(**kwargs: Any) -> None:
235
245
  return
236
246
  block = scripts[script_name]
237
247
  sig = _format_script_signature(block.name, block.param_defs)
238
- src = _format_script_source(block.span)
248
+ src = _format_script_source(block.span, full_path=True)
239
249
  _state.output.write(f"Script: {sig}\n")
240
250
  _state.output.write(f"Source: {src}\n")
241
251
  if block.param_defs:
@@ -114,14 +114,48 @@ _EXEC_SCRIPT_RX = re.compile(
114
114
  re.I,
115
115
  )
116
116
 
117
+ # A parameter default value is either a double-quoted string, a single-quoted
118
+ # string (both may contain spaces, commas, and other special characters), or
119
+ # a bare run of non-whitespace characters. Unquoted values must not begin
120
+ # with a quote — that catches mismatched / unterminated quoted values
121
+ # (e.g. ``name="value``) instead of silently treating them as literals.
122
+ _PARAM_VALUE = r'(?:"[^"]*"|\'[^\']*\'|[^\s\'"]\S*)'
123
+
117
124
  _WITH_PARAMS_RX = re.compile(
118
125
  r"(?:\s+WITH)?(?:\s+PARAM(?:ETER)?S)?\s*\(\s*(?P<params>"
119
- r"\w+(?:\s*=\s*\S+)?(?:\s*,\s*\w+(?:\s*=\s*\S+)?)*"
126
+ rf"\w+(?:\s*=\s*{_PARAM_VALUE})?(?:\s*,\s*\w+(?:\s*=\s*{_PARAM_VALUE})?)*"
120
127
  r")\s*\)\s*$",
121
128
  re.I,
122
129
  )
123
130
 
124
- _PARAM_TOKEN_RX = re.compile(r"(\w+)(?:\s*=\s*(\S+))?")
131
+ _PARAM_TOKEN_RX = re.compile(rf"(\w+)(?:\s*=\s*({_PARAM_VALUE}))?\s*\Z")
132
+
133
+
134
+ def _split_param_tokens(params_str: str) -> list[str]:
135
+ """Split a parameter list on commas, respecting quoted values.
136
+
137
+ Quoted segments (``"..."`` or ``'...'``) are kept intact so that commas
138
+ inside quotes do not split the token. Each returned token is stripped
139
+ of surrounding whitespace.
140
+ """
141
+ tokens: list[str] = []
142
+ current: list[str] = []
143
+ in_quote: str | None = None
144
+ for ch in params_str:
145
+ if in_quote is not None:
146
+ current.append(ch)
147
+ if ch == in_quote:
148
+ in_quote = None
149
+ elif ch in ('"', "'"):
150
+ current.append(ch)
151
+ in_quote = ch
152
+ elif ch == ",":
153
+ tokens.append("".join(current))
154
+ current = []
155
+ else:
156
+ current.append(ch)
157
+ tokens.append("".join(current))
158
+ return [t.strip() for t in tokens]
125
159
 
126
160
 
127
161
  def _parse_param_defs(
@@ -129,16 +163,21 @@ def _parse_param_defs(
129
163
  lineno: int,
130
164
  source: str,
131
165
  ) -> list[ParamDef]:
132
- """Parse ``'a, b, c=100, d=false'`` into a list of :class:`ParamDef`.
166
+ """Parse ``'a, b, c=100, d="hello world"'`` into a list of :class:`ParamDef`.
167
+
168
+ Defaults may be unquoted (``key=value``), double-quoted (``key="v a l"``),
169
+ or single-quoted (``key='v,a,l'``). Surrounding quotes are stripped so
170
+ that ``ParamDef.default`` always holds the resolved value, mirroring the
171
+ quote-handling done at the call site for passed arguments.
133
172
 
134
173
  Required parameters (no default) must precede optional parameters
135
174
  (with default). Raises :class:`ErrInfo` if ordering is violated.
136
175
  """
137
- tokens = [t.strip() for t in params_str.split(",")]
176
+ tokens = _split_param_tokens(params_str)
138
177
  defs: list[ParamDef] = []
139
178
  seen_optional: str | None = None # name of first optional param
140
179
  for token in tokens:
141
- m = _PARAM_TOKEN_RX.match(token.strip())
180
+ m = _PARAM_TOKEN_RX.match(token)
142
181
  if not m:
143
182
  raise ErrInfo(
144
183
  type="cmd",
@@ -146,6 +185,7 @@ def _parse_param_defs(
146
185
  )
147
186
  name, default = m.group(1), m.group(2)
148
187
  if default is not None:
188
+ default = _strip_quotes(default)
149
189
  if seen_optional is None:
150
190
  seen_optional = name
151
191
  elif seen_optional is not None:
@@ -65,6 +65,27 @@ class TestFormatScriptSource:
65
65
  span = SourceSpan("test.sql", None)
66
66
  assert _format_script_source(span) == "test.sql"
67
67
 
68
+ def test_full_path_keeps_directory(self):
69
+ span = SourceSpan("/long/path/to/script.sql", 1, 10)
70
+ assert _format_script_source(span, full_path=True) == "/long/path/to/script.sql:1-10"
71
+
72
+ def test_full_path_single_line(self):
73
+ span = SourceSpan("/abs/lib.sql", 7)
74
+ assert _format_script_source(span, full_path=True) == "/abs/lib.sql:7"
75
+
76
+ def test_full_path_unknown_file(self):
77
+ assert _format_script_source(None, full_path=True) == "<unknown>"
78
+
79
+ def test_full_path_inline_source(self):
80
+ """`execsql -c <command>` parses with source_name='<inline>'."""
81
+ span = SourceSpan("<inline>", 1, 5)
82
+ assert _format_script_source(span, full_path=True) == "<inline>:1-5"
83
+
84
+ def test_basename_inline_source(self):
85
+ """List-view rendering of an inline source is unchanged."""
86
+ span = SourceSpan("<inline>", 1, 5)
87
+ assert _format_script_source(span) == "<inline>:1-5"
88
+
68
89
 
69
90
  # ---------------------------------------------------------------------------
70
91
  # Handler tests (mock _state)
@@ -162,3 +183,46 @@ class TestShowScriptsDetail:
162
183
  x_show_scripts(metacommandline="SHOW SCRIPTS")
163
184
  output = mock_state.output.getvalue()
164
185
  assert "Registered scripts (1)" in output
186
+
187
+ def test_detail_shows_full_path(self, mock_state):
188
+ """SHOW SCRIPTS <name> renders the full source path, not just the basename."""
189
+ block = ScriptBlock(
190
+ span=SourceSpan("/home/user/etl/lib/load.sql", 12, 30),
191
+ name="proc",
192
+ param_defs=None,
193
+ doc=None,
194
+ )
195
+ mock_state.ast_scripts = {"proc": block}
196
+ x_show_scripts(script_id="proc", metacommandline="SHOW SCRIPTS proc")
197
+ output = mock_state.output.getvalue()
198
+ assert "Source: /home/user/etl/lib/load.sql:12-30" in output
199
+
200
+ def test_detail_inline_source(self, mock_state):
201
+ """`execsql -c <command>` registers scripts with span.file == '<inline>'."""
202
+ block = ScriptBlock(
203
+ span=SourceSpan("<inline>", 1, 4),
204
+ name="proc",
205
+ param_defs=None,
206
+ doc=None,
207
+ )
208
+ mock_state.ast_scripts = {"proc": block}
209
+ x_show_scripts(script_id="proc", metacommandline="SHOW SCRIPTS proc")
210
+ output = mock_state.output.getvalue()
211
+ assert "Source: <inline>:1-4" in output
212
+
213
+
214
+ class TestShowScriptsListBasenamePreserved:
215
+ """List view (no <name>) keeps basename — column-aligned output stays scannable."""
216
+
217
+ def test_list_uses_basename_for_long_paths(self, mock_state):
218
+ block = ScriptBlock(
219
+ span=SourceSpan("/home/user/etl/lib/load.sql", 12, 30),
220
+ name="proc",
221
+ param_defs=None,
222
+ doc=None,
223
+ )
224
+ mock_state.ast_scripts = {"proc": block}
225
+ x_show_scripts(metacommandline="SHOW SCRIPTS")
226
+ output = mock_state.output.getvalue()
227
+ assert "load.sql:12-30" in output
228
+ assert "/home/user/etl/lib/" not in output
@@ -453,6 +453,56 @@ class TestHandleDotCommand:
453
453
  _handle_dot_command(".stack")
454
454
  assert "empty" in capture.getvalue()
455
455
 
456
+ def test_scripts_detail_shows_full_path(self, capture):
457
+ """.scripts <name> renders the full source path, not just the basename."""
458
+ from execsql.script.ast import ScriptBlock, SourceSpan
459
+
460
+ _state.ast_scripts = {
461
+ "proc": ScriptBlock(
462
+ span=SourceSpan("/home/user/etl/lib/load.sql", 12, 30),
463
+ name="proc",
464
+ param_defs=None,
465
+ doc=None,
466
+ ),
467
+ }
468
+ with patch("execsql.debug.repl._use_color", return_value=False):
469
+ _handle_dot_command(".scripts proc")
470
+ assert "/home/user/etl/lib/load.sql:12-30" in capture.getvalue()
471
+
472
+ def test_scripts_detail_inline_source(self, capture):
473
+ """.scripts <name> for an `execsql -c <command>` script renders <inline>."""
474
+ from execsql.script.ast import ScriptBlock, SourceSpan
475
+
476
+ _state.ast_scripts = {
477
+ "proc": ScriptBlock(
478
+ span=SourceSpan("<inline>", 1, 4),
479
+ name="proc",
480
+ param_defs=None,
481
+ doc=None,
482
+ ),
483
+ }
484
+ with patch("execsql.debug.repl._use_color", return_value=False):
485
+ _handle_dot_command(".scripts proc")
486
+ assert "<inline>:1-4" in capture.getvalue()
487
+
488
+ def test_scripts_list_uses_basename(self, capture):
489
+ """.scripts (no name) keeps basename for compact column-aligned output."""
490
+ from execsql.script.ast import ScriptBlock, SourceSpan
491
+
492
+ _state.ast_scripts = {
493
+ "proc": ScriptBlock(
494
+ span=SourceSpan("/home/user/etl/lib/load.sql", 12, 30),
495
+ name="proc",
496
+ param_defs=None,
497
+ doc=None,
498
+ ),
499
+ }
500
+ with patch("execsql.debug.repl._use_color", return_value=False):
501
+ _handle_dot_command(".scripts")
502
+ output = capture.getvalue()
503
+ assert "load.sql:12-30" in output
504
+ assert "/home/user/etl/lib/" not in output
505
+
456
506
 
457
507
  # ---------------------------------------------------------------------------
458
508
  # x_breakpoint — public entry point
@@ -1504,6 +1504,72 @@ class TestDefaultParameters:
1504
1504
  assert result.returncode == 0, result.stderr
1505
1505
  assert "load(schema, batch=1000)" in result.stdout
1506
1506
 
1507
+ def test_quoted_default_strips_quotes_at_substitution(self, tmp_path):
1508
+ """`name="Default"` binds the value `Default`, not `"Default"`.
1509
+
1510
+ Regression for a parser bug where quoted defaults preserved their
1511
+ surrounding quotes — diverging from passed-args which strip them via
1512
+ ``wo_quotes`` — so ``write "!!default_unit_set!!"`` produced
1513
+ ``write ""Default""`` (a syntax error) instead of ``write "Default"``.
1514
+ """
1515
+ result = _run_ast(
1516
+ '-- !x! BEGIN SCRIPT proc1(default_unit_set="Default")\n'
1517
+ "INSERT INTO t VALUES ('!!#default_unit_set!!');\n"
1518
+ "-- !x! END SCRIPT\n"
1519
+ "CREATE TABLE t (x TEXT);\n"
1520
+ "-- !x! EXECUTE SCRIPT proc1\n",
1521
+ tmp_path,
1522
+ )
1523
+ assert result.returncode == 0, result.stderr
1524
+ assert _query_db(tmp_path, "SELECT x FROM t") == [("Default",)]
1525
+
1526
+ def test_quoted_default_with_spaces(self, tmp_path):
1527
+ """Quoted defaults with embedded spaces are preserved verbatim."""
1528
+ result = _run_ast(
1529
+ '-- !x! BEGIN SCRIPT proc1(msg="hello world")\n'
1530
+ "INSERT INTO t VALUES ('!!#msg!!');\n"
1531
+ "-- !x! END SCRIPT\n"
1532
+ "CREATE TABLE t (x TEXT);\n"
1533
+ "-- !x! EXECUTE SCRIPT proc1\n",
1534
+ tmp_path,
1535
+ )
1536
+ assert result.returncode == 0, result.stderr
1537
+ assert _query_db(tmp_path, "SELECT x FROM t") == [("hello world",)]
1538
+
1539
+ def test_quoted_default_with_special_chars(self, tmp_path):
1540
+ """Path-like values with slashes survive parsing."""
1541
+ result = _run_ast(
1542
+ '-- !x! BEGIN SCRIPT proc1(logfile="/tmp/run.log")\n'
1543
+ "INSERT INTO t VALUES ('!!#logfile!!');\n"
1544
+ "-- !x! END SCRIPT\n"
1545
+ "CREATE TABLE t (x TEXT);\n"
1546
+ "-- !x! EXECUTE SCRIPT proc1\n",
1547
+ tmp_path,
1548
+ )
1549
+ assert result.returncode == 0, result.stderr
1550
+ assert _query_db(tmp_path, "SELECT x FROM t") == [("/tmp/run.log",)]
1551
+
1552
+ def test_quoted_default_passed_arg_consistency(self, tmp_path):
1553
+ """A quoted default and a quoted passed-arg produce the same value.
1554
+
1555
+ Both code paths should resolve to the same unquoted value — this is
1556
+ the consistency guarantee the parser fix establishes.
1557
+ """
1558
+ result = _run_ast(
1559
+ '-- !x! BEGIN SCRIPT proc1(label="alpha")\n'
1560
+ "INSERT INTO t VALUES ('!!#label!!');\n"
1561
+ "-- !x! END SCRIPT\n"
1562
+ "CREATE TABLE t (x TEXT);\n"
1563
+ "-- !x! EXECUTE SCRIPT proc1\n"
1564
+ '-- !x! EXECUTE SCRIPT proc1(label="alpha")\n',
1565
+ tmp_path,
1566
+ )
1567
+ assert result.returncode == 0, result.stderr
1568
+ assert _query_db(tmp_path, "SELECT x FROM t ORDER BY rowid") == [
1569
+ ("alpha",),
1570
+ ("alpha",),
1571
+ ]
1572
+
1507
1573
 
1508
1574
  # ---------------------------------------------------------------------------
1509
1575
  # Docstrings
@@ -0,0 +1,124 @@
1
+ """Direct unit tests for parser parameter and docstring internals."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import pytest
6
+
7
+ from execsql.exceptions import ErrInfo
8
+ from execsql.script.ast import ParamDef
9
+ from execsql.script.parser import _parse_param_defs
10
+
11
+
12
+ class TestParseParamDefs:
13
+ """Tests for _parse_param_defs — the parameter definition parser."""
14
+
15
+ def test_single_required(self):
16
+ result = _parse_param_defs("a", 1, "test.sql")
17
+ assert result == [ParamDef("a")]
18
+
19
+ def test_multiple_required(self):
20
+ result = _parse_param_defs("a, b, c", 1, "test.sql")
21
+ assert result == [ParamDef("a"), ParamDef("b"), ParamDef("c")]
22
+
23
+ def test_single_optional(self):
24
+ result = _parse_param_defs("a=100", 1, "test.sql")
25
+ assert result == [ParamDef("a", "100")]
26
+
27
+ def test_mixed_required_optional(self):
28
+ result = _parse_param_defs("schema, table, batch=1000, dry_run=false", 1, "test.sql")
29
+ assert result == [
30
+ ParamDef("schema"),
31
+ ParamDef("table"),
32
+ ParamDef("batch", "1000"),
33
+ ParamDef("dry_run", "false"),
34
+ ]
35
+
36
+ def test_required_after_optional_raises(self):
37
+ with pytest.raises(ErrInfo, match="Required parameter.*after optional"):
38
+ _parse_param_defs("a=1, b", 5, "bad.sql")
39
+
40
+ def test_required_after_optional_names_params(self):
41
+ """Error message includes the parameter names."""
42
+ with pytest.raises(ErrInfo, match="'b'.*'a'"):
43
+ _parse_param_defs("a=1, b", 5, "bad.sql")
44
+
45
+ def test_all_optional(self):
46
+ result = _parse_param_defs("x=1, y=2, z=3", 1, "test.sql")
47
+ assert all(p.default is not None for p in result)
48
+
49
+ def test_whitespace_handling(self):
50
+ result = _parse_param_defs(" a , b = 100 ", 1, "test.sql")
51
+ assert result == [ParamDef("a"), ParamDef("b", "100")]
52
+
53
+ def test_required_property(self):
54
+ defs = _parse_param_defs("a, b=10", 1, "test.sql")
55
+ assert defs[0].required is True
56
+ assert defs[1].required is False
57
+
58
+
59
+ class TestParseParamDefsQuotedDefaults:
60
+ """Quoted default values: surrounding quotes are stripped, spaces and
61
+ commas inside quotes are preserved, and both quote styles are accepted."""
62
+
63
+ def test_double_quoted_default_strips_quotes(self):
64
+ """`name="value"` stores `value`, not `"value"`."""
65
+ result = _parse_param_defs('name="Default"', 1, "test.sql")
66
+ assert result == [ParamDef("name", "Default")]
67
+
68
+ def test_single_quoted_default_strips_quotes(self):
69
+ result = _parse_param_defs("name='Default'", 1, "test.sql")
70
+ assert result == [ParamDef("name", "Default")]
71
+
72
+ def test_quoted_default_with_spaces(self):
73
+ """Embedded spaces are preserved when quoted."""
74
+ result = _parse_param_defs('msg="hello world"', 1, "test.sql")
75
+ assert result == [ParamDef("msg", "hello world")]
76
+
77
+ def test_quoted_default_with_comma(self):
78
+ """Embedded commas don't split the token when inside quotes."""
79
+ result = _parse_param_defs('list="a, b, c"', 1, "test.sql")
80
+ assert result == [ParamDef("list", "a, b, c")]
81
+
82
+ def test_quoted_default_with_special_chars(self):
83
+ """Slashes, equals, and other special chars survive."""
84
+ result = _parse_param_defs('logfile="/dev/null"', 1, "test.sql")
85
+ assert result == [ParamDef("logfile", "/dev/null")]
86
+
87
+ def test_quoted_default_with_empty_string(self):
88
+ """`name=""` stores an empty string."""
89
+ result = _parse_param_defs('name=""', 1, "test.sql")
90
+ assert result == [ParamDef("name", "")]
91
+
92
+ def test_unquoted_default_unchanged(self):
93
+ """Unquoted values are stored verbatim — no quote-stripping false positive."""
94
+ result = _parse_param_defs("count=100", 1, "test.sql")
95
+ assert result == [ParamDef("count", "100")]
96
+
97
+ def test_mixed_quoted_and_unquoted(self):
98
+ """User's reported case: a real-world signature."""
99
+ result = _parse_param_defs(
100
+ 'rows, output_table="std_chem", default_unit_set="Default", logfile="/dev/null"',
101
+ 1,
102
+ "test.sql",
103
+ )
104
+ assert result == [
105
+ ParamDef("rows"),
106
+ ParamDef("output_table", "std_chem"),
107
+ ParamDef("default_unit_set", "Default"),
108
+ ParamDef("logfile", "/dev/null"),
109
+ ]
110
+
111
+ def test_mismatched_quotes_treated_as_unquoted(self):
112
+ """`name="value` (no closing quote) is rejected as malformed."""
113
+ with pytest.raises(ErrInfo, match="Invalid parameter token"):
114
+ _parse_param_defs('name="value', 1, "test.sql")
115
+
116
+ def test_double_quotes_inside_single_quoted(self):
117
+ """Single-quoted value can contain double quotes."""
118
+ result = _parse_param_defs("""msg='say "hi"'""", 1, "test.sql")
119
+ assert result == [ParamDef("msg", 'say "hi"')]
120
+
121
+ def test_single_quotes_inside_double_quoted(self):
122
+ """Double-quoted value can contain single quotes (apostrophes)."""
123
+ result = _parse_param_defs("""msg=\"it's\"""", 1, "test.sql")
124
+ assert result == [ParamDef("msg", "it's")]
@@ -648,7 +648,7 @@ wheels = [
648
648
 
649
649
  [[package]]
650
650
  name = "execsql2"
651
- version = "2.16.17"
651
+ version = "2.16.18"
652
652
  source = { editable = "." }
653
653
  dependencies = [
654
654
  { name = "python-dateutil" },
@@ -1,56 +0,0 @@
1
- """Direct unit tests for parser parameter and docstring internals."""
2
-
3
- from __future__ import annotations
4
-
5
- import pytest
6
-
7
- from execsql.exceptions import ErrInfo
8
- from execsql.script.ast import ParamDef
9
- from execsql.script.parser import _parse_param_defs
10
-
11
-
12
- class TestParseParamDefs:
13
- """Tests for _parse_param_defs — the parameter definition parser."""
14
-
15
- def test_single_required(self):
16
- result = _parse_param_defs("a", 1, "test.sql")
17
- assert result == [ParamDef("a")]
18
-
19
- def test_multiple_required(self):
20
- result = _parse_param_defs("a, b, c", 1, "test.sql")
21
- assert result == [ParamDef("a"), ParamDef("b"), ParamDef("c")]
22
-
23
- def test_single_optional(self):
24
- result = _parse_param_defs("a=100", 1, "test.sql")
25
- assert result == [ParamDef("a", "100")]
26
-
27
- def test_mixed_required_optional(self):
28
- result = _parse_param_defs("schema, table, batch=1000, dry_run=false", 1, "test.sql")
29
- assert result == [
30
- ParamDef("schema"),
31
- ParamDef("table"),
32
- ParamDef("batch", "1000"),
33
- ParamDef("dry_run", "false"),
34
- ]
35
-
36
- def test_required_after_optional_raises(self):
37
- with pytest.raises(ErrInfo, match="Required parameter.*after optional"):
38
- _parse_param_defs("a=1, b", 5, "bad.sql")
39
-
40
- def test_required_after_optional_names_params(self):
41
- """Error message includes the parameter names."""
42
- with pytest.raises(ErrInfo, match="'b'.*'a'"):
43
- _parse_param_defs("a=1, b", 5, "bad.sql")
44
-
45
- def test_all_optional(self):
46
- result = _parse_param_defs("x=1, y=2, z=3", 1, "test.sql")
47
- assert all(p.default is not None for p in result)
48
-
49
- def test_whitespace_handling(self):
50
- result = _parse_param_defs(" a , b = 100 ", 1, "test.sql")
51
- assert result == [ParamDef("a"), ParamDef("b", "100")]
52
-
53
- def test_required_property(self):
54
- defs = _parse_param_defs("a, b=10", 1, "test.sql")
55
- assert defs[0].required is True
56
- assert defs[1].required is False