execsql2 2.14.1__tar.gz → 2.15.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 (316) hide show
  1. {execsql2-2.14.1 → execsql2-2.15.1}/.gitignore +4 -7
  2. {execsql2-2.14.1 → execsql2-2.15.1}/CHANGELOG.md +59 -0
  3. {execsql2-2.14.1 → execsql2-2.15.1}/PKG-INFO +4 -3
  4. {execsql2-2.14.1 → execsql2-2.15.1}/docs/about/divergence.md +53 -39
  5. {execsql2-2.14.1 → execsql2-2.15.1}/docs/reference/configuration.md +4 -1
  6. {execsql2-2.14.1 → execsql2-2.15.1}/docs/reference/metacommands.md +13 -0
  7. {execsql2-2.14.1 → execsql2-2.15.1}/docs/reference/substitution_vars.md +1 -1
  8. {execsql2-2.14.1 → execsql2-2.15.1}/pyproject.toml +4 -3
  9. execsql2-2.15.1/src/execsql/config.py +586 -0
  10. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/db/base.py +0 -1
  11. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/gui/base.py +173 -28
  12. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/gui/console.py +50 -13
  13. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/gui/desktop.py +70 -28
  14. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/gui/tui.py +57 -32
  15. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/metacommands/conditions.py +0 -24
  16. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/metacommands/io_export.py +6 -0
  17. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/metacommands/io_import.py +5 -5
  18. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/metacommands/upsert.py +141 -49
  19. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/models.py +0 -1
  20. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/parser.py +19 -21
  21. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/script/engine.py +2 -0
  22. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/types.py +0 -15
  23. execsql2-2.15.1/src/execsql/utils/datetime.py +86 -0
  24. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/utils/errors.py +0 -19
  25. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/utils/fileio.py +0 -8
  26. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/utils/gui.py +2 -2
  27. {execsql2-2.14.1 → execsql2-2.15.1}/tests/db/test_sqlite_extra.py +12 -23
  28. {execsql2-2.14.1 → execsql2-2.15.1}/tests/gui/test_backends.py +28 -0
  29. {execsql2-2.14.1 → execsql2-2.15.1}/tests/gui/test_compare_stats.py +69 -9
  30. execsql2-2.15.1/tests/gui/test_compute_row_diffs.py +430 -0
  31. {execsql2-2.14.1 → execsql2-2.15.1}/tests/metacommands/test_io_import.py +31 -31
  32. {execsql2-2.14.1 → execsql2-2.15.1}/tests/metacommands/test_metacommands.py +0 -50
  33. {execsql2-2.14.1 → execsql2-2.15.1}/tests/metacommands/test_pg_upsert.py +478 -20
  34. {execsql2-2.14.1 → execsql2-2.15.1}/tests/test_config_extended.py +55 -10
  35. {execsql2-2.14.1 → execsql2-2.15.1}/tests/test_engine.py +12 -0
  36. execsql2-2.15.1/tests/utils/test_datetime.py +197 -0
  37. {execsql2-2.14.1 → execsql2-2.15.1}/tests/utils/test_errors.py +1 -81
  38. {execsql2-2.14.1 → execsql2-2.15.1}/tests/utils/test_fileio_extra.py +0 -4
  39. {execsql2-2.14.1 → execsql2-2.15.1}/uv.lock +7 -5
  40. execsql2-2.14.1/.claude/agents/dba.md +0 -123
  41. execsql2-2.14.1/.claude/agents/herald.md +0 -92
  42. execsql2-2.14.1/.claude/agents/inspector.md +0 -107
  43. execsql2-2.14.1/.claude/agents/liaison.md +0 -107
  44. execsql2-2.14.1/.claude/agents/oracle.md +0 -134
  45. execsql2-2.14.1/.claude/agents/patcher.md +0 -70
  46. execsql2-2.14.1/.claude/agents/qa.md +0 -83
  47. execsql2-2.14.1/.claude/agents/scribe.md +0 -100
  48. execsql2-2.14.1/.claude/commands/code-oracle.md +0 -20
  49. execsql2-2.14.1/.claude/commands/migrate.md +0 -77
  50. execsql2-2.14.1/.claude/commands/review-changes.md +0 -79
  51. execsql2-2.14.1/.claude/commands/test-module.md +0 -75
  52. execsql2-2.14.1/.claude/commands/update-changelog.md +0 -21
  53. execsql2-2.14.1/.claude/commands/where-is.md +0 -51
  54. execsql2-2.14.1/.claude/project_context.md +0 -576
  55. execsql2-2.14.1/.claude/state/status.md +0 -6
  56. execsql2-2.14.1/src/execsql/config.py +0 -658
  57. execsql2-2.14.1/src/execsql/constants.py +0 -370
  58. execsql2-2.14.1/src/execsql/utils/datetime.py +0 -280
  59. execsql2-2.14.1/tests/test_constants.py +0 -94
  60. execsql2-2.14.1/tests/utils/test_datetime.py +0 -102
  61. {execsql2-2.14.1 → execsql2-2.15.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  62. {execsql2-2.14.1 → execsql2-2.15.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  63. {execsql2-2.14.1 → execsql2-2.15.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  64. {execsql2-2.14.1 → execsql2-2.15.1}/.github/workflows/ci-cd.yml +0 -0
  65. {execsql2-2.14.1 → execsql2-2.15.1}/.pre-commit-config.yaml +0 -0
  66. {execsql2-2.14.1 → execsql2-2.15.1}/.pre-commit-hooks.yaml +0 -0
  67. {execsql2-2.14.1 → execsql2-2.15.1}/.python-version +0 -0
  68. {execsql2-2.14.1 → execsql2-2.15.1}/.readthedocs.yaml +0 -0
  69. {execsql2-2.14.1 → execsql2-2.15.1}/CLAUDE.md +0 -0
  70. {execsql2-2.14.1 → execsql2-2.15.1}/CONTRIBUTING.md +0 -0
  71. {execsql2-2.14.1 → execsql2-2.15.1}/LICENSE.txt +0 -0
  72. {execsql2-2.14.1 → execsql2-2.15.1}/NOTICE +0 -0
  73. {execsql2-2.14.1 → execsql2-2.15.1}/README.md +0 -0
  74. {execsql2-2.14.1 → execsql2-2.15.1}/SECURITY.md +0 -0
  75. {execsql2-2.14.1 → execsql2-2.15.1}/docs/about/contributors.md +0 -0
  76. {execsql2-2.14.1 → execsql2-2.15.1}/docs/about/copyright.md +0 -0
  77. {execsql2-2.14.1 → execsql2-2.15.1}/docs/api/cli.md +0 -0
  78. {execsql2-2.14.1 → execsql2-2.15.1}/docs/api/db.md +0 -0
  79. {execsql2-2.14.1 → execsql2-2.15.1}/docs/api/exporters.md +0 -0
  80. {execsql2-2.14.1 → execsql2-2.15.1}/docs/api/importers.md +0 -0
  81. {execsql2-2.14.1 → execsql2-2.15.1}/docs/api/index.md +0 -0
  82. {execsql2-2.14.1 → execsql2-2.15.1}/docs/api/metacommands.md +0 -0
  83. {execsql2-2.14.1 → execsql2-2.15.1}/docs/dev/adding_db_adapters.md +0 -0
  84. {execsql2-2.14.1 → execsql2-2.15.1}/docs/dev/adding_exporters.md +0 -0
  85. {execsql2-2.14.1 → execsql2-2.15.1}/docs/dev/adding_importers.md +0 -0
  86. {execsql2-2.14.1 → execsql2-2.15.1}/docs/dev/adding_metacommands.md +0 -0
  87. {execsql2-2.14.1 → execsql2-2.15.1}/docs/dev/architecture.md +0 -0
  88. {execsql2-2.14.1 → execsql2-2.15.1}/docs/getting-started/installation.md +0 -0
  89. {execsql2-2.14.1 → execsql2-2.15.1}/docs/getting-started/requirements.md +0 -0
  90. {execsql2-2.14.1 → execsql2-2.15.1}/docs/getting-started/syntax.md +0 -0
  91. {execsql2-2.14.1 → execsql2-2.15.1}/docs/guides/debugging.md +0 -0
  92. {execsql2-2.14.1 → execsql2-2.15.1}/docs/guides/documentation.md +0 -0
  93. {execsql2-2.14.1 → execsql2-2.15.1}/docs/guides/encoding.md +0 -0
  94. {execsql2-2.14.1 → execsql2-2.15.1}/docs/guides/examples.md +0 -0
  95. {execsql2-2.14.1 → execsql2-2.15.1}/docs/guides/formatter.md +0 -0
  96. {execsql2-2.14.1 → execsql2-2.15.1}/docs/guides/logging.md +0 -0
  97. {execsql2-2.14.1 → execsql2-2.15.1}/docs/guides/sql_syntax.md +0 -0
  98. {execsql2-2.14.1 → execsql2-2.15.1}/docs/guides/usage.md +0 -0
  99. {execsql2-2.14.1 → execsql2-2.15.1}/docs/guides/using_scripts.md +0 -0
  100. {execsql2-2.14.1 → execsql2-2.15.1}/docs/images/Compare_planets.png +0 -0
  101. {execsql2-2.14.1 → execsql2-2.15.1}/docs/images/actions.png +0 -0
  102. {execsql2-2.14.1 → execsql2-2.15.1}/docs/images/actions2.png +0 -0
  103. {execsql2-2.14.1 → execsql2-2.15.1}/docs/images/checkboxes.png +0 -0
  104. {execsql2-2.14.1 → execsql2-2.15.1}/docs/images/connect.b64 +0 -0
  105. {execsql2-2.14.1 → execsql2-2.15.1}/docs/images/connect.png +0 -0
  106. {execsql2-2.14.1 → execsql2-2.15.1}/docs/images/create_conf.png +0 -0
  107. {execsql2-2.14.1 → execsql2-2.15.1}/docs/images/data_error1_screenshot.jpg +0 -0
  108. {execsql2-2.14.1 → execsql2-2.15.1}/docs/images/entry_form.png +0 -0
  109. {execsql2-2.14.1 → execsql2-2.15.1}/docs/images/execsql_console.png +0 -0
  110. {execsql2-2.14.1 → execsql2-2.15.1}/docs/images/execsql_logo_01.png +0 -0
  111. {execsql2-2.14.1 → execsql2-2.15.1}/docs/images/fatals.png +0 -0
  112. {execsql2-2.14.1 → execsql2-2.15.1}/docs/images/logo_small.png +0 -0
  113. {execsql2-2.14.1 → execsql2-2.15.1}/docs/images/pause_terminal.png +0 -0
  114. {execsql2-2.14.1 → execsql2-2.15.1}/docs/images/pause_terminal_sm.b64 +0 -0
  115. {execsql2-2.14.1 → execsql2-2.15.1}/docs/images/pause_terminal_sm.png +0 -0
  116. {execsql2-2.14.1 → execsql2-2.15.1}/docs/images/prompt_compare.png +0 -0
  117. {execsql2-2.14.1 → execsql2-2.15.1}/docs/images/set_build_commands.jpg +0 -0
  118. {execsql2-2.14.1 → execsql2-2.15.1}/docs/images/unit_conversions.b64 +0 -0
  119. {execsql2-2.14.1 → execsql2-2.15.1}/docs/images/unit_conversions_029.png +0 -0
  120. {execsql2-2.14.1 → execsql2-2.15.1}/docs/images/unmatched.png +0 -0
  121. {execsql2-2.14.1 → execsql2-2.15.1}/docs/images/vim_execsql_highlight.png +0 -0
  122. {execsql2-2.14.1 → execsql2-2.15.1}/docs/index.md +0 -0
  123. {execsql2-2.14.1 → execsql2-2.15.1}/docs/reference/security.md +0 -0
  124. {execsql2-2.14.1 → execsql2-2.15.1}/extras/vscode-execsql/README.md +0 -0
  125. {execsql2-2.14.1 → execsql2-2.15.1}/extras/vscode-execsql/package.json +0 -0
  126. {execsql2-2.14.1 → execsql2-2.15.1}/extras/vscode-execsql/syntaxes/execsql.tmLanguage.json +0 -0
  127. {execsql2-2.14.1 → execsql2-2.15.1}/justfile +0 -0
  128. {execsql2-2.14.1 → execsql2-2.15.1}/scripts/generate_vscode_grammar.py +0 -0
  129. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/__init__.py +0 -0
  130. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/__main__.py +0 -0
  131. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/cli/__init__.py +0 -0
  132. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/cli/dsn.py +0 -0
  133. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/cli/help.py +0 -0
  134. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/cli/lint.py +0 -0
  135. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/cli/run.py +0 -0
  136. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/db/__init__.py +0 -0
  137. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/db/access.py +0 -0
  138. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/db/dsn.py +0 -0
  139. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/db/duckdb.py +0 -0
  140. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/db/factory.py +0 -0
  141. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/db/firebird.py +0 -0
  142. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/db/mysql.py +0 -0
  143. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/db/oracle.py +0 -0
  144. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/db/postgres.py +0 -0
  145. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/db/sqlite.py +1 -1
  146. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/db/sqlserver.py +0 -0
  147. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/debug/__init__.py +0 -0
  148. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/debug/repl.py +0 -0
  149. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/exceptions.py +0 -0
  150. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/exporters/__init__.py +0 -0
  151. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/exporters/base.py +0 -0
  152. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/exporters/delimited.py +0 -0
  153. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/exporters/duckdb.py +0 -0
  154. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/exporters/feather.py +0 -0
  155. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/exporters/html.py +0 -0
  156. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/exporters/json.py +0 -0
  157. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/exporters/latex.py +0 -0
  158. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/exporters/markdown.py +0 -0
  159. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/exporters/ods.py +0 -0
  160. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/exporters/parquet.py +0 -0
  161. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/exporters/pretty.py +0 -0
  162. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/exporters/protocol.py +0 -0
  163. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/exporters/raw.py +0 -0
  164. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/exporters/sqlite.py +0 -0
  165. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/exporters/templates.py +0 -0
  166. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/exporters/values.py +0 -0
  167. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/exporters/xls.py +0 -0
  168. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/exporters/xlsx.py +0 -0
  169. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/exporters/xml.py +0 -0
  170. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/exporters/yaml.py +0 -0
  171. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/exporters/zip.py +0 -0
  172. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/format.py +0 -0
  173. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/gui/__init__.py +0 -0
  174. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/importers/__init__.py +0 -0
  175. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/importers/base.py +0 -0
  176. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/importers/csv.py +0 -0
  177. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/importers/feather.py +0 -0
  178. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/importers/json.py +0 -0
  179. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/importers/ods.py +0 -0
  180. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/importers/xls.py +0 -0
  181. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/metacommands/__init__.py +0 -0
  182. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/metacommands/connect.py +0 -0
  183. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/metacommands/control.py +0 -0
  184. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/metacommands/data.py +0 -0
  185. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/metacommands/debug.py +0 -0
  186. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/metacommands/dispatch.py +0 -0
  187. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/metacommands/io.py +0 -0
  188. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/metacommands/io_fileops.py +0 -0
  189. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/metacommands/io_write.py +0 -0
  190. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/metacommands/prompt.py +0 -0
  191. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/metacommands/script_ext.py +0 -0
  192. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/metacommands/system.py +0 -0
  193. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/py.typed +0 -0
  194. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/script/__init__.py +0 -0
  195. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/script/control.py +0 -0
  196. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/script/variables.py +0 -0
  197. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/state.py +0 -0
  198. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/utils/__init__.py +0 -0
  199. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/utils/auth.py +0 -0
  200. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/utils/crypto.py +0 -0
  201. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/utils/mail.py +0 -0
  202. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/utils/numeric.py +0 -0
  203. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/utils/regex.py +0 -0
  204. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/utils/strings.py +0 -0
  205. {execsql2-2.14.1 → execsql2-2.15.1}/src/execsql/utils/timer.py +0 -0
  206. {execsql2-2.14.1 → execsql2-2.15.1}/templates/README.md +0 -0
  207. {execsql2-2.14.1 → execsql2-2.15.1}/templates/config_settings.sqlite +0 -0
  208. {execsql2-2.14.1 → execsql2-2.15.1}/templates/example_config_prompt.sql +0 -0
  209. {execsql2-2.14.1 → execsql2-2.15.1}/templates/execsql.conf +0 -0
  210. {execsql2-2.14.1 → execsql2-2.15.1}/templates/make_config_db.sql +0 -0
  211. {execsql2-2.14.1 → execsql2-2.15.1}/templates/md_compare.sql +0 -0
  212. {execsql2-2.14.1 → execsql2-2.15.1}/templates/md_glossary.sql +0 -0
  213. {execsql2-2.14.1 → execsql2-2.15.1}/templates/md_upsert.sql +0 -0
  214. {execsql2-2.14.1 → execsql2-2.15.1}/templates/pg_compare.sql +0 -0
  215. {execsql2-2.14.1 → execsql2-2.15.1}/templates/pg_glossary.sql +0 -0
  216. {execsql2-2.14.1 → execsql2-2.15.1}/templates/pg_upsert.sql +0 -0
  217. {execsql2-2.14.1 → execsql2-2.15.1}/templates/script_template.sql +0 -0
  218. {execsql2-2.14.1 → execsql2-2.15.1}/templates/ss_compare.sql +0 -0
  219. {execsql2-2.14.1 → execsql2-2.15.1}/templates/ss_glossary.sql +0 -0
  220. {execsql2-2.14.1 → execsql2-2.15.1}/templates/ss_upsert.sql +0 -0
  221. {execsql2-2.14.1 → execsql2-2.15.1}/tests/__init__.py +0 -0
  222. {execsql2-2.14.1 → execsql2-2.15.1}/tests/cli/__init__.py +0 -0
  223. {execsql2-2.14.1 → execsql2-2.15.1}/tests/cli/test_cli.py +0 -0
  224. {execsql2-2.14.1 → execsql2-2.15.1}/tests/cli/test_cli_e2e.py +0 -0
  225. {execsql2-2.14.1 → execsql2-2.15.1}/tests/cli/test_cli_run.py +0 -0
  226. {execsql2-2.14.1 → execsql2-2.15.1}/tests/cli/test_lint.py +0 -0
  227. {execsql2-2.14.1 → execsql2-2.15.1}/tests/cli/test_ping.py +0 -0
  228. {execsql2-2.14.1 → execsql2-2.15.1}/tests/cli/test_profile.py +0 -0
  229. {execsql2-2.14.1 → execsql2-2.15.1}/tests/conftest.py +0 -0
  230. {execsql2-2.14.1 → execsql2-2.15.1}/tests/db/__init__.py +0 -0
  231. {execsql2-2.14.1 → execsql2-2.15.1}/tests/db/test_base.py +0 -0
  232. {execsql2-2.14.1 → execsql2-2.15.1}/tests/db/test_duckdb.py +0 -0
  233. {execsql2-2.14.1 → execsql2-2.15.1}/tests/db/test_factory.py +0 -0
  234. {execsql2-2.14.1 → execsql2-2.15.1}/tests/db/test_postgres.py +0 -0
  235. {execsql2-2.14.1 → execsql2-2.15.1}/tests/db/test_sqlite.py +0 -0
  236. {execsql2-2.14.1 → execsql2-2.15.1}/tests/exporters/__init__.py +0 -0
  237. {execsql2-2.14.1 → execsql2-2.15.1}/tests/exporters/test_base.py +0 -0
  238. {execsql2-2.14.1 → execsql2-2.15.1}/tests/exporters/test_db.py +0 -0
  239. {execsql2-2.14.1 → execsql2-2.15.1}/tests/exporters/test_delimited.py +0 -0
  240. {execsql2-2.14.1 → execsql2-2.15.1}/tests/exporters/test_duckdb_exporter.py +0 -0
  241. {execsql2-2.14.1 → execsql2-2.15.1}/tests/exporters/test_exporters.py +0 -0
  242. {execsql2-2.14.1 → execsql2-2.15.1}/tests/exporters/test_feather.py +0 -0
  243. {execsql2-2.14.1 → execsql2-2.15.1}/tests/exporters/test_html_extended.py +0 -0
  244. {execsql2-2.14.1 → execsql2-2.15.1}/tests/exporters/test_html_latex.py +0 -0
  245. {execsql2-2.14.1 → execsql2-2.15.1}/tests/exporters/test_json.py +0 -0
  246. {execsql2-2.14.1 → execsql2-2.15.1}/tests/exporters/test_json_extended.py +0 -0
  247. {execsql2-2.14.1 → execsql2-2.15.1}/tests/exporters/test_latex_extended.py +0 -0
  248. {execsql2-2.14.1 → execsql2-2.15.1}/tests/exporters/test_markdown.py +0 -0
  249. {execsql2-2.14.1 → execsql2-2.15.1}/tests/exporters/test_ods.py +0 -0
  250. {execsql2-2.14.1 → execsql2-2.15.1}/tests/exporters/test_parquet.py +0 -0
  251. {execsql2-2.14.1 → execsql2-2.15.1}/tests/exporters/test_pretty_extended.py +0 -0
  252. {execsql2-2.14.1 → execsql2-2.15.1}/tests/exporters/test_raw_extended.py +0 -0
  253. {execsql2-2.14.1 → execsql2-2.15.1}/tests/exporters/test_sqlite_exporter.py +0 -0
  254. {execsql2-2.14.1 → execsql2-2.15.1}/tests/exporters/test_templates.py +0 -0
  255. {execsql2-2.14.1 → execsql2-2.15.1}/tests/exporters/test_templates_extended.py +0 -0
  256. {execsql2-2.14.1 → execsql2-2.15.1}/tests/exporters/test_values_extended.py +0 -0
  257. {execsql2-2.14.1 → execsql2-2.15.1}/tests/exporters/test_xls_xlsx.py +0 -0
  258. {execsql2-2.14.1 → execsql2-2.15.1}/tests/exporters/test_xlsx.py +0 -0
  259. {execsql2-2.14.1 → execsql2-2.15.1}/tests/exporters/test_xml.py +0 -0
  260. {execsql2-2.14.1 → execsql2-2.15.1}/tests/exporters/test_yaml.py +0 -0
  261. {execsql2-2.14.1 → execsql2-2.15.1}/tests/exporters/test_zip.py +0 -0
  262. {execsql2-2.14.1 → execsql2-2.15.1}/tests/gui/__init__.py +0 -0
  263. {execsql2-2.14.1 → execsql2-2.15.1}/tests/importers/__init__.py +0 -0
  264. {execsql2-2.14.1 → execsql2-2.15.1}/tests/importers/test_base_extended.py +0 -0
  265. {execsql2-2.14.1 → execsql2-2.15.1}/tests/importers/test_csv_importer.py +0 -0
  266. {execsql2-2.14.1 → execsql2-2.15.1}/tests/importers/test_feather_importer.py +0 -0
  267. {execsql2-2.14.1 → execsql2-2.15.1}/tests/importers/test_json_importer.py +0 -0
  268. {execsql2-2.14.1 → execsql2-2.15.1}/tests/importers/test_ods_importer.py +0 -0
  269. {execsql2-2.14.1 → execsql2-2.15.1}/tests/importers/test_xls_importer.py +0 -0
  270. {execsql2-2.14.1 → execsql2-2.15.1}/tests/integration/__init__.py +0 -0
  271. {execsql2-2.14.1 → execsql2-2.15.1}/tests/integration/conftest.py +0 -0
  272. {execsql2-2.14.1 → execsql2-2.15.1}/tests/integration/test_dsn.py +0 -0
  273. {execsql2-2.14.1 → execsql2-2.15.1}/tests/integration/test_duckdb.py +0 -0
  274. {execsql2-2.14.1 → execsql2-2.15.1}/tests/integration/test_mysql.py +0 -0
  275. {execsql2-2.14.1 → execsql2-2.15.1}/tests/integration/test_postgres.py +0 -0
  276. {execsql2-2.14.1 → execsql2-2.15.1}/tests/integration/test_sqlite.py +0 -0
  277. {execsql2-2.14.1 → execsql2-2.15.1}/tests/metacommands/__init__.py +0 -0
  278. {execsql2-2.14.1 → execsql2-2.15.1}/tests/metacommands/test_assert.py +0 -0
  279. {execsql2-2.14.1 → execsql2-2.15.1}/tests/metacommands/test_breakpoint.py +0 -0
  280. {execsql2-2.14.1 → execsql2-2.15.1}/tests/metacommands/test_connect.py +0 -0
  281. {execsql2-2.14.1 → execsql2-2.15.1}/tests/metacommands/test_io_export.py +0 -0
  282. {execsql2-2.14.1 → execsql2-2.15.1}/tests/metacommands/test_metacommands_connect.py +0 -0
  283. {execsql2-2.14.1 → execsql2-2.15.1}/tests/metacommands/test_metacommands_data.py +0 -0
  284. {execsql2-2.14.1 → execsql2-2.15.1}/tests/metacommands/test_metacommands_extended.py +0 -0
  285. {execsql2-2.14.1 → execsql2-2.15.1}/tests/metacommands/test_metacommands_fileops_extra.py +0 -0
  286. {execsql2-2.14.1 → execsql2-2.15.1}/tests/metacommands/test_metacommands_io.py +0 -0
  287. {execsql2-2.14.1 → execsql2-2.15.1}/tests/metacommands/test_metacommands_io_write_extra.py +0 -0
  288. {execsql2-2.14.1 → execsql2-2.15.1}/tests/metacommands/test_metacommands_script_ext.py +0 -0
  289. {execsql2-2.14.1 → execsql2-2.15.1}/tests/metacommands/test_metacommands_system.py +0 -0
  290. {execsql2-2.14.1 → execsql2-2.15.1}/tests/metacommands/test_metacommands_system_extra.py +0 -0
  291. {execsql2-2.14.1 → execsql2-2.15.1}/tests/metacommands/test_row_count.py +0 -0
  292. {execsql2-2.14.1 → execsql2-2.15.1}/tests/test_config.py +0 -0
  293. {execsql2-2.14.1 → execsql2-2.15.1}/tests/test_config_data.py +0 -0
  294. {execsql2-2.14.1 → execsql2-2.15.1}/tests/test_error_messages.py +0 -0
  295. {execsql2-2.14.1 → execsql2-2.15.1}/tests/test_exceptions.py +0 -0
  296. {execsql2-2.14.1 → execsql2-2.15.1}/tests/test_format.py +0 -0
  297. {execsql2-2.14.1 → execsql2-2.15.1}/tests/test_mail.py +0 -0
  298. {execsql2-2.14.1 → execsql2-2.15.1}/tests/test_models.py +0 -0
  299. {execsql2-2.14.1 → execsql2-2.15.1}/tests/test_package.py +0 -0
  300. {execsql2-2.14.1 → execsql2-2.15.1}/tests/test_parser.py +0 -0
  301. {execsql2-2.14.1 → execsql2-2.15.1}/tests/test_registry.py +0 -0
  302. {execsql2-2.14.1 → execsql2-2.15.1}/tests/test_script.py +0 -0
  303. {execsql2-2.14.1 → execsql2-2.15.1}/tests/test_state.py +0 -0
  304. {execsql2-2.14.1 → execsql2-2.15.1}/tests/test_types.py +0 -0
  305. {execsql2-2.14.1 → execsql2-2.15.1}/tests/utils/__init__.py +0 -0
  306. {execsql2-2.14.1 → execsql2-2.15.1}/tests/utils/test_auth.py +0 -0
  307. {execsql2-2.14.1 → execsql2-2.15.1}/tests/utils/test_auth_extra.py +0 -0
  308. {execsql2-2.14.1 → execsql2-2.15.1}/tests/utils/test_crypto.py +0 -0
  309. {execsql2-2.14.1 → execsql2-2.15.1}/tests/utils/test_errors_extra.py +0 -0
  310. {execsql2-2.14.1 → execsql2-2.15.1}/tests/utils/test_fileio.py +0 -0
  311. {execsql2-2.14.1 → execsql2-2.15.1}/tests/utils/test_numeric.py +0 -0
  312. {execsql2-2.14.1 → execsql2-2.15.1}/tests/utils/test_regex.py +0 -0
  313. {execsql2-2.14.1 → execsql2-2.15.1}/tests/utils/test_strings.py +0 -0
  314. {execsql2-2.14.1 → execsql2-2.15.1}/tests/utils/test_timer.py +0 -0
  315. {execsql2-2.14.1 → execsql2-2.15.1}/tests/utils/test_timer_extra.py +0 -0
  316. {execsql2-2.14.1 → execsql2-2.15.1}/zensical.toml +0 -0
@@ -33,12 +33,8 @@ coverage.xml
33
33
  .tox/
34
34
  htmlcov/
35
35
  execsql.log
36
- scripts/execsql.conf
37
- scripts/*.csv
38
- scripts/test_import_sqlite.sql
39
- scripts/test_script.sql
40
- scripts/test.db
41
- scripts/*.log
36
+ !scripts/generate_vscode_grammar.py
37
+ scripts/
42
38
 
43
39
  # Distribution
44
40
  *.whl
@@ -48,5 +44,6 @@ scripts/*.log
48
44
  _execsql/
49
45
 
50
46
  # Claude
51
- .claude/settings.local.json
47
+ .claude/
52
48
  ANALYSIS.md
49
+ FINDINGS.md
@@ -13,8 +13,67 @@ ______________________________________________________________________
13
13
 
14
14
  ______________________________________________________________________
15
15
 
16
+ ## [2.15.1] - 2026-04-14
17
+
18
+ ### Added
19
+
20
+ - Cell-level diff marking in `PROMPT COMPARE` dialog — when "Highlight Diffs" is toggled, differing cells within changed rows are prefixed with a bullet marker so users can see exactly which columns differ. Works across all three backends (Tkinter, Textual, and console).
21
+ - `macos_config_file` option in `execsql.conf` `[config]` section — specifies an additional configuration file to read on macOS (`sys.platform == "darwin"`). Behaves identically to `linux_config_file` with tilde expansion support.
22
+ - EXPORT operations now log structured `action` records to `execsql.log` with the query name, output file, and source line number.
23
+
24
+ ### Removed
25
+
26
+ - `Logger.log_action_prompt_quit()` — dead code inherited from upstream, never called. Prompt halt events are already captured by `log_exit_halt()`.
27
+ - `constants.py` — 370 lines of map tile servers, XBM bitmaps, and X11 color names never imported by any module. Vestigial from the upstream monolith.
28
+ - `Tz` class in `types.py` — custom `tzinfo` subclass orphaned by the `python-dateutil` migration.
29
+ - Duplicate `file_size_date()`, `chainfuncs()`, and `as_none()` definitions in `conditions.py` — canonical versions in `utils/errors.py`. `chainfuncs()` and `as_none()` were also removed from `utils/errors.py` as they had zero callers.
30
+
31
+ ### Changed
32
+
33
+ - `linux_config_file` config option now only applies on Linux (`sys.platform == "linux"`), not all POSIX systems. macOS users should use the new `macos_config_file` option instead.
34
+ - Date/time parsing now uses `python-dateutil` instead of 231 hardcoded `strptime` format strings. Handles ISO 8601 with `T` separator, microseconds, `Z` suffix, and named timezones that the old format list could not parse.
35
+ - Refactored `ConfigData` to use private helper methods (`_get_str`, `_get_enum`, `_get_bool`, `_get_int`, `_get_float`) — reduces ~370 lines of repetitive option parsing to ~80 lines with identical behavior.
36
+
37
+ ### Fixed
38
+
39
+ - `NumericParser` now uses left-associative parsing for arithmetic operators. Previously, right-recursive descent caused `10 - 3 - 2` to evaluate as `10 - (3 - 2) = 9` instead of the correct `(10 - 3) - 2 = 5`. Same fix for division.
40
+ - Operator precedence bug in `DataTable` and `Database.populate_table()` empty-column check — a redundant `and conf.del_empty_cols` inside an already-guarded block caused incorrect short-circuit evaluation due to Python operator precedence.
41
+ - SQLite `populate_table()` now applies `trim_strings`, `replace_newlines`, and `empty_strings` processing before extracting column data. Previously, processing was applied after the insert data was copied, so trimming and null-conversion never took effect.
42
+ - `$CURRENT_DATABASE` and `$CURRENT_DBMS` system variables now refresh on `USE` metacommand. Previously they were only set at startup and on `CONNECT`, becoming stale after switching the active database with `USE`.
43
+ - Documentation: `$CONSOLE_WAIT_WHEN_ERROR_HALT_STATE` variable name corrected in substitution variables reference (was incorrectly documented as `$CONSOLE_WAIT_WHEN_ERROR_STATE`).
44
+ - `PROMPT COMPARE` diff logic now uses native Python equality instead of string comparison — `int(1)` vs `float(1.0)`, `Decimal("10.00")` vs `Decimal("10.0")`, and `True` vs `1` are correctly treated as equal instead of producing false diffs.
45
+ - `PROMPT COMPARE` diff logic now treats `None` (SQL NULL) as distinct from empty string `""`. Previously both were normalized to `""` and compared as equal.
46
+ - `PROMPT COMPARE` summary stats now match by column name instead of position — tables with the same columns in different order no longer produce false diffs.
47
+ - `PROMPT COMPARE` summary stats no longer include key columns in the diff comparison — only non-key shared columns are compared, consistent with the cell-level diff engine.
48
+ - `PROMPT COMPARE` diff engine now keeps the first row when duplicate PK values exist, instead of silently using the last.
49
+ - `compare_stats()` now delegates to `compute_row_diffs()` so the summary line and cell-level highlighting always agree.
50
+ - `PG_UPSERT` metacommand no longer writes pg-upsert output to `execsql.log`. Logging now only goes to the file specified by the `LOGFILE` keyword.
51
+ - `win_config_file` config option now works on Windows. Previously checked `os.name == "windows"` which is never true (Python returns `"nt"`). Inherited from upstream.
52
+
53
+ ______________________________________________________________________
54
+
55
+ ## [2.15.0] - 2026-04-09
56
+
57
+ ### Added
58
+
59
+ - `PG_UPSERT` metacommand: new `EXPORT_FAILURES <dir>`, `EXPORT_FORMAT csv|json|xlsx`, and `EXPORT_MAX_ROWS <n>` keywords that write a "fix sheet" of failing QA rows — one row per unique violating staging row with a consolidated `_issues` column — to CSV, JSON, or XLSX. Works in all three modes (full pipeline, QA-only, schema check) and runs even when QA fails. New `$PG_UPSERT_EXPORT_PATH` substitution variable holds the directory written. A user-visible message reporting the export directory and format is emitted to both the console and the execsql log after every export.
60
+
61
+ ### Changed
62
+
63
+ - `[upsert]` extra now requires `pg-upsert>=1.21.0` (up from `>=1.20.0`) for the fix-sheet export feature.
64
+
65
+ ### Fixed
66
+
67
+ - `PROMPT MESSAGE ... CREDENTIALS <user_var> <pw_var>` no longer crashes in console-fallback mode with `TypeError: get_password() missing 2 required positional arguments: 'database_name' and 'user_name'`. The fallback now uses `getpass.getpass()` to read the password, matching the intent (keyring-aware `auth.get_password()` is for CONNECT, not for bare credential prompts).
68
+
69
+ ______________________________________________________________________
70
+
16
71
  ## [2.14.1] - 2026-04-07
17
72
 
73
+ ### Fixed
74
+
75
+ - Fix Windows CI: use `zf.namelist()[0]` instead of path for zip entry lookup.
76
+
18
77
  ______________________________________________________________________
19
78
 
20
79
  ## [2.14.0] - 2026-04-07
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: execsql2
3
- Version: 2.14.1
3
+ Version: 2.15.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
@@ -39,6 +39,7 @@ Classifier: Programming Language :: Python :: 3.14
39
39
  Classifier: Topic :: Database
40
40
  Classifier: Topic :: Database :: Front-Ends
41
41
  Requires-Python: >=3.10
42
+ Requires-Dist: python-dateutil>=2.8
42
43
  Requires-Dist: rich>=13.0
43
44
  Requires-Dist: sqlglot>=25.0
44
45
  Requires-Dist: textual>=0.47.0
@@ -51,7 +52,7 @@ Requires-Dist: keyring; extra == 'all'
51
52
  Requires-Dist: odfpy; extra == 'all'
52
53
  Requires-Dist: openpyxl; extra == 'all'
53
54
  Requires-Dist: oracledb; extra == 'all'
54
- Requires-Dist: pg-upsert>=1.20.0; extra == 'all'
55
+ Requires-Dist: pg-upsert>=1.21.0; extra == 'all'
55
56
  Requires-Dist: polars; extra == 'all'
56
57
  Requires-Dist: psycopg2-binary; extra == 'all'
57
58
  Requires-Dist: pymysql; extra == 'all'
@@ -109,7 +110,7 @@ Requires-Dist: oracledb; extra == 'oracle'
109
110
  Provides-Extra: postgres
110
111
  Requires-Dist: psycopg2-binary; extra == 'postgres'
111
112
  Provides-Extra: upsert
112
- Requires-Dist: pg-upsert>=1.20.0; extra == 'upsert'
113
+ Requires-Dist: pg-upsert>=1.21.0; extra == 'upsert'
113
114
  Description-Content-Type: text/markdown
114
115
 
115
116
  > [!NOTE]
@@ -41,14 +41,14 @@ ______________________________________________________________________
41
41
 
42
42
  ### Metacommands
43
43
 
44
- | Metacommand | Description |
45
- | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
46
- | `ASSERT` | Evaluate a condition and raise an error (halting the script) if it is false. Supports all IF conditions. Optional quoted failure message. Skipped in false IF blocks. |
47
- | `BREAKPOINT` | Pause script execution and drop into an interactive debug REPL. See [Debugging](#debugging) below for full details. |
48
- | `CONFIG SHOW_PROGRESS` | Enable the Rich progress bar for IMPORT operations at runtime. |
49
- | `CONFIG LOG_SQL` | Enable SQL query audit logging — writes executed SQL to the log file. |
50
- | `PG_UPSERT` | QA-checked, FK-dependency-ordered upserts from staging to base schema on PostgreSQL. Integrates [pg-upsert](https://pg-upsert.readthedocs.io/) as an optional dependency. Three modes: full pipeline, QA-only, and schema check. |
51
- | `IMPORT … FROM JSON` | Import a JSON file (array of objects or NDJSON) into a database table. Nested objects are flattened with dot-separated column names; arrays are stored as JSON strings. |
44
+ | Metacommand | Description |
45
+ | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
46
+ | `ASSERT` | Evaluate a condition and raise an error (halting the script) if it is false. Supports all IF conditions. Optional quoted failure message. Skipped in false IF blocks. |
47
+ | `BREAKPOINT` | Pause script execution and drop into an interactive debug REPL. See [Debugging](#debugging) below for full details. |
48
+ | `CONFIG SHOW_PROGRESS` | Enable the Rich progress bar for IMPORT operations at runtime. |
49
+ | `CONFIG LOG_SQL` | Enable SQL query audit logging — writes executed SQL to the log file. |
50
+ | `PG_UPSERT` | QA-checked, FK-dependency-ordered upserts from staging to base schema on PostgreSQL. Integrates [pg-upsert](https://pg-upsert.readthedocs.io/) as an optional dependency. Three modes: full pipeline, QA-only, and schema check. Supports `EXPORT_FAILURES`, `EXPORT_FORMAT`, and `EXPORT_MAX_ROWS` keywords to write a "fix sheet" of failing QA rows to CSV/JSON/XLSX (requires `pg-upsert>=1.21.0`). |
51
+ | `IMPORT … FROM JSON` | Import a JSON file (array of objects or NDJSON) into a database table. Nested objects are flattened with dot-separated column names; arrays are stored as JSON strings. |
52
52
 
53
53
  ### Conditional Tests
54
54
 
@@ -63,13 +63,15 @@ ______________________________________________________________________
63
63
 
64
64
  New options in `execsql.conf`:
65
65
 
66
- | Option | Section | Description |
67
- | -------------------------- | ----------- | ----------------------------------------------------------------- |
68
- | `use_keyring` | `[connect]` | Use the OS keyring for credential storage (default: `yes`). |
69
- | `show_progress` | `[input]` | Enable Rich progress bar for IMPORT (default: `no`). |
70
- | `import_progress_interval` | `[input]` | Log a status line every N rows during IMPORT (default: `0`). |
71
- | `log_sql` | `[config]` | Enable SQL audit logging (default: `no`). |
72
- | `max_log_size_mb` | `[config]` | Rotate the log file at this size in MB (default: `0` = disabled). |
66
+ | Option | Section | Description |
67
+ | -------------------------- | ------------- | ----------------------------------------------------------------- |
68
+ | `use_keyring` | `[connect]` | Use the OS keyring for credential storage (default: `yes`). |
69
+ | `show_progress` | `[input]` | Enable Rich progress bar for IMPORT (default: `no`). |
70
+ | `import_progress_interval` | `[input]` | Log a status line every N rows during IMPORT (default: `0`). |
71
+ | `gui_framework` | `[interface]` | GUI backend: `tkinter` (default) or `textual` (terminal UI). |
72
+ | `log_sql` | `[config]` | Enable SQL audit logging (default: `no`). |
73
+ | `max_log_size_mb` | `[config]` | Rotate the log file at this size in MB (default: `0` = disabled). |
74
+ | `macos_config_file` | `[config]` | Additional config file path, active only on macOS. |
73
75
 
74
76
  ### Tools
75
77
 
@@ -79,14 +81,15 @@ New options in `execsql.conf`:
79
81
 
80
82
  ### GUI
81
83
 
82
- | Feature | Description |
83
- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
84
- | Textual TUI backend | Full terminal-UI backend via the `textual` library. Provides all dialog types (password, pause, message, entry, compare, action, etc.) in the terminal. |
85
- | Console fallback | Text-only backend that handles GUI calls in headless environments by printing to stdout. |
86
- | Table row counts | All GUI backends (Tkinter, Textual, Console) display a row count footer below every table widget (e.g. "3 rows", "1 row"). |
87
- | Help URL button | Dialogs that accept the `HELP` keyword display a clickable Help button that opens the URL in the system browser. |
88
- | Compare diff summary | The compare dialog shows a one-line summary of matching, differing, and table-exclusive rows when key columns are specified. |
89
- | Form validation | `PROMPT ENTRY_FORM` enforces `validation_regex` (on submit) and `validation_key_regex` (per-keystroke) across all backends. Required fields validated. |
84
+ | Feature | Description |
85
+ | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
86
+ | Textual TUI backend | Full terminal-UI backend via the `textual` library. Provides all dialog types (password, pause, message, entry, compare, action, etc.) in the terminal. |
87
+ | Console fallback | Text-only backend that handles GUI calls in headless environments by printing to stdout. |
88
+ | Table row counts | All GUI backends (Tkinter, Textual, Console) display a row count footer below every table widget (e.g. "3 rows", "1 row"). |
89
+ | Help URL button | Dialogs that accept the `HELP` keyword display a clickable Help button that opens the URL in the system browser. |
90
+ | Compare diff summary | The compare dialog shows a one-line summary of matching, differing, and table-exclusive rows when key columns are specified. |
91
+ | Compare cell markers | When diff highlighting is active, individual cells that differ within a changed row are prefixed with a bullet marker across all backends (Tkinter, Textual, console). |
92
+ | Form validation | `PROMPT ENTRY_FORM` enforces `validation_regex` (on submit) and `validation_key_regex` (per-keystroke) across all backends. Required fields validated. |
90
93
 
91
94
  ### Authentication
92
95
 
@@ -107,11 +110,12 @@ New options in `execsql.conf`:
107
110
 
108
111
  ### Developer / Packaging
109
112
 
110
- | Feature | Description |
111
- | --------------------------- | --------------------------------------------------------------------------------------------------------------- |
112
- | VS Code syntax highlighting | Auto-generated `tmLanguage.json` grammar from the dispatch table. |
113
- | `py.typed` marker | PEP 561 marker enabling downstream static type checking. |
114
- | Structured keyword registry | `--dump-keywords` introspects the dispatch table and outputs JSON used by the grammar generator and test suite. |
113
+ | Feature | Description |
114
+ | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
115
+ | VS Code syntax highlighting | Auto-generated `tmLanguage.json` grammar from the dispatch table. |
116
+ | `py.typed` marker | PEP 561 marker enabling downstream static type checking. |
117
+ | Structured keyword registry | `--dump-keywords` introspects the dispatch table and outputs JSON used by the grammar generator and test suite. |
118
+ | `python-dateutil` dependency | Date/time parsing delegates to `dateutil.parser.parse()` instead of 231 hardcoded `strptime` format strings. Handles ISO 8601 `T` separators, microseconds, `Z` suffix, and named timezones. |
115
119
 
116
120
  ### Debugging { #debugging }
117
121
 
@@ -161,6 +165,10 @@ ______________________________________________________________________
161
165
 
162
166
  The CLI framework changed from `optparse` to [Typer](https://typer.tiangolo.com/) with Rich-formatted help text. All original short flags (`-a` through `-z`) are preserved. The tool can be invoked as either `execsql` or `execsql2`.
163
167
 
168
+ ### Configuration
169
+
170
+ - **`linux_config_file`** — now only active on Linux (`sys.platform == "linux"`). Upstream applied it to all POSIX systems, including macOS. A new `macos_config_file` option handles macOS specifically.
171
+
164
172
  ### Internal State Management
165
173
 
166
174
  All 33 mutable runtime globals in `state.py` have been consolidated into a `RuntimeContext` object. The module uses a transparent proxy so existing code is unaffected, but the architecture now supports isolated contexts for testing and future concurrent execution.
@@ -223,17 +231,23 @@ These are behavioral changes driven by security or correctness issues in the ups
223
231
 
224
232
  ### Bug Fixes
225
233
 
226
- | Area | Fix |
227
- | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
228
- | Oracle default port | Corrected from `5432` (PostgreSQL) to `1521`. |
229
- | MySQL `LOAD DATA INFILE` encoding | Python encoding names are now mapped to MySQL charset names. |
230
- | `dt_cast` type converters | Base `Database` class auto-populates 8 type converters that were previously left empty after the refactor. |
231
- | `FileWriter` CPU busy-loop | Uses blocking `queue.get(timeout=0.1)` instead of `get_nowait()` in a tight loop. |
232
- | Substitution variable cycles | 100-iteration limit prevents infinite loops on cyclic variable references. |
233
- | Script location in error messages | `ErrInfo.script_file` and `script_line_no` are now populated via `stamp_errinfo()` so error output includes "Line N of script foo.sql" context — restoring behavior present in the monolith. |
234
- | `$ERROR_MESSAGE` not updated | `$ERROR_MESSAGE` is now set on every error path: `exit_now()`, non-halting SQL errors, and non-halting metacommand errors. Previously it was initialized to `""` and never changed. |
235
- | Metacommand error message lost | When `halt_on_metacommand_err` is `ON`, the original handler `ErrInfo` is now re-raised; the generic "Unknown metacommand" message no longer replaces the specific error from the handler. |
236
- | Empty script name in error msg | `_execute_script_direct()` and `_execute_script_textual_console()` no longer append "in script , line 0" to uncaught-exception messages when `current_script_line()` returns an empty string. |
234
+ | Area | Fix |
235
+ | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
236
+ | Oracle default port | Corrected from `5432` (PostgreSQL) to `1521`. |
237
+ | MySQL `LOAD DATA INFILE` encoding | Python encoding names are now mapped to MySQL charset names. |
238
+ | `dt_cast` type converters | Base `Database` class auto-populates 8 type converters that were previously left empty after the refactor. |
239
+ | `FileWriter` CPU busy-loop | Uses blocking `queue.get(timeout=0.1)` instead of `get_nowait()` in a tight loop. |
240
+ | Substitution variable cycles | 100-iteration limit prevents infinite loops on cyclic variable references. |
241
+ | Script location in error messages | `ErrInfo.script_file` and `script_line_no` are now populated via `stamp_errinfo()` so error output includes "Line N of script foo.sql" context — restoring behavior present in the monolith. |
242
+ | `$ERROR_MESSAGE` not updated | `$ERROR_MESSAGE` is now set on every error path: `exit_now()`, non-halting SQL errors, and non-halting metacommand errors. Previously it was initialized to `""` and never changed. |
243
+ | Metacommand error message lost | When `halt_on_metacommand_err` is `ON`, the original handler `ErrInfo` is now re-raised; the generic "Unknown metacommand" message no longer replaces the specific error from the handler. |
244
+ | Empty script name in error msg | `_execute_script_direct()` and `_execute_script_textual_console()` no longer append "in script , line 0" to uncaught-exception messages when `current_script_line()` returns an empty string. |
245
+ | `PROMPT COMPARE` diff comparison | Diff engine uses native Python equality instead of string comparison — numeric types, Decimals, and booleans compare correctly. `None` is distinguished from empty string. Columns are matched by name (not position), key columns are excluded from comparison, and duplicate PKs keep the first row. |
246
+ | `win_config_file` broken | Checked `os.name == "windows"` which Python never returns (correct value is `"nt"`). Fixed to `os.name == "nt"`. Inherited from upstream. |
247
+ | `NumericParser` right-associative | Arithmetic operators were parsed right-to-left. `10 - 3 - 2` evaluated as `9` instead of `5`. Fixed to left-associative parsing. Inherited from upstream. |
248
+ | Empty-column check precedence | `DataTable` and `Database.populate_table()` had an operator precedence bug in the extra-column emptiness check — a redundant `and conf.del_empty_cols` caused incorrect short-circuit evaluation. Inherited from upstream. |
249
+ | SQLite import string processing | `SQLiteDatabase.populate_table()` applied `trim_strings`, `replace_newlines`, and `empty_strings` after copying row data, so processing never reached the INSERT. Fixed to process before extraction. Inherited from upstream. |
250
+ | `$CURRENT_DATABASE`/`$CURRENT_DBMS` stale after USE | These variables were only set at startup and on CONNECT, not refreshed when `USE` switched the active database. Now set in `set_static_system_vars()` so they update on any connection change. |
237
251
 
238
252
  ______________________________________________________________________
239
253
 
@@ -312,7 +312,10 @@ The section and property names that may be used in a configuration file are list
312
312
  : The number of seconds that *execsql* should wait between the time that a query is created in Access (which uses DAO) and the time that the next statement is executed using ODBC. This value must be greater than or equal to 5.0. The default is `5.0`.
313
313
 
314
314
  `linux_config_file`
315
- : The full name or path to an additional configuration file to be read if *execsql* is running on Linux. If only a path is specified, the name of the configuration file should be `execsql.conf`. The configuration file specified will be read immediately following the configuration file in which it is named. No configuration file will be read more than once. If the name or path are invalid, this setting will be silently ignored. This setting may include [substitution variables](substitution_vars.md#substitution_vars); at the time that configuration files are read, however, only environment variables and system variables related to the script name and path are defined.
315
+ : The full name or path to an additional configuration file to be read if *execsql* is running on Linux (`sys.platform == "linux"`). If only a path is specified, the name of the configuration file should be `execsql.conf`. The configuration file specified will be read immediately following the configuration file in which it is named. No configuration file will be read more than once. If the name or path are invalid, this setting will be silently ignored. This setting may include [substitution variables](substitution_vars.md#substitution_vars); at the time that configuration files are read, however, only environment variables and system variables related to the script name and path are defined.
316
+
317
+ `macos_config_file`
318
+ : The full name or path to an additional configuration file to be read if *execsql* is running on macOS (`sys.platform == "darwin"`). Behaves identically to `linux_config_file` but is only active on macOS. Tilde expansion (`~`) is supported.
316
319
 
317
320
  `log_sql` { #log_sql }
318
321
  : When set to "Yes", all executed SQL statements are written to the log file with a `sql` record type, including the database name, line number, and query text. The property value should be either "Yes" or "No". The default is "No". This can also be enabled via the `CONFIG LOG_SQL` metacommand.
@@ -2127,6 +2127,9 @@ Keywords can appear in any order after the table list.
2127
2127
  | `COMPACT` | Use compact grid format for QA summary instead of detailed per-table panels. |
2128
2128
  | `LOGFILE <path>` | Append pg-upsert's plain-text log output to the given file. Supports quoted paths for spaces: `LOGFILE "path/to/log.txt"`. |
2129
2129
  | `CLEANUP` | Drop all `ups_*` temporary tables and views after execution. Without it, temp objects persist for inspection (default). |
2130
+ | `EXPORT_FAILURES <dir>` | Write a "fix sheet" of failing QA rows into `<dir>` (directory is created if missing). One row per unique violating staging row, with an `_issues` column summarizing every problem on that row. Supports quoted paths for spaces. Export runs even when QA fails — that is the whole point. A confirmation message (`PG_UPSERT: exported QA failures to <dir> (<format>)`) is written to the terminal, the execsql log, and the `LOGFILE` target if one is given. |
2131
+ | `EXPORT_FORMAT csv\|json\|xlsx` | Fix sheet format when `EXPORT_FAILURES` is given. `csv` (default) writes one file per table; `json` writes a single nested file; `xlsx` writes a single workbook with one sheet per table (requires `openpyxl`). |
2132
+ | `EXPORT_MAX_ROWS <n>` | Maximum rows to capture per check per table for the fix sheet. Default `1000`. Only meaningful with `EXPORT_FAILURES`. |
2130
2133
 
2131
2134
  ### Substitution variables
2132
2135
 
@@ -2150,6 +2153,7 @@ Set after every `PG_UPSERT` execution:
2150
2153
  | `$PG_UPSERT_TABLE_QA_PASSED` | TRUE/FALSE | QA result for the current table (updated per table) |
2151
2154
  | `$PG_UPSERT_TABLE_ROWS_UPDATED` | integer | Rows updated for the current table (updated per table) |
2152
2155
  | `$PG_UPSERT_TABLE_ROWS_INSERTED` | integer | Rows inserted for the current table (updated per table) |
2156
+ | `$PG_UPSERT_EXPORT_PATH` | string | Directory path that the QA fix sheet was written to, or empty if `EXPORT_FAILURES` was not given or nothing was exported. |
2153
2157
 
2154
2158
  !!! note "Using `$PG_UPSERT_RESULT_JSON` with WRITE"
2155
2159
  The JSON value is stored as compact single-line JSON. Because it contains double quotes (`"`), square brackets (`[]`), and apostrophes may appear in data, use tilde or backtick delimiters with WRITE:
@@ -2211,6 +2215,13 @@ For the full list of temporary objects and their schemas, see the [pg-upsert Tem
2211
2215
  -- Write the full JSON result using tilde delimiters (JSON contains " and [])
2212
2216
  -- !x! PG_UPSERT FROM staging TO public TABLES books COMMIT
2213
2217
  -- !x! WRITE ~!!$PG_UPSERT_RESULT_JSON!!~
2218
+
2219
+ -- Export failing QA rows to an Excel fix sheet (one sheet per table)
2220
+ -- !x! PG_UPSERT QA FROM staging TO public TABLES books, authors EXPORT_FAILURES "qa_failures/" EXPORT_FORMAT xlsx
2221
+
2222
+ -- Full pipeline with a CSV fix sheet cap of 50 rows per check per table
2223
+ -- !x! PG_UPSERT FROM staging TO public TABLES books, authors EXPORT_FAILURES /tmp/fix EXPORT_MAX_ROWS 50 COMMIT
2224
+ -- !x! WRITE "Fix sheet: !!$PG_UPSERT_EXPORT_PATH!!"
2214
2225
  ```
2215
2226
 
2216
2227
 
@@ -2331,6 +2342,8 @@ The 'Show mismatches' button will highlight all the rows in each table that have
2331
2342
 
2332
2343
  ![Display of unmatched data with the PROMPT COMPARE metacommand](../images/unmatched.png)
2333
2344
 
2345
+ The "Highlight Diffs" button toggles row-level coloring (green for matching rows, yellow for changed rows, red for rows only in one table) and cell-level markers — individual cells that differ within a changed row are prefixed with a bullet marker so you can see exactly which columns have different values. A one-line summary of matching, differing, and table-exclusive row counts is shown below the tables. Cell-level diff marking works across all backends (Tkinter, Textual, and console).
2346
+
2334
2347
  If the 'Continue' button is selected, the script will continue to run. If the 'Cancel' button is selected, the script will immediately halt unless [CANCEL_HALT](#cancel_halt) is set to OFF. The Enter key also carries out the action of the 'Continue' button, and the Escape key carries out the action of the 'Cancel' button.
2335
2348
 
2336
2349
  If a URL is provided with the HELP keyword, the dialog box will include a button that will open that URL when clicked. The URL must be double-quoted if it contains spaces.
@@ -75,7 +75,7 @@ $CANCEL_HALT_STATE
75
75
  $CONSOLE_WAIT_WHEN_DONE_STATE
76
76
  : The value of the status flag that is set by the `console_wait_when_done` configuration setting or by the [CONFIG CONSOLE WAIT_WHEN_DONE](metacommands.md#console_wait_when_done) metacommand. The value of this variable is always either "ON" or "OFF".
77
77
 
78
- $CONSOLE_WAIT_WHEN_ERROR_STATE
78
+ $CONSOLE_WAIT_WHEN_ERROR_HALT_STATE
79
79
  : The value of the status flag that is set by the `console_wait_when_error_halt` configuration setting or by the [CONFIG CONSOLE WAIT_WHEN_ERROR](metacommands.md#console_wait_when_error) metacommand. The value of this variable is always either "ON" or "OFF".
80
80
 
81
81
  $COUNTER_x
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "execsql2"
7
- version = "2.14.1"
7
+ version = "2.15.1"
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" }
@@ -40,6 +40,7 @@ classifiers = [
40
40
  ]
41
41
  requires-python = ">=3.10"
42
42
  dependencies = [
43
+ "python-dateutil>=2.8",
43
44
  "rich>=13.0",
44
45
  "sqlglot>=25.0",
45
46
  "textual>=0.47.0",
@@ -58,7 +59,7 @@ odbc = ["pyodbc"]
58
59
  # Feature bundles
59
60
  formats = ["odfpy", "xlrd", "openpyxl", "Jinja2", "polars", "tables", "PyYAML"]
60
61
  auth = ["keyring"]
61
- upsert = ["pg-upsert>=1.20.0"]
62
+ upsert = ["pg-upsert>=1.21.0"]
62
63
  # Convenience groups
63
64
  all-db = [
64
65
  "psycopg2-binary",
@@ -161,7 +162,7 @@ skip-magic-trailing-comma = false
161
162
  line-ending = "auto"
162
163
 
163
164
  [tool.bumpversion]
164
- current_version = "2.14.1"
165
+ current_version = "2.15.1"
165
166
  commit = true
166
167
  commit_args = "--no-verify"
167
168
  tag = true