execsql2 2.33.0__tar.gz → 2.34.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (519) hide show
  1. {execsql2-2.33.0 → execsql2-2.34.0}/CHANGELOG.md +20 -0
  2. {execsql2-2.33.0 → execsql2-2.34.0}/PKG-INFO +3 -3
  3. {execsql2-2.33.0 → execsql2-2.34.0}/README.md +2 -2
  4. {execsql2-2.33.0 → execsql2-2.34.0}/docs/about/divergence.md +10 -0
  5. {execsql2-2.33.0 → execsql2-2.34.0}/docs/api/db.md +6 -0
  6. {execsql2-2.33.0 → execsql2-2.34.0}/docs/dev/adding_db_adapters.md +18 -17
  7. {execsql2-2.33.0 → execsql2-2.34.0}/docs/dev/architecture.md +2 -0
  8. {execsql2-2.33.0 → execsql2-2.34.0}/docs/guides/formatter.md +1 -1
  9. {execsql2-2.33.0 → execsql2-2.34.0}/docs/guides/sql_syntax.md +20 -0
  10. {execsql2-2.33.0 → execsql2-2.34.0}/docs/index.md +1 -1
  11. {execsql2-2.33.0 → execsql2-2.34.0}/docs/reference/lint.md +1 -1
  12. {execsql2-2.33.0 → execsql2-2.34.0}/docs/reference/metacommands.md +22 -3
  13. {execsql2-2.33.0 → execsql2-2.34.0}/extras/vscode-execsql/package-lock.json +2 -2
  14. {execsql2-2.33.0 → execsql2-2.34.0}/extras/vscode-execsql/package.json +1 -1
  15. {execsql2-2.33.0 → execsql2-2.34.0}/extras/vscode-execsql/syntaxes/execsql.tmLanguage.json +1 -1
  16. {execsql2-2.33.0 → execsql2-2.34.0}/pyproject.toml +2 -2
  17. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/db/base.py +71 -8
  18. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/db/duckdb.py +6 -0
  19. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/db/mysql.py +54 -2
  20. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/db/oracle.py +36 -0
  21. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/db/postgres.py +17 -1
  22. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/db/sqlite.py +7 -1
  23. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/db/sqlserver.py +24 -0
  24. execsql2-2.34.0/src/execsql/db/target_types.py +277 -0
  25. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/exporters/json.py +51 -7
  26. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/exporters/protocol.py +1 -0
  27. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/importers/base.py +1 -1
  28. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/importers/csv.py +1 -1
  29. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/metacommands/__init__.py +17 -1
  30. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/metacommands/io_export.py +3 -1
  31. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/metacommands/io_fileops.py +2 -2
  32. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/script/variables.py +4 -0
  33. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/types.py +2 -2
  34. execsql2-2.34.0/tests/data/existing_table_loads/changes.json +5618 -0
  35. execsql2-2.34.0/tests/data/existing_table_loads/duckdb.json +1112 -0
  36. execsql2-2.34.0/tests/data/existing_table_loads/mysql.json +1782 -0
  37. execsql2-2.34.0/tests/data/existing_table_loads/mysql_files.json +2104 -0
  38. execsql2-2.34.0/tests/data/existing_table_loads/postgres.json +1166 -0
  39. execsql2-2.34.0/tests/data/existing_table_loads/postgres_files.json +1228 -0
  40. execsql2-2.34.0/tests/data/existing_table_loads/sqlite.json +1090 -0
  41. execsql2-2.34.0/tests/data/existing_table_loads/sqlserver.json +1307 -0
  42. execsql2-2.34.0/tests/db/test_existing_table_loads.py +357 -0
  43. {execsql2-2.33.0 → execsql2-2.34.0}/tests/db/test_postgres_inprocess.py +13 -1
  44. execsql2-2.34.0/tests/db/test_target_types.py +520 -0
  45. execsql2-2.34.0/tests/exporters/test_jsonl.py +101 -0
  46. {execsql2-2.33.0 → execsql2-2.34.0}/tests/integration/test_mysql.py +76 -0
  47. {execsql2-2.33.0 → execsql2-2.34.0}/tests/live_db.py +23 -1
  48. {execsql2-2.33.0 → execsql2-2.34.0}/tests/test_script.py +13 -0
  49. {execsql2-2.33.0 → execsql2-2.34.0}/uv.lock +1 -1
  50. {execsql2-2.33.0 → execsql2-2.34.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  51. {execsql2-2.33.0 → execsql2-2.34.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  52. {execsql2-2.33.0 → execsql2-2.34.0}/.github/dependabot.yml +0 -0
  53. {execsql2-2.33.0 → execsql2-2.34.0}/.github/workflows/ci-cd.yml +0 -0
  54. {execsql2-2.33.0 → execsql2-2.34.0}/.gitignore +0 -0
  55. {execsql2-2.33.0 → execsql2-2.34.0}/.pre-commit-config.yaml +0 -0
  56. {execsql2-2.33.0 → execsql2-2.34.0}/.pre-commit-hooks.yaml +0 -0
  57. {execsql2-2.33.0 → execsql2-2.34.0}/.python-version +0 -0
  58. {execsql2-2.33.0 → execsql2-2.34.0}/.readthedocs.yaml +0 -0
  59. {execsql2-2.33.0 → execsql2-2.34.0}/CONTRIBUTING.md +0 -0
  60. {execsql2-2.33.0 → execsql2-2.34.0}/LICENSE.txt +0 -0
  61. {execsql2-2.33.0 → execsql2-2.34.0}/NOTICE +0 -0
  62. {execsql2-2.33.0 → execsql2-2.34.0}/SECURITY.md +0 -0
  63. {execsql2-2.33.0 → execsql2-2.34.0}/docs/about/contributors.md +0 -0
  64. {execsql2-2.33.0 → execsql2-2.34.0}/docs/about/copyright.md +0 -0
  65. {execsql2-2.33.0 → execsql2-2.34.0}/docs/api/cli.md +0 -0
  66. {execsql2-2.33.0 → execsql2-2.34.0}/docs/api/exporters.md +0 -0
  67. {execsql2-2.33.0 → execsql2-2.34.0}/docs/api/importers.md +0 -0
  68. {execsql2-2.33.0 → execsql2-2.34.0}/docs/api/index.md +0 -0
  69. {execsql2-2.33.0 → execsql2-2.34.0}/docs/api/metacommands.md +0 -0
  70. {execsql2-2.33.0 → execsql2-2.34.0}/docs/dev/adding_exporters.md +0 -0
  71. {execsql2-2.33.0 → execsql2-2.34.0}/docs/dev/adding_importers.md +0 -0
  72. {execsql2-2.33.0 → execsql2-2.34.0}/docs/dev/adding_metacommands.md +0 -0
  73. {execsql2-2.33.0 → execsql2-2.34.0}/docs/dev/releasing.md +0 -0
  74. {execsql2-2.33.0 → execsql2-2.34.0}/docs/getting-started/installation.md +0 -0
  75. {execsql2-2.33.0 → execsql2-2.34.0}/docs/getting-started/requirements.md +0 -0
  76. {execsql2-2.33.0 → execsql2-2.34.0}/docs/getting-started/syntax.md +0 -0
  77. {execsql2-2.33.0 → execsql2-2.34.0}/docs/guides/debugging.md +0 -0
  78. {execsql2-2.33.0 → execsql2-2.34.0}/docs/guides/documentation.md +0 -0
  79. {execsql2-2.33.0 → execsql2-2.34.0}/docs/guides/editors.md +0 -0
  80. {execsql2-2.33.0 → execsql2-2.34.0}/docs/guides/encoding.md +0 -0
  81. {execsql2-2.33.0 → execsql2-2.34.0}/docs/guides/examples.md +0 -0
  82. {execsql2-2.33.0 → execsql2-2.34.0}/docs/guides/logging.md +0 -0
  83. {execsql2-2.33.0 → execsql2-2.34.0}/docs/guides/usage.md +0 -0
  84. {execsql2-2.33.0 → execsql2-2.34.0}/docs/guides/using_scripts.md +0 -0
  85. {execsql2-2.33.0 → execsql2-2.34.0}/docs/images/Compare_planets.png +0 -0
  86. {execsql2-2.33.0 → execsql2-2.34.0}/docs/images/actions.png +0 -0
  87. {execsql2-2.33.0 → execsql2-2.34.0}/docs/images/actions2.png +0 -0
  88. {execsql2-2.33.0 → execsql2-2.34.0}/docs/images/checkboxes.png +0 -0
  89. {execsql2-2.33.0 → execsql2-2.34.0}/docs/images/connect.png +0 -0
  90. {execsql2-2.33.0 → execsql2-2.34.0}/docs/images/data_error1_screenshot.jpg +0 -0
  91. {execsql2-2.33.0 → execsql2-2.34.0}/docs/images/execsql_logo_01.png +0 -0
  92. {execsql2-2.33.0 → execsql2-2.34.0}/docs/images/fatals.png +0 -0
  93. {execsql2-2.33.0 → execsql2-2.34.0}/docs/images/pause_terminal_sm.png +0 -0
  94. {execsql2-2.33.0 → execsql2-2.34.0}/docs/images/set_build_commands.jpg +0 -0
  95. {execsql2-2.33.0 → execsql2-2.34.0}/docs/images/unit_conversions_029.png +0 -0
  96. {execsql2-2.33.0 → execsql2-2.34.0}/docs/images/unmatched.png +0 -0
  97. {execsql2-2.33.0 → execsql2-2.34.0}/docs/images/vim_execsql_highlight.png +0 -0
  98. {execsql2-2.33.0 → execsql2-2.34.0}/docs/reference/configuration.md +0 -0
  99. {execsql2-2.33.0 → execsql2-2.34.0}/docs/reference/security.md +0 -0
  100. {execsql2-2.33.0 → execsql2-2.34.0}/docs/reference/substitution_vars.md +0 -0
  101. {execsql2-2.33.0 → execsql2-2.34.0}/docs/templates/compare.md +0 -0
  102. {execsql2-2.33.0 → execsql2-2.34.0}/docs/templates/glossary.md +0 -0
  103. {execsql2-2.33.0 → execsql2-2.34.0}/docs/templates/index.md +0 -0
  104. {execsql2-2.33.0 → execsql2-2.34.0}/docs/templates/upsert.md +0 -0
  105. {execsql2-2.33.0 → execsql2-2.34.0}/extras/plugin-template/README.md +0 -0
  106. {execsql2-2.33.0 → execsql2-2.34.0}/extras/plugin-template/pyproject.toml +0 -0
  107. {execsql2-2.33.0 → execsql2-2.34.0}/extras/plugin-template/src/execsql_plugin_YOURNAME/__init__.py +0 -0
  108. {execsql2-2.33.0 → execsql2-2.34.0}/extras/plugin-template/tests/test_plugin.py.example +0 -0
  109. {execsql2-2.33.0 → execsql2-2.34.0}/extras/vscode-execsql/.vscodeignore +0 -0
  110. {execsql2-2.33.0 → execsql2-2.34.0}/extras/vscode-execsql/LICENSE.txt +0 -0
  111. {execsql2-2.33.0 → execsql2-2.34.0}/extras/vscode-execsql/README.md +0 -0
  112. {execsql2-2.33.0 → execsql2-2.34.0}/extras/vscode-execsql/extension.js +0 -0
  113. {execsql2-2.33.0 → execsql2-2.34.0}/extras/vscode-execsql/icon.png +0 -0
  114. {execsql2-2.33.0 → execsql2-2.34.0}/justfile +0 -0
  115. {execsql2-2.33.0 → execsql2-2.34.0}/scripts/benchmarks/README.md +0 -0
  116. {execsql2-2.33.0 → execsql2-2.34.0}/scripts/benchmarks/results/.gitignore +0 -0
  117. {execsql2-2.33.0 → execsql2-2.34.0}/scripts/benchmarks/run_benchmarks.py +0 -0
  118. {execsql2-2.33.0 → execsql2-2.34.0}/scripts/generate_vscode_grammar.py +0 -0
  119. {execsql2-2.33.0 → execsql2-2.34.0}/scripts/release_notes.py +0 -0
  120. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/__init__.py +0 -0
  121. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/__main__.py +0 -0
  122. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/api.py +0 -0
  123. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/cli/__init__.py +0 -0
  124. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/cli/application.py +0 -0
  125. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/cli/commands/__init__.py +0 -0
  126. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/cli/commands/config.py +0 -0
  127. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/cli/commands/format.py +0 -0
  128. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/cli/commands/init.py +0 -0
  129. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/cli/commands/lint.py +0 -0
  130. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/cli/commands/listing.py +0 -0
  131. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/cli/commands/lsp.py +0 -0
  132. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/cli/commands/run.py +0 -0
  133. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/cli/dispatch.py +0 -0
  134. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/cli/dsn.py +0 -0
  135. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/cli/help.py +0 -0
  136. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/cli/lint.py +0 -0
  137. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/cli/options.py +0 -0
  138. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/cli/run.py +0 -0
  139. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/config.py +0 -0
  140. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/config_validation.py +0 -0
  141. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/data/__init__.py +0 -0
  142. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/data/execsql.conf.template +0 -0
  143. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/data/script_header.sql +0 -0
  144. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/db/__init__.py +0 -0
  145. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/db/access.py +0 -0
  146. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/db/dsn.py +0 -0
  147. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/db/factory.py +0 -0
  148. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/db/firebird.py +0 -0
  149. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/db/odbc.py +0 -0
  150. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/db/tiers.py +0 -0
  151. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/debug/__init__.py +0 -0
  152. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/debug/repl.py +0 -0
  153. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/exceptions.py +0 -0
  154. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/exporters/__init__.py +0 -0
  155. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/exporters/base.py +0 -0
  156. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/exporters/delimited.py +0 -0
  157. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/exporters/duckdb.py +0 -0
  158. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/exporters/feather.py +0 -0
  159. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/exporters/html.py +0 -0
  160. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/exporters/latex.py +0 -0
  161. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/exporters/markdown.py +0 -0
  162. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/exporters/ods.py +0 -0
  163. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/exporters/parquet.py +0 -0
  164. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/exporters/pretty.py +0 -0
  165. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/exporters/raw.py +0 -0
  166. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/exporters/sqlite.py +0 -0
  167. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/exporters/templates.py +0 -0
  168. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/exporters/values.py +0 -0
  169. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/exporters/xls.py +0 -0
  170. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/exporters/xlsx.py +0 -0
  171. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/exporters/xml.py +0 -0
  172. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/exporters/yaml.py +0 -0
  173. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/exporters/zip.py +0 -0
  174. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/format.py +0 -0
  175. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/gui/__init__.py +0 -0
  176. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/gui/base.py +0 -0
  177. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/gui/console.py +0 -0
  178. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/gui/desktop.py +0 -0
  179. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/gui/tui.py +0 -0
  180. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/importers/__init__.py +0 -0
  181. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/importers/feather.py +0 -0
  182. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/importers/json.py +0 -0
  183. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/importers/ods.py +0 -0
  184. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/importers/xls.py +0 -0
  185. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/interactive.py +0 -0
  186. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/lsp/__init__.py +0 -0
  187. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/lsp/_util.py +0 -0
  188. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/lsp/cache.py +0 -0
  189. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/lsp/code_actions.py +0 -0
  190. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/lsp/completion.py +0 -0
  191. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/lsp/diagnostics.py +0 -0
  192. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/lsp/document.py +0 -0
  193. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/lsp/folding.py +0 -0
  194. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/lsp/formatting.py +0 -0
  195. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/lsp/hover.py +0 -0
  196. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/lsp/navigation.py +0 -0
  197. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/lsp/rename.py +0 -0
  198. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/lsp/server.py +0 -0
  199. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/manifest.py +0 -0
  200. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/metacommands/conditions.py +0 -0
  201. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/metacommands/connect.py +0 -0
  202. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/metacommands/control.py +0 -0
  203. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/metacommands/data.py +0 -0
  204. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/metacommands/debug.py +0 -0
  205. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/metacommands/dispatch.py +0 -0
  206. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/metacommands/effects.py +0 -0
  207. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/metacommands/io.py +0 -0
  208. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/metacommands/io_import.py +0 -0
  209. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/metacommands/io_write.py +0 -0
  210. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/metacommands/prompt.py +0 -0
  211. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/metacommands/reference.py +0 -0
  212. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/metacommands/script_ext.py +0 -0
  213. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/metacommands/system.py +0 -0
  214. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/metacommands/upsert.py +0 -0
  215. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/models.py +0 -0
  216. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/parser.py +0 -0
  217. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/plugins.py +0 -0
  218. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/py.typed +0 -0
  219. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/script/__init__.py +0 -0
  220. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/script/ast.py +0 -0
  221. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/script/control.py +0 -0
  222. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/script/engine.py +0 -0
  223. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/script/executor.py +0 -0
  224. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/script/parser.py +0 -0
  225. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/state.py +0 -0
  226. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/state.pyi +0 -0
  227. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/utils/__init__.py +0 -0
  228. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/utils/auth.py +0 -0
  229. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/utils/color.py +0 -0
  230. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/utils/crypto.py +0 -0
  231. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/utils/datetime.py +0 -0
  232. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/utils/errors.py +0 -0
  233. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/utils/fileio.py +0 -0
  234. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/utils/gui.py +0 -0
  235. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/utils/mail.py +0 -0
  236. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/utils/numeric.py +0 -0
  237. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/utils/paths.py +0 -0
  238. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/utils/regex.py +0 -0
  239. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/utils/strings.py +0 -0
  240. {execsql2-2.33.0 → execsql2-2.34.0}/src/execsql/utils/timer.py +0 -0
  241. {execsql2-2.33.0 → execsql2-2.34.0}/templates/README.md +0 -0
  242. {execsql2-2.33.0 → execsql2-2.34.0}/templates/config_settings.sqlite +0 -0
  243. {execsql2-2.33.0 → execsql2-2.34.0}/templates/example_config_prompt.sql +0 -0
  244. {execsql2-2.33.0 → execsql2-2.34.0}/templates/make_config_db.sql +0 -0
  245. {execsql2-2.33.0 → execsql2-2.34.0}/templates/md_compare.sql +0 -0
  246. {execsql2-2.33.0 → execsql2-2.34.0}/templates/md_glossary.sql +0 -0
  247. {execsql2-2.33.0 → execsql2-2.34.0}/templates/md_upsert.sql +0 -0
  248. {execsql2-2.33.0 → execsql2-2.34.0}/templates/pg_compare.sql +0 -0
  249. {execsql2-2.33.0 → execsql2-2.34.0}/templates/pg_glossary.sql +0 -0
  250. {execsql2-2.33.0 → execsql2-2.34.0}/templates/script_template.sql +0 -0
  251. {execsql2-2.33.0 → execsql2-2.34.0}/templates/ss_compare.sql +0 -0
  252. {execsql2-2.33.0 → execsql2-2.34.0}/templates/ss_glossary.sql +0 -0
  253. {execsql2-2.33.0 → execsql2-2.34.0}/templates/ss_upsert.sql +0 -0
  254. {execsql2-2.33.0 → execsql2-2.34.0}/tests/__init__.py +0 -0
  255. {execsql2-2.33.0 → execsql2-2.34.0}/tests/cli/__init__.py +0 -0
  256. {execsql2-2.33.0 → execsql2-2.34.0}/tests/cli/test_cli.py +0 -0
  257. {execsql2-2.33.0 → execsql2-2.34.0}/tests/cli/test_cli_e2e.py +0 -0
  258. {execsql2-2.33.0 → execsql2-2.34.0}/tests/cli/test_cli_run.py +0 -0
  259. {execsql2-2.33.0 → execsql2-2.34.0}/tests/cli/test_cli_surface.py +0 -0
  260. {execsql2-2.33.0 → execsql2-2.34.0}/tests/cli/test_commands.py +0 -0
  261. {execsql2-2.33.0 → execsql2-2.34.0}/tests/cli/test_config_validate.py +0 -0
  262. {execsql2-2.33.0 → execsql2-2.34.0}/tests/cli/test_dispatch.py +0 -0
  263. {execsql2-2.33.0 → execsql2-2.34.0}/tests/cli/test_error_location.py +0 -0
  264. {execsql2-2.33.0 → execsql2-2.34.0}/tests/cli/test_init.py +0 -0
  265. {execsql2-2.33.0 → execsql2-2.34.0}/tests/cli/test_lint.py +0 -0
  266. {execsql2-2.33.0 → execsql2-2.34.0}/tests/cli/test_lint_fixtures.py +0 -0
  267. {execsql2-2.33.0 → execsql2-2.34.0}/tests/cli/test_log_exit_record.py +0 -0
  268. {execsql2-2.33.0 → execsql2-2.34.0}/tests/cli/test_manifest.py +0 -0
  269. {execsql2-2.33.0 → execsql2-2.34.0}/tests/cli/test_ping.py +0 -0
  270. {execsql2-2.33.0 → execsql2-2.34.0}/tests/cli/test_profile.py +0 -0
  271. {execsql2-2.33.0 → execsql2-2.34.0}/tests/cli/test_sigterm.py +0 -0
  272. {execsql2-2.33.0 → execsql2-2.34.0}/tests/cli/test_undeclared_imports.py +0 -0
  273. {execsql2-2.33.0 → execsql2-2.34.0}/tests/conftest.py +0 -0
  274. {execsql2-2.33.0 → execsql2-2.34.0}/tests/data/formatter_shapes/comment_anchoring.sql +0 -0
  275. {execsql2-2.33.0 → execsql2-2.34.0}/tests/data/formatter_shapes/escapes_and_literals.sql +0 -0
  276. {execsql2-2.33.0 → execsql2-2.34.0}/tests/data/formatter_shapes/substitution_and_metacommands.sql +0 -0
  277. {execsql2-2.33.0 → execsql2-2.34.0}/tests/data/formatter_shapes/unparsable_sql.sql +0 -0
  278. {execsql2-2.33.0 → execsql2-2.34.0}/tests/data/lint/conditions_and_blocks.sql +0 -0
  279. {execsql2-2.33.0 → execsql2-2.34.0}/tests/data/lint/does_not_parse.sql +0 -0
  280. {execsql2-2.33.0 → execsql2-2.34.0}/tests/data/lint/every_metacommand.sql +0 -0
  281. {execsql2-2.33.0 → execsql2-2.34.0}/tests/data/lint/invalid.sql +0 -0
  282. {execsql2-2.33.0 → execsql2-2.34.0}/tests/data/ods/libreoffice.fods +0 -0
  283. {execsql2-2.33.0 → execsql2-2.34.0}/tests/data/ods/libreoffice.ods +0 -0
  284. {execsql2-2.33.0 → execsql2-2.34.0}/tests/db/__init__.py +0 -0
  285. {execsql2-2.33.0 → execsql2-2.34.0}/tests/db/conftest.py +0 -0
  286. {execsql2-2.33.0 → execsql2-2.34.0}/tests/db/test_access_windows.py +0 -0
  287. {execsql2-2.33.0 → execsql2-2.34.0}/tests/db/test_base.py +0 -0
  288. {execsql2-2.33.0 → execsql2-2.34.0}/tests/db/test_bulk_inserts.py +0 -0
  289. {execsql2-2.33.0 → execsql2-2.34.0}/tests/db/test_copy_new.py +0 -0
  290. {execsql2-2.33.0 → execsql2-2.34.0}/tests/db/test_db_adapters_mocked.py +0 -0
  291. {execsql2-2.33.0 → execsql2-2.34.0}/tests/db/test_db_adapters_mocked_extra.py +0 -0
  292. {execsql2-2.33.0 → execsql2-2.34.0}/tests/db/test_dsn.py +0 -0
  293. {execsql2-2.33.0 → execsql2-2.34.0}/tests/db/test_duckdb.py +0 -0
  294. {execsql2-2.33.0 → execsql2-2.34.0}/tests/db/test_factory.py +0 -0
  295. {execsql2-2.33.0 → execsql2-2.34.0}/tests/db/test_factory_postgres_encoding.py +0 -0
  296. {execsql2-2.33.0 → execsql2-2.34.0}/tests/db/test_import_atomic.py +0 -0
  297. {execsql2-2.33.0 → execsql2-2.34.0}/tests/db/test_import_replacement_quoted.py +0 -0
  298. {execsql2-2.33.0 → execsql2-2.34.0}/tests/db/test_import_short_rows.py +0 -0
  299. {execsql2-2.33.0 → execsql2-2.34.0}/tests/db/test_import_timezones.py +0 -0
  300. {execsql2-2.33.0 → execsql2-2.34.0}/tests/db/test_mysql_case_folding.py +0 -0
  301. {execsql2-2.33.0 → execsql2-2.34.0}/tests/db/test_mysql_inprocess.py +0 -0
  302. {execsql2-2.33.0 → execsql2-2.34.0}/tests/db/test_mysql_trailing_blank_lines.py +0 -0
  303. {execsql2-2.33.0 → execsql2-2.34.0}/tests/db/test_new_table_checks.py +0 -0
  304. {execsql2-2.33.0 → execsql2-2.34.0}/tests/db/test_odbc_fresh_sessions.py +0 -0
  305. {execsql2-2.33.0 → execsql2-2.34.0}/tests/db/test_oracle_driver.py +0 -0
  306. {execsql2-2.33.0 → execsql2-2.34.0}/tests/db/test_postgres.py +0 -0
  307. {execsql2-2.33.0 → execsql2-2.34.0}/tests/db/test_quoted_substitution_roundtrip.py +0 -0
  308. {execsql2-2.33.0 → execsql2-2.34.0}/tests/db/test_replacement_missing_table.py +0 -0
  309. {execsql2-2.33.0 → execsql2-2.34.0}/tests/db/test_roundtrip_adapters.py +0 -0
  310. {execsql2-2.33.0 → execsql2-2.34.0}/tests/db/test_sqlite.py +0 -0
  311. {execsql2-2.33.0 → execsql2-2.34.0}/tests/db/test_sqlite_extra.py +0 -0
  312. {execsql2-2.33.0 → execsql2-2.34.0}/tests/db/test_sqlserver_driver18.py +0 -0
  313. {execsql2-2.33.0 → execsql2-2.34.0}/tests/db/test_sqlserver_inprocess.py +0 -0
  314. {execsql2-2.33.0 → execsql2-2.34.0}/tests/db/test_tiers.py +0 -0
  315. {execsql2-2.33.0 → execsql2-2.34.0}/tests/db/test_transactions.py +0 -0
  316. {execsql2-2.33.0 → execsql2-2.34.0}/tests/exporters/__init__.py +0 -0
  317. {execsql2-2.33.0 → execsql2-2.34.0}/tests/exporters/conftest.py +0 -0
  318. {execsql2-2.33.0 → execsql2-2.34.0}/tests/exporters/test_base.py +0 -0
  319. {execsql2-2.33.0 → execsql2-2.34.0}/tests/exporters/test_db.py +0 -0
  320. {execsql2-2.33.0 → execsql2-2.34.0}/tests/exporters/test_delimited.py +0 -0
  321. {execsql2-2.33.0 → execsql2-2.34.0}/tests/exporters/test_duckdb_exporter.py +0 -0
  322. {execsql2-2.33.0 → execsql2-2.34.0}/tests/exporters/test_exporters.py +0 -0
  323. {execsql2-2.33.0 → execsql2-2.34.0}/tests/exporters/test_feather.py +0 -0
  324. {execsql2-2.33.0 → execsql2-2.34.0}/tests/exporters/test_html_extended.py +0 -0
  325. {execsql2-2.33.0 → execsql2-2.34.0}/tests/exporters/test_html_latex.py +0 -0
  326. {execsql2-2.33.0 → execsql2-2.34.0}/tests/exporters/test_json.py +0 -0
  327. {execsql2-2.33.0 → execsql2-2.34.0}/tests/exporters/test_json_extended.py +0 -0
  328. {execsql2-2.33.0 → execsql2-2.34.0}/tests/exporters/test_latex_extended.py +0 -0
  329. {execsql2-2.33.0 → execsql2-2.34.0}/tests/exporters/test_markdown.py +0 -0
  330. {execsql2-2.33.0 → execsql2-2.34.0}/tests/exporters/test_ods.py +0 -0
  331. {execsql2-2.33.0 → execsql2-2.34.0}/tests/exporters/test_ods_export.py +0 -0
  332. {execsql2-2.33.0 → execsql2-2.34.0}/tests/exporters/test_parquet.py +0 -0
  333. {execsql2-2.33.0 → execsql2-2.34.0}/tests/exporters/test_pretty_extended.py +0 -0
  334. {execsql2-2.33.0 → execsql2-2.34.0}/tests/exporters/test_raw_extended.py +0 -0
  335. {execsql2-2.33.0 → execsql2-2.34.0}/tests/exporters/test_roundtrip_columnar.py +0 -0
  336. {execsql2-2.33.0 → execsql2-2.34.0}/tests/exporters/test_roundtrip_dbfile.py +0 -0
  337. {execsql2-2.33.0 → execsql2-2.34.0}/tests/exporters/test_roundtrip_delimited.py +0 -0
  338. {execsql2-2.33.0 → execsql2-2.34.0}/tests/exporters/test_roundtrip_json.py +0 -0
  339. {execsql2-2.33.0 → execsql2-2.34.0}/tests/exporters/test_roundtrip_latex_xml.py +0 -0
  340. {execsql2-2.33.0 → execsql2-2.34.0}/tests/exporters/test_roundtrip_markup.py +0 -0
  341. {execsql2-2.33.0 → execsql2-2.34.0}/tests/exporters/test_roundtrip_ods.py +0 -0
  342. {execsql2-2.33.0 → execsql2-2.34.0}/tests/exporters/test_roundtrip_pretty_raw.py +0 -0
  343. {execsql2-2.33.0 → execsql2-2.34.0}/tests/exporters/test_roundtrip_template.py +0 -0
  344. {execsql2-2.33.0 → execsql2-2.34.0}/tests/exporters/test_roundtrip_values.py +0 -0
  345. {execsql2-2.33.0 → execsql2-2.34.0}/tests/exporters/test_roundtrip_xlsx.py +0 -0
  346. {execsql2-2.33.0 → execsql2-2.34.0}/tests/exporters/test_sqlite_exporter.py +0 -0
  347. {execsql2-2.33.0 → execsql2-2.34.0}/tests/exporters/test_templates.py +0 -0
  348. {execsql2-2.33.0 → execsql2-2.34.0}/tests/exporters/test_templates_extended.py +0 -0
  349. {execsql2-2.33.0 → execsql2-2.34.0}/tests/exporters/test_values_extended.py +0 -0
  350. {execsql2-2.33.0 → execsql2-2.34.0}/tests/exporters/test_values_roundtrip.py +0 -0
  351. {execsql2-2.33.0 → execsql2-2.34.0}/tests/exporters/test_xls_xlsx.py +0 -0
  352. {execsql2-2.33.0 → execsql2-2.34.0}/tests/exporters/test_xlsx.py +0 -0
  353. {execsql2-2.33.0 → execsql2-2.34.0}/tests/exporters/test_xml.py +0 -0
  354. {execsql2-2.33.0 → execsql2-2.34.0}/tests/exporters/test_yaml.py +0 -0
  355. {execsql2-2.33.0 → execsql2-2.34.0}/tests/exporters/test_zip.py +0 -0
  356. {execsql2-2.33.0 → execsql2-2.34.0}/tests/gui/__init__.py +0 -0
  357. {execsql2-2.33.0 → execsql2-2.34.0}/tests/gui/test_backends.py +0 -0
  358. {execsql2-2.33.0 → execsql2-2.34.0}/tests/gui/test_backends_extended.py +0 -0
  359. {execsql2-2.33.0 → execsql2-2.34.0}/tests/gui/test_compare_stats.py +0 -0
  360. {execsql2-2.33.0 → execsql2-2.34.0}/tests/gui/test_compute_row_diffs.py +0 -0
  361. {execsql2-2.33.0 → execsql2-2.34.0}/tests/gui/test_desktop_dialogs.py +0 -0
  362. {execsql2-2.33.0 → execsql2-2.34.0}/tests/gui/test_format_table.py +0 -0
  363. {execsql2-2.33.0 → execsql2-2.34.0}/tests/gui/test_tui_pilot.py +0 -0
  364. {execsql2-2.33.0 → execsql2-2.34.0}/tests/gui/test_tui_pilot_complex.py +0 -0
  365. {execsql2-2.33.0 → execsql2-2.34.0}/tests/gui/test_utils_gui_extended.py +0 -0
  366. {execsql2-2.33.0 → execsql2-2.34.0}/tests/importers/__init__.py +0 -0
  367. {execsql2-2.33.0 → execsql2-2.34.0}/tests/importers/conftest.py +0 -0
  368. {execsql2-2.33.0 → execsql2-2.34.0}/tests/importers/test_base_extended.py +0 -0
  369. {execsql2-2.33.0 → execsql2-2.34.0}/tests/importers/test_csv_blank_lines.py +0 -0
  370. {execsql2-2.33.0 → execsql2-2.34.0}/tests/importers/test_csv_edge_cases.py +0 -0
  371. {execsql2-2.33.0 → execsql2-2.34.0}/tests/importers/test_csv_importer.py +0 -0
  372. {execsql2-2.33.0 → execsql2-2.34.0}/tests/importers/test_feather_importer.py +0 -0
  373. {execsql2-2.33.0 → execsql2-2.34.0}/tests/importers/test_json_importer.py +0 -0
  374. {execsql2-2.33.0 → execsql2-2.34.0}/tests/importers/test_ods_importer.py +0 -0
  375. {execsql2-2.33.0 → execsql2-2.34.0}/tests/importers/test_ods_values.py +0 -0
  376. {execsql2-2.33.0 → execsql2-2.34.0}/tests/importers/test_roundtrip_csv.py +0 -0
  377. {execsql2-2.33.0 → execsql2-2.34.0}/tests/importers/test_xls_importer.py +0 -0
  378. {execsql2-2.33.0 → execsql2-2.34.0}/tests/integration/__init__.py +0 -0
  379. {execsql2-2.33.0 → execsql2-2.34.0}/tests/integration/conftest.py +0 -0
  380. {execsql2-2.33.0 → execsql2-2.34.0}/tests/integration/test_dsn.py +0 -0
  381. {execsql2-2.33.0 → execsql2-2.34.0}/tests/integration/test_duckdb.py +0 -0
  382. {execsql2-2.33.0 → execsql2-2.34.0}/tests/integration/test_postgres.py +0 -0
  383. {execsql2-2.33.0 → execsql2-2.34.0}/tests/integration/test_sqlite.py +0 -0
  384. {execsql2-2.33.0 → execsql2-2.34.0}/tests/lsp/__init__.py +0 -0
  385. {execsql2-2.33.0 → execsql2-2.34.0}/tests/lsp/test_cache.py +0 -0
  386. {execsql2-2.33.0 → execsql2-2.34.0}/tests/lsp/test_code_actions.py +0 -0
  387. {execsql2-2.33.0 → execsql2-2.34.0}/tests/lsp/test_completion.py +0 -0
  388. {execsql2-2.33.0 → execsql2-2.34.0}/tests/lsp/test_diagnostics.py +0 -0
  389. {execsql2-2.33.0 → execsql2-2.34.0}/tests/lsp/test_document.py +0 -0
  390. {execsql2-2.33.0 → execsql2-2.34.0}/tests/lsp/test_folding.py +0 -0
  391. {execsql2-2.33.0 → execsql2-2.34.0}/tests/lsp/test_formatting.py +0 -0
  392. {execsql2-2.33.0 → execsql2-2.34.0}/tests/lsp/test_hover.py +0 -0
  393. {execsql2-2.33.0 → execsql2-2.34.0}/tests/lsp/test_lsp_command.py +0 -0
  394. {execsql2-2.33.0 → execsql2-2.34.0}/tests/lsp/test_navigation.py +0 -0
  395. {execsql2-2.33.0 → execsql2-2.34.0}/tests/lsp/test_rename.py +0 -0
  396. {execsql2-2.33.0 → execsql2-2.34.0}/tests/lsp/test_server.py +0 -0
  397. {execsql2-2.33.0 → execsql2-2.34.0}/tests/metacommands/__init__.py +0 -0
  398. {execsql2-2.33.0 → execsql2-2.34.0}/tests/metacommands/conftest.py +0 -0
  399. {execsql2-2.33.0 → execsql2-2.34.0}/tests/metacommands/test_assert.py +0 -0
  400. {execsql2-2.33.0 → execsql2-2.34.0}/tests/metacommands/test_breakpoint.py +0 -0
  401. {execsql2-2.33.0 → execsql2-2.34.0}/tests/metacommands/test_conditions_extra.py +0 -0
  402. {execsql2-2.33.0 → execsql2-2.34.0}/tests/metacommands/test_connect.py +0 -0
  403. {execsql2-2.33.0 → execsql2-2.34.0}/tests/metacommands/test_io_export.py +0 -0
  404. {execsql2-2.33.0 → execsql2-2.34.0}/tests/metacommands/test_io_import.py +0 -0
  405. {execsql2-2.33.0 → execsql2-2.34.0}/tests/metacommands/test_metacommands.py +0 -0
  406. {execsql2-2.33.0 → execsql2-2.34.0}/tests/metacommands/test_metacommands_connect.py +0 -0
  407. {execsql2-2.33.0 → execsql2-2.34.0}/tests/metacommands/test_metacommands_data.py +0 -0
  408. {execsql2-2.33.0 → execsql2-2.34.0}/tests/metacommands/test_metacommands_extended.py +0 -0
  409. {execsql2-2.33.0 → execsql2-2.34.0}/tests/metacommands/test_metacommands_fileops_extra.py +0 -0
  410. {execsql2-2.33.0 → execsql2-2.34.0}/tests/metacommands/test_metacommands_io.py +0 -0
  411. {execsql2-2.33.0 → execsql2-2.34.0}/tests/metacommands/test_metacommands_io_write_extra.py +0 -0
  412. {execsql2-2.33.0 → execsql2-2.34.0}/tests/metacommands/test_metacommands_script_ext.py +0 -0
  413. {execsql2-2.33.0 → execsql2-2.34.0}/tests/metacommands/test_metacommands_system.py +0 -0
  414. {execsql2-2.33.0 → execsql2-2.34.0}/tests/metacommands/test_metacommands_system_extra.py +0 -0
  415. {execsql2-2.33.0 → execsql2-2.34.0}/tests/metacommands/test_pg_upsert.py +0 -0
  416. {execsql2-2.33.0 → execsql2-2.34.0}/tests/metacommands/test_prompt.py +0 -0
  417. {execsql2-2.33.0 → execsql2-2.34.0}/tests/metacommands/test_row_count.py +0 -0
  418. {execsql2-2.33.0 → execsql2-2.34.0}/tests/metacommands/test_serve_output.py +0 -0
  419. {execsql2-2.33.0 → execsql2-2.34.0}/tests/metacommands/test_show_scripts.py +0 -0
  420. {execsql2-2.33.0 → execsql2-2.34.0}/tests/scripts/__init__.py +0 -0
  421. {execsql2-2.33.0 → execsql2-2.34.0}/tests/scripts/fixtures/audit_lint_bad.sql +0 -0
  422. {execsql2-2.33.0 → execsql2-2.34.0}/tests/scripts/fixtures/audit_smoke/sample.json +0 -0
  423. {execsql2-2.33.0 → execsql2-2.34.0}/tests/scripts/fixtures/audit_smoke/sample.jsonl +0 -0
  424. {execsql2-2.33.0 → execsql2-2.34.0}/tests/scripts/fixtures/audit_smoke.sql +0 -0
  425. {execsql2-2.33.0 → execsql2-2.34.0}/tests/scripts/fixtures/config_runtime.sql +0 -0
  426. {execsql2-2.33.0 → execsql2-2.34.0}/tests/scripts/fixtures/control_flow.sql +0 -0
  427. {execsql2-2.33.0 → execsql2-2.34.0}/tests/scripts/fixtures/counters_and_locals.sql +0 -0
  428. {execsql2-2.33.0 → execsql2-2.34.0}/tests/scripts/fixtures/error_handling.sql +0 -0
  429. {execsql2-2.33.0 → execsql2-2.34.0}/tests/scripts/fixtures/import_options/has_comments.csv +0 -0
  430. {execsql2-2.33.0 → execsql2-2.34.0}/tests/scripts/fixtures/import_options.sql +0 -0
  431. {execsql2-2.33.0 → execsql2-2.34.0}/tests/scripts/fixtures/includes/helper.sql +0 -0
  432. {execsql2-2.33.0 → execsql2-2.34.0}/tests/scripts/fixtures/includes.sql +0 -0
  433. {execsql2-2.33.0 → execsql2-2.34.0}/tests/scripts/fixtures/io_formats.sql +0 -0
  434. {execsql2-2.33.0 → execsql2-2.34.0}/tests/scripts/fixtures/io_roundtrip.sql +0 -0
  435. {execsql2-2.33.0 → execsql2-2.34.0}/tests/scripts/fixtures/multi_db.sql +0 -0
  436. {execsql2-2.33.0 → execsql2-2.34.0}/tests/scripts/fixtures/parquet_feather_roundtrip.sql +0 -0
  437. {execsql2-2.33.0 → execsql2-2.34.0}/tests/scripts/fixtures/parse_only/parse_tree.sql +0 -0
  438. {execsql2-2.33.0 → execsql2-2.34.0}/tests/scripts/fixtures/predicates.sql +0 -0
  439. {execsql2-2.33.0 → execsql2-2.34.0}/tests/scripts/fixtures/scripts.sql +0 -0
  440. {execsql2-2.33.0 → execsql2-2.34.0}/tests/scripts/fixtures/smoke.sql +0 -0
  441. {execsql2-2.33.0 → execsql2-2.34.0}/tests/scripts/fixtures/subroutine_loops.sql +0 -0
  442. {execsql2-2.33.0 → execsql2-2.34.0}/tests/scripts/fixtures/subvars_advanced.sql +0 -0
  443. {execsql2-2.33.0 → execsql2-2.34.0}/tests/scripts/fixtures/template_export/greeting.tmpl +0 -0
  444. {execsql2-2.33.0 → execsql2-2.34.0}/tests/scripts/fixtures/template_export.sql +0 -0
  445. {execsql2-2.33.0 → execsql2-2.34.0}/tests/scripts/fixtures/timer.sql +0 -0
  446. {execsql2-2.33.0 → execsql2-2.34.0}/tests/scripts/fixtures/transactions.sql +0 -0
  447. {execsql2-2.33.0 → execsql2-2.34.0}/tests/scripts/fixtures/write_create_table.sql +0 -0
  448. {execsql2-2.33.0 → execsql2-2.34.0}/tests/scripts/fixtures/xlsx_ods_roundtrip.sql +0 -0
  449. {execsql2-2.33.0 → execsql2-2.34.0}/tests/scripts/test_sql_scripts.py +0 -0
  450. {execsql2-2.33.0 → execsql2-2.34.0}/tests/security/__init__.py +0 -0
  451. {execsql2-2.33.0 → execsql2-2.34.0}/tests/security/test_env_detection.py +0 -0
  452. {execsql2-2.33.0 → execsql2-2.34.0}/tests/security/test_expansion_bomb.py +0 -0
  453. {execsql2-2.33.0 → execsql2-2.34.0}/tests/security/test_import_regex_hardening.py +0 -0
  454. {execsql2-2.33.0 → execsql2-2.34.0}/tests/security/test_log_redaction.py +0 -0
  455. {execsql2-2.33.0 → execsql2-2.34.0}/tests/security/test_log_secrets.py +0 -0
  456. {execsql2-2.33.0 → execsql2-2.34.0}/tests/security/test_network_paths.py +0 -0
  457. {execsql2-2.33.0 → execsql2-2.34.0}/tests/security/test_odbc_connection_strings.py +0 -0
  458. {execsql2-2.33.0 → execsql2-2.34.0}/tests/security/test_odbc_injection.py +0 -0
  459. {execsql2-2.33.0 → execsql2-2.34.0}/tests/security/test_path_containment.py +0 -0
  460. {execsql2-2.33.0 → execsql2-2.34.0}/tests/security/test_substitution_injection.py +0 -0
  461. {execsql2-2.33.0 → execsql2-2.34.0}/tests/security/test_zip_bomb.py +0 -0
  462. {execsql2-2.33.0 → execsql2-2.34.0}/tests/templates/__init__.py +0 -0
  463. {execsql2-2.33.0 → execsql2-2.34.0}/tests/templates/conftest.py +0 -0
  464. {execsql2-2.33.0 → execsql2-2.34.0}/tests/templates/test_compare.py +0 -0
  465. {execsql2-2.33.0 → execsql2-2.34.0}/tests/templates/test_glossary.py +0 -0
  466. {execsql2-2.33.0 → execsql2-2.34.0}/tests/templates/test_static.py +0 -0
  467. {execsql2-2.33.0 → execsql2-2.34.0}/tests/templates/test_upsert.py +0 -0
  468. {execsql2-2.33.0 → execsql2-2.34.0}/tests/test_api.py +0 -0
  469. {execsql2-2.33.0 → execsql2-2.34.0}/tests/test_api_errors.py +0 -0
  470. {execsql2-2.33.0 → execsql2-2.34.0}/tests/test_api_extended.py +0 -0
  471. {execsql2-2.33.0 → execsql2-2.34.0}/tests/test_api_filewriter.py +0 -0
  472. {execsql2-2.33.0 → execsql2-2.34.0}/tests/test_api_isolation.py +0 -0
  473. {execsql2-2.33.0 → execsql2-2.34.0}/tests/test_api_subprocess.py +0 -0
  474. {execsql2-2.33.0 → execsql2-2.34.0}/tests/test_ast.py +0 -0
  475. {execsql2-2.33.0 → execsql2-2.34.0}/tests/test_ast_parser.py +0 -0
  476. {execsql2-2.33.0 → execsql2-2.34.0}/tests/test_bundled_templates.py +0 -0
  477. {execsql2-2.33.0 → execsql2-2.34.0}/tests/test_config.py +0 -0
  478. {execsql2-2.33.0 → execsql2-2.34.0}/tests/test_config_data.py +0 -0
  479. {execsql2-2.33.0 → execsql2-2.34.0}/tests/test_config_extended.py +0 -0
  480. {execsql2-2.33.0 → execsql2-2.34.0}/tests/test_date_inference.py +0 -0
  481. {execsql2-2.33.0 → execsql2-2.34.0}/tests/test_debug_repl.py +0 -0
  482. {execsql2-2.33.0 → execsql2-2.34.0}/tests/test_engine.py +0 -0
  483. {execsql2-2.33.0 → execsql2-2.34.0}/tests/test_error_messages.py +0 -0
  484. {execsql2-2.33.0 → execsql2-2.34.0}/tests/test_exceptions.py +0 -0
  485. {execsql2-2.33.0 → execsql2-2.34.0}/tests/test_executor.py +0 -0
  486. {execsql2-2.33.0 → execsql2-2.34.0}/tests/test_executor_inprocess.py +0 -0
  487. {execsql2-2.33.0 → execsql2-2.34.0}/tests/test_format.py +0 -0
  488. {execsql2-2.33.0 → execsql2-2.34.0}/tests/test_interactive.py +0 -0
  489. {execsql2-2.33.0 → execsql2-2.34.0}/tests/test_mail.py +0 -0
  490. {execsql2-2.33.0 → execsql2-2.34.0}/tests/test_metacommand_errors.py +0 -0
  491. {execsql2-2.33.0 → execsql2-2.34.0}/tests/test_models.py +0 -0
  492. {execsql2-2.33.0 → execsql2-2.34.0}/tests/test_package.py +0 -0
  493. {execsql2-2.33.0 → execsql2-2.34.0}/tests/test_parser.py +0 -0
  494. {execsql2-2.33.0 → execsql2-2.34.0}/tests/test_parser_params.py +0 -0
  495. {execsql2-2.33.0 → execsql2-2.34.0}/tests/test_plugins.py +0 -0
  496. {execsql2-2.33.0 → execsql2-2.34.0}/tests/test_reference.py +0 -0
  497. {execsql2-2.33.0 → execsql2-2.34.0}/tests/test_registry.py +0 -0
  498. {execsql2-2.33.0 → execsql2-2.34.0}/tests/test_release_notes.py +0 -0
  499. {execsql2-2.33.0 → execsql2-2.34.0}/tests/test_state.py +0 -0
  500. {execsql2-2.33.0 → execsql2-2.34.0}/tests/test_tooling_versions.py +0 -0
  501. {execsql2-2.33.0 → execsql2-2.34.0}/tests/test_types.py +0 -0
  502. {execsql2-2.33.0 → execsql2-2.34.0}/tests/utils/__init__.py +0 -0
  503. {execsql2-2.33.0 → execsql2-2.34.0}/tests/utils/test_auth.py +0 -0
  504. {execsql2-2.33.0 → execsql2-2.34.0}/tests/utils/test_auth_extra.py +0 -0
  505. {execsql2-2.33.0 → execsql2-2.34.0}/tests/utils/test_crypto.py +0 -0
  506. {execsql2-2.33.0 → execsql2-2.34.0}/tests/utils/test_datetime.py +0 -0
  507. {execsql2-2.33.0 → execsql2-2.34.0}/tests/utils/test_encodedfile_context.py +0 -0
  508. {execsql2-2.33.0 → execsql2-2.34.0}/tests/utils/test_errors.py +0 -0
  509. {execsql2-2.33.0 → execsql2-2.34.0}/tests/utils/test_errors_extra.py +0 -0
  510. {execsql2-2.33.0 → execsql2-2.34.0}/tests/utils/test_fileio.py +0 -0
  511. {execsql2-2.33.0 → execsql2-2.34.0}/tests/utils/test_fileio_extra.py +0 -0
  512. {execsql2-2.33.0 → execsql2-2.34.0}/tests/utils/test_keyring_rules.py +0 -0
  513. {execsql2-2.33.0 → execsql2-2.34.0}/tests/utils/test_mail_tls.py +0 -0
  514. {execsql2-2.33.0 → execsql2-2.34.0}/tests/utils/test_numeric.py +0 -0
  515. {execsql2-2.33.0 → execsql2-2.34.0}/tests/utils/test_regex.py +0 -0
  516. {execsql2-2.33.0 → execsql2-2.34.0}/tests/utils/test_strings.py +0 -0
  517. {execsql2-2.33.0 → execsql2-2.34.0}/tests/utils/test_timer.py +0 -0
  518. {execsql2-2.33.0 → execsql2-2.34.0}/tests/utils/test_timer_extra.py +0 -0
  519. {execsql2-2.33.0 → execsql2-2.34.0}/zensical.toml +0 -0
@@ -13,6 +13,26 @@ ______________________________________________________________________
13
13
 
14
14
  ______________________________________________________________________
15
15
 
16
+ ## [2.34.0] - 2026-10-18
17
+
18
+ ### Added
19
+
20
+ - **`EXPORT … AS JSONL` and `EXPORT QUERY … AS JSONL`** write [JSON Lines](https://jsonlines.org/): one object per row, one row per line. `APPEND` adds rows to the file, which stays valid; `IMPORT … FROM JSON` reads it back.
21
+
22
+ ### Changed
23
+
24
+ - **A value with a fraction loaded into an integer column** (`INTEGER`, `BIGINT`, `DECIMAL(10,0)`, …) of an existing table by `IMPORT` or `COPY` stops the load with an error that names the value and the column, and nothing is loaded. The database used to round it (`1.5` stored as `2`), or on SQL Server truncate it. A delimited file loaded into PostgreSQL or MySQL is not affected: the server reads it as written. Round the values in the source, or load them into a `DECIMAL` column with places for the fraction.
25
+ - **MySQL / MariaDB sessions run in strict mode**: a SQL statement, `IMPORT` or `COPY` that gives a column a value MySQL can't store as given stops with MySQL's error, and nothing is loaded. MySQL used to store a changed value with a warning (`3000000000` as `2147483647`, `2024-02-30` as `0000-00-00`, `abcdef` in a `VARCHAR(3)` as `abc`). A timestamp ending in `Z` is one such value: write it with `+00:00`. To keep the old behavior in a script, run `SET SESSION sql_mode = 'ANSI';` after connecting.
26
+ - **Scripts with many substitution variables run faster**: a statement or metacommand with no `!!`, `!'!` or `!"!` in it is no longer checked against every defined variable. With 500 variables each such line took about 0.1 ms longer; a 1,000-pass `LOOP` over `EXECUTE SCRIPT` with 200 variables now runs in half the time.
27
+
28
+ ### Fixed
29
+
30
+ - **`IMPORT` and `COPY` into a table that already exists** convert each value by the type of the column it goes into, not the type guessed from the source data. A text column gets the value as written: `yes`, `1`, `01/05/2024`, `1e3` and `" 7 "` were stored as `true`, `true`, `2024-01-05 00:00:00`, `1000` and `7`. On MySQL a missing value in such a column is NULL, not `''`. `TO NEW` and `TO REPLACEMENT` tables are created and filled as before.
31
+ - **`COPY` of a column of 0s and 1s into a PostgreSQL integer, numeric or float column** loads the numbers; it failed with "column … is of type integer but expression is of type boolean". The same goes for true/false values (`yes`/`no`, `t`/`f`) copied, or imported from a spreadsheet, JSON or Feather file, into such a column: they load as 1 and 0, as on the other databases.
32
+ - **A missing date or timestamp loaded into MySQL by `IMPORT` or `COPY`** is stored as NULL; it was stored as `0000-00-00` (or `0` in a number column). Delimited files were not affected.
33
+
34
+ ______________________________________________________________________
35
+
16
36
  ## [2.33.0] - 2026-10-16
17
37
 
18
38
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: execsql2
3
- Version: 2.33.0
3
+ Version: 2.34.0
4
4
  Summary: Runs a SQL script against a PostgreSQL, SQLite, MariaDB/MySQL, DuckDB, Firebird, MS-Access, MS-SQL-Server, or Oracle database, or an ODBC DSN. Provides metacommands to import and export data, copy data between databases, conditionally execute SQL and metacommands, and dynamically alter SQL and metacommands with substitution variables.
5
5
  Project-URL: Homepage, https://execsql2.readthedocs.io/en/stable/
6
6
  Project-URL: Repository, https://github.com/geocoug/execsql
@@ -318,7 +318,7 @@ See the [full options reference](https://execsql2.readthedocs.io/en/stable/getti
318
318
  **Data movement**
319
319
 
320
320
  - Import data from CSV, TSV, JSON, Excel, OpenDocument, Feather, or Parquet files into a database table.
321
- - Export query results in 20+ formats including CSV, TSV, JSON, YAML, XML, HTML, Markdown, LaTeX, XLSX, OpenDocument, Feather, Parquet, HDF5, DuckDB, SQLite, plain text, and Jinja2 templates.
321
+ - Export query results in 20+ formats including CSV, TSV, JSON, JSON Lines, YAML, XML, HTML, Markdown, LaTeX, XLSX, OpenDocument, Feather, Parquet, HDF5, DuckDB, SQLite, plain text, and Jinja2 templates.
322
322
  - Copy data between databases, including across different DBMS types.
323
323
 
324
324
  **Script logic**
@@ -441,7 +441,7 @@ execsql format --no-sql --in-place scripts/
441
441
  ```yaml
442
442
  repos:
443
443
  - repo: https://github.com/geocoug/execsql
444
- rev: v2.33.0
444
+ rev: v2.34.0
445
445
  hooks:
446
446
  - id: execsql-format
447
447
  - id: execsql-lint
@@ -198,7 +198,7 @@ See the [full options reference](https://execsql2.readthedocs.io/en/stable/getti
198
198
  **Data movement**
199
199
 
200
200
  - Import data from CSV, TSV, JSON, Excel, OpenDocument, Feather, or Parquet files into a database table.
201
- - Export query results in 20+ formats including CSV, TSV, JSON, YAML, XML, HTML, Markdown, LaTeX, XLSX, OpenDocument, Feather, Parquet, HDF5, DuckDB, SQLite, plain text, and Jinja2 templates.
201
+ - Export query results in 20+ formats including CSV, TSV, JSON, JSON Lines, YAML, XML, HTML, Markdown, LaTeX, XLSX, OpenDocument, Feather, Parquet, HDF5, DuckDB, SQLite, plain text, and Jinja2 templates.
202
202
  - Copy data between databases, including across different DBMS types.
203
203
 
204
204
  **Script logic**
@@ -321,7 +321,7 @@ execsql format --no-sql --in-place scripts/
321
321
  ```yaml
322
322
  repos:
323
323
  - repo: https://github.com/geocoug/execsql
324
- rev: v2.33.0
324
+ rev: v2.34.0
325
325
  hooks:
326
326
  - id: execsql-format
327
327
  - id: execsql-lint
@@ -43,6 +43,7 @@ ______________________________________________________________________
43
43
  | `PARQUET` | Export query or table results to Apache Parquet via `polars`. |
44
44
  | `FEATHER` | Export to Apache Feather/IPC via `polars` (upstream used `pandas` + `pyarrow`). |
45
45
  | `YAML` | Export query or table results as a YAML sequence of mappings via `PyYAML`. |
46
+ | `JSONL` | Export query or table results as JSON Lines: one object per row, one row per line; `APPEND` adds rows (#7). |
46
47
  | `MARKDOWN` / `MD` | Export query or table results as a GitHub-Flavored Markdown (GFM) pipe table. Pure Python, no optional dependencies. |
47
48
  | `XLSX` | Export query or table results to an Excel XLSX workbook via `openpyxl` (single or multi-sheet). |
48
49
 
@@ -228,6 +229,12 @@ Upstream opened MySQL and MariaDB connections as `latin1`, and execsql2 did the
228
229
 
229
230
  The default is now `utf8mb4`. This does not change stored data or column charsets: the connection charset governs only how text moves between client and server, and MySQL transcodes to and from each column's own charset. A latin1 column read over a utf8mb4 connection returns the same string it always did. `-e latin1` restores the upstream behavior.
230
231
 
232
+ ### MySQL Strict Mode
233
+
234
+ Upstream set `sql_mode='ANSI'` on every MySQL and MariaDB connection. That replaces the server's whole mode, and `ANSI` leaves out `STRICT_TRANS_TABLES`, so MySQL stored a changed value with a warning instead of raising an error: `3000000000` in an `INTEGER` column became `2147483647`, `2024-02-30` became `0000-00-00`, and `abcdef` in a `VARCHAR(3)` became `abc`. That applied to plain SQL statements, `IMPORT` and `COPY` alike (#101).
235
+
236
+ The mode is now `ANSI,STRICT_TRANS_TABLES`, so such a value is an error, as in the `mysql` client with the server's default settings. The mode stays fixed rather than added to the server's own, because execsql's quoting of literals relies on backslash escapes being on (no `NO_BACKSLASH_ESCAPES`). `LOAD DATA LOCAL`, which `IMPORT` uses for a delimited file, ignores strict mode, so `IMPORT` checks its warnings and rolls the load back if MySQL changed a value. `SET SESSION sql_mode = 'ANSI';` in a script restores the upstream behavior for that session, `LOAD DATA` included.
237
+
231
238
  ### Database Support Tiers
232
239
 
233
240
  Upstream documented all nine DBMS adapters with equal confidence. execsql2 splits them by what is actually verified: **Supported** (PostgreSQL, MySQL/MariaDB, MS SQL Server, SQLite, DuckDB) run against a live server or a real database file on every CI run, while **best effort** (MS Access, Firebird, Oracle, ODBC DSN) are carried forward from the monolith and are exercised nowhere in CI.
@@ -436,6 +443,9 @@ These are behavioral changes driven by security or correctness issues in the ups
436
443
  | XLSX export wrote `numeric` columns as text | `_cell_value()` passed `int` and `float` straight to openpyxl but let every other type fall through to `str()`. PostgreSQL, MySQL, and DuckDB all return `Decimal` for `numeric`/`decimal` columns, so measurement values arrived in Excel as text cells — invisible in a screenshot, useless in a formula, a sort, or a chart. openpyxl lists `Decimal` in its own `NUMERIC_TYPES` and stores it as a real numeric cell, so the conversion was never needed. Found by exporting from a live PostgreSQL container and checking `cell.data_type` rather than `cell.value`. |
437
444
  | `EMAIL` SMTP login without a password | Upstream calls `smtplib.login()` with only a username when `[email] password` and `enc_password` are both unset, which raises `TypeError` from the stdlib — `login()` has no single-argument form. execsql2 reports a configuration error naming the missing password instead. |
438
445
  | Type scan of large integers | Working out a column's type tested each integer against the binary type by building `bytearray(n)`, n zero bytes, so a column of large integers (epoch milliseconds: about a terabyte per value) hung or ran out of memory. The binary test now answers for an integer without building it; the inferred types are unchanged. |
446
+ | Values loaded into an existing table | `IMPORT` and `COPY` converted every value to the type guessed from the source data, even when the table was already there with its own column types, so a text column got a different string than the source held (`yes` and `1` stored as `true`, `01/05/2024` as `2024-01-05 00:00:00`, `1e3` as `1000`), and on PostgreSQL a column of 0s and 1s copied into an integer column failed (the guess was boolean). For a table that exists, the target column's type now decides: a text column gets the value as written, a number column gets true/false values as 1 and 0, and other columns are converted as before. Each adapter reads the column types from its catalog (`pg_attribute`, `SHOW COLUMNS`, `sys.columns`) or its driver's description of the table. `TO NEW` / `REPLACEMENT` are unchanged. |
447
+ | Missing dates loaded into MySQL | `IMPORT` and `COPY` sent a missing value in a column scanned as a date or timestamp as `''`, so MySQL stored `0000-00-00` (or `0` in a number column) instead of NULL. It is sent as NULL. |
448
+ | Fractions loaded into an integer column | A value with a fraction going into an integer column (including `DECIMAL(p,0)`, `NUMERIC(p)` and Oracle `NUMBER(p)`) of an existing table is an error naming the value and the column. Upstream sent the number and the database rounded it (`1.5` → `2`; SQL Server truncated it to `1`). |
439
449
 
440
450
  ______________________________________________________________________
441
451
 
@@ -8,6 +8,12 @@ If you are adding support for a new database, start with the [Adding Database Ad
8
8
 
9
9
  ::: execsql.db.base
10
10
 
11
+ ## Target column types
12
+
13
+ `IMPORT` and `COPY` into a table that already exists convert each value by the type of the column it goes into (`Database.column_kinds()`), not by the type scanned from the source data.
14
+
15
+ ::: execsql.db.target_types
16
+
11
17
  ## Database factory
12
18
 
13
19
  Convenience constructors used internally to create typed `Database` instances. The CLI calls the appropriate factory function based on the `-t` flag value. Each factory validates its arguments (e.g., checking that a file exists) before constructing and returning the adapter.
@@ -133,23 +133,24 @@ class MyDBDatabase(Database):
133
133
 
134
134
  These are the instance attributes and methods you must configure correctly:
135
135
 
136
- | Attribute / Method | Type | Purpose |
137
- | ------------------ | ----------------- | --------------------------------------------------------------------------------- |
138
- | `self.type` | `DbType` | DBMS type token (e.g., `dbt_sqlite`). Controls quoting and type-mapping. |
139
- | `self.paramstr` | `str` | SQL parameter placeholder: `"?"` (most drivers) or `"%s"` (psycopg, PyMySQL). |
140
- | `self.encoding` | `str` | Database character encoding. Detect from the database on connect if possible. |
141
- | `self.autocommit` | `bool` | `True` means the driver commits automatically; `False` requires explicit commits. |
142
- | `self.conn` | driver connection | Set in `open_db()`. |
143
- | `open_db()` | method | **Must override.** Establish the connection and assign `self.conn`. |
144
- | `table_exists()` | method | **Must override.** Query the DBMS catalog. Use parameterized queries. |
145
- | `column_exists()` | method | **Must override.** Check column presence. |
146
- | `table_columns()` | method | **Must override.** Return column names for a table. |
147
- | `view_exists()` | method | **Must override.** Check view presence. |
148
- | `schema_exists()` | method | **Must override.** Return `False` if schemas are not supported. |
149
- | `drop_table()` | method | **Must override.** Drop a table (used by IMPORT when creating fresh). |
150
- | `populate_table()` | method | **Must override.** Bulk-load rows from a generator (used by IMPORT). |
151
- | `row_inserter()` | method | Override for a faster bulk path that stores exactly what `executemany()` stores. |
152
- | `exec_cmd()` | method | Override if the DBMS can execute stored procedures or views as commands. |
136
+ | Attribute / Method | Type | Purpose |
137
+ | ------------------ | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
138
+ | `self.type` | `DbType` | DBMS type token (e.g., `dbt_sqlite`). Controls quoting and type-mapping. |
139
+ | `self.paramstr` | `str` | SQL parameter placeholder: `"?"` (most drivers) or `"%s"` (psycopg, PyMySQL). |
140
+ | `self.encoding` | `str` | Database character encoding. Detect from the database on connect if possible. |
141
+ | `self.autocommit` | `bool` | `True` means the driver commits automatically; `False` requires explicit commits. |
142
+ | `self.conn` | driver connection | Set in `open_db()`. |
143
+ | `open_db()` | method | **Must override.** Establish the connection and assign `self.conn`. |
144
+ | `table_exists()` | method | **Must override.** Query the DBMS catalog. Use parameterized queries. |
145
+ | `column_exists()` | method | **Must override.** Check column presence. |
146
+ | `table_columns()` | method | **Must override.** Return column names for a table. |
147
+ | `view_exists()` | method | **Must override.** Check view presence. |
148
+ | `schema_exists()` | method | **Must override.** Return `False` if schemas are not supported. |
149
+ | `drop_table()` | method | **Must override.** Drop a table (used by IMPORT when creating fresh). |
150
+ | `populate_table()` | method | **Must override.** Bulk-load rows from a generator (used by IMPORT). |
151
+ | `row_inserter()` | method | Override for a faster bulk path that stores exactly what `executemany()` stores. |
152
+ | `column_kinds()` | method | Override if the driver's description of a table does not tell its column types apart (see `db/target_types.py`); IMPORT and COPY into an existing table convert each value by them. |
153
+ | `exec_cmd()` | method | Override if the DBMS can execute stored procedures or views as commands. |
153
154
 
154
155
  Methods inherited from `Database` that you get for free include `execute()`, `cursor()`, `close()`, `commit()`, `rollback()`, `select_rowsource()`, `select_data()`, `schema_qualified_table_name()`, `quote_identifier()`, and `paramsubs()`.
155
156
 
@@ -264,6 +264,8 @@ ______________________________________________________________________
264
264
 
265
265
  The base class provides shared implementations for `execute()`, `select_data()`, `select_rowsource()`, `select_rowdict()`, `commit()`, `rollback()`, and `populate_table()`.
266
266
 
267
+ `populate_table()` converts each value to the type scanned from the source (`models.DataTable`). For a table that was already there (`existing_table=True`, passed by IMPORT and COPY), `column_kinds()` reports the target columns' types and `db/target_types.py` picks each column's conversion instead: a text column gets the value as given, an integer column whole numbers or an error. `column_kinds()` reads the driver's description of `select * from <table> where 1=0` by default; PostgreSQL, MySQL and SQL Server read their catalogs, where the description cannot tell some types apart. `tests/db/test_existing_table_loads.py` compares what every value type stores in every column type with the record in `tests/data/existing_table_loads/`.
268
+
267
269
  ### DatabasePool
268
270
 
269
271
  `DatabasePool` is a dict-like container mapping string aliases to open `Database` instances. It is the canonical `_state.dbs` object. Key operations:
@@ -272,7 +272,7 @@ select id,name,created_at from users where active = true order by name;
272
272
  ```yaml
273
273
  repos:
274
274
  - repo: https://github.com/geocoug/execsql
275
- rev: v2.33.0
275
+ rev: v2.34.0
276
276
  hooks:
277
277
  - id: execsql-format
278
278
  - id: execsql-lint # optional: fail the commit on lint errors
@@ -4,6 +4,26 @@
4
4
 
5
5
  When execsql connects to a SQL Server or MySQL/MariaDB database, it automatically configures the DBMS to expect ANSI-compatible SQL, to allow the use of more standards-compliant, and thus consistent, SQL. In particular, for MySQL/MariaDB, note that the double-quote character, rather than the backtick character, must be used to quote table, schema, and column names, and only the apostrophe can be used to quote character data.
6
6
 
7
+ ### MySQL strict mode { #mysql_strict_mode }
8
+
9
+ On MySQL/MariaDB, execsql sets the session's `sql_mode` to `ANSI,STRICT_TRANS_TABLES`. A value that MySQL can't store as given is an error, for a SQL statement, [IMPORT](../reference/metacommands.md#import) or [COPY](../reference/metacommands.md#copy):
10
+
11
+ ```sql
12
+ create table m (n integer, d date, s varchar(3));
13
+ insert into m values (3000000000, '2024-02-30', 'abcdef');
14
+ -- ERROR 1264 (22003): Out of range value for column 'n' at row 1
15
+ ```
16
+
17
+ Without strict mode, MySQL would have stored `2147483647`, `0000-00-00` and `abc` with only a warning. This is how the `mysql` client behaves with the server's default settings. An IMPORT of a delimited file that MySQL reads with `LOAD DATA` is held to the same rule: if MySQL warns that it changed a value, the IMPORT stops and nothing is loaded.
18
+
19
+ MySQL in strict mode does not read a trailing `Z` as a time zone: write `2024-01-05T10:00:00Z` as `2024-01-05 10:00:00+00:00`. A decimal with more places than its column has (`1.2345` into `DECIMAL(10,3)`) is still rounded, as MySQL does in strict mode.
20
+
21
+ A script that relies on MySQL's changed values can turn strict mode off for the rest of its session:
22
+
23
+ ```sql
24
+ SET SESSION sql_mode = 'ANSI';
25
+ ```
26
+
7
27
  ## Implicit Commits
8
28
 
9
29
  By default, execsql immediately commits all SQL statements. The [AUTOCOMMIT](../reference/metacommands.md#autocommit) metacommand can be used to turn off automatic commits, and the [BATCH](../reference/metacommands.md#batch) metacommand can be used to delay commits until the end of a batch. [IMPORT](../reference/metacommands.md#import) and [COPY](../reference/metacommands.md#copy) are the only metacommands that change data, and they also automatically commit their changes when complete (unless [AUTOCOMMIT](../reference/metacommands.md#autocommit) has been turned off). If a new table is created with either of these metacommands (through the use of the NEW or REPLACEMENT keywords), the CREATE TABLE statement will not be committed separately from the data addition, except when using Firebird. Thus, if an error occurs during addition of the data, the new target table will not exist---except when using Firebird.
@@ -9,7 +9,7 @@
9
9
  You can use *execsql* to:
10
10
 
11
11
  - Import data from text files and spreadsheets into a database.
12
- - Export tables and views to ~30 data formats including CSV, OpenDocument spreadsheets, XLSX, HTML, JSON, YAML, Markdown, LaTeX, XML, Parquet, Feather, HDF5, SQLite, and DuckDB.
12
+ - Export tables and views to ~30 data formats including CSV, OpenDocument spreadsheets, XLSX, HTML, JSON, JSON Lines, YAML, Markdown, LaTeX, XML, Parquet, Feather, HDF5, SQLite, and DuckDB.
13
13
  - Copy data between different databases, even databases using different DBMSs.
14
14
  - Display tables or views on the terminal or in a GUI dialog window.
15
15
  - Export data using template processors to produce non-tabular output with customized format and contents.
@@ -158,7 +158,7 @@ The `execsql-lint` [pre-commit](https://pre-commit.com/) hook runs `execsql lint
158
158
  ```yaml
159
159
  repos:
160
160
  - repo: https://github.com/geocoug/execsql
161
- rev: v2.33.0
161
+ rev: v2.34.0
162
162
  hooks:
163
163
  - id: execsql-lint
164
164
  args: [--ignore, V002] # optional: any execsql lint option
@@ -889,6 +889,8 @@ Copies the data from a data table or view in one database to a data table in a s
889
889
 
890
890
  The second (destination) table must have column names that are identical to the names of the columns in the first (source) table. The second table may have additional columns; if it does, they will not be affected and their names don't matter. The data types in the columns to be copied must be compatible, though not necessarily identical. The order of the columns in the two tables does not have to be identical.
891
891
 
892
+ When neither "NEW" nor "REPLACEMENT" is used, each value is converted for the destination column it goes into, as for [IMPORT into an existing table](#import_existing_table_types): a text column gets the value as the source database returned it (an integer `1` is stored as `1`, a date as `2024-01-05`), an integer column gets whole numbers and stops the COPY with an error on a value with a fraction instead of rounding it, and a number column gets a column of 0s and 1s, or of true/false values, as 1 and 0.
893
+
892
894
  If the "NEW" keyword is used, the destination table will be automatically created with column names and data types that are compatible with the first (source) table. The data types used for the columns in the newly created table will be determined by a scan of all of the data in the first table, but may not exactly match those in the first table. A text column stays text, whatever its values look like: `00123`, `1`/`0` and `2024-01-05` are copied as written, not as numbers, booleans or dates. The source is read once; while its rows are scanned they are held in a temporary file, so a large copy does not need memory for the whole table. If the destination table already exists when the "NEW" keyword is used, the copy stops before creating anything, with the error `Table <name> already exists`; a name without a schema is looked for only in the schema the table would be created in (on MySQL, the current database)
893
895
 
894
896
  If the "REPLACEMENT" keyword is used, the destination table will also be created to be compatible with the source table, but any existing destination table of the same name will be dropped first. This replacement is not atomic: if the drop succeeds but a later create or load step fails, the old destination table has already been removed. If the drop fails, *execsql* stops the COPY command before creating or loading the replacement table. *execsql* uses a "drop table" statement to drop an existing destination table, and this statement may not succeed if there are dependencies on that table (see the discussion of [implicit drop table
@@ -1165,6 +1167,15 @@ JSON
1165
1167
  : [Javascript Object Notation](http://www.json.org/). The data table is represented as an array of JSON objects, where each object represents a row of the table. Each row is represented as a set of key:value pairs, with column names used as the keys. No description text will be included in the output even if it is provided.
1166
1168
 
1167
1169
 
1170
+ JSONL
1171
+
1172
+ : [JSON Lines](https://jsonlines.org/): one JSON object per row, one row per line, with no surrounding array. Values are written as for JSON (dates and times as text, NULL as `null`); a line break inside a value is written as `\n`, so each row stays on one line. If the "APPEND" keyword is used, the rows are added to the end of the file, which stays valid JSON Lines (appending JSON instead leaves two arrays in one file). A query that returns no rows writes an empty file. The file can be read back with [IMPORT … FROM JSON](#import). No description text will be included in the output even if it is provided:
1173
+
1174
+ ```
1175
+ -- !x! EXPORT QUERY <<select * from events where day = current_date;>> APPEND TO events.jsonl AS JSONL
1176
+ ```
1177
+
1178
+
1168
1179
  JSON_TS or JSON_TABLESCHEMA
1169
1180
 
1170
1181
  : [JSON Table Schema](https://frictionlessdata.io/specs/table-schema/). The data themselves are not exported. Ordinarily this form would be used in conjunction with an additional export of the data to a CSV file. The column type descriptions in the output are derived by scanning the entire table to evaluate the data type of each column. If a "NOTYPE" keyword is included before the "DESCRIPTION" keyword, this scanning is not done, and no type information is included in the output file.:
@@ -1945,7 +1956,7 @@ The syntax for importing data from a [JSON](https://www.json.org/) file is:
1945
1956
  IMPORT TO [NEW|REPLACEMENT] <table_name> FROM JSON <file_name>
1946
1957
  ```
1947
1958
 
1948
- The JSON file must contain either a JSON array of objects (`[{…}, …]`) or [JSON Lines](https://jsonlines.org/) (JSONL, one object per line). Nested objects are flattened with dot-separated column names (e.g., an object `{"address": {"city": "Portland"}}` produces column `address.city`). Nested arrays within objects are stored as JSON strings. Records with different keys produce a superset of columns — missing keys become NULL. The parser auto-detects which form is present by peeking at the first non-whitespace character (`[` → array, `{` → JSON Lines); the file extension is not consulted.
1959
+ The JSON file must contain either a JSON array of objects (`[{…}, …]`) or [JSON Lines](https://jsonlines.org/) (JSONL, one object per line). Nested objects are flattened with dot-separated column names (e.g., an object `{"address": {"city": "Portland"}}` produces column `address.city`). Nested arrays within objects are stored as JSON strings. Records with different keys produce a superset of columns — missing keys become NULL. The parser auto-detects which form is present by peeking at the first non-whitespace character (`[` → array, `{` → JSON Lines); the file extension is not consulted. A file written by [EXPORT … AS JSONL](#export) is read as JSON Lines.
1949
1960
 
1950
1961
  Column names in the input must be valid for the DBMS in use.
1951
1962
 
@@ -1968,7 +1979,15 @@ A data line with fewer values than the header has NULL for the values left off t
1968
1979
 
1969
1980
  The rows are loaded in one transaction: if the load fails part-way (a constraint violation, a value of the wrong type, a row with too many columns), none of the file's rows are kept, on every DBMS. The exception is an ODBC DSN whose driver can only autocommit, which keeps the rows loaded before the failure; *execsql* warns once on such a connection.
1970
1981
 
1971
- If neither the NEW or REPLACEMENT keywords are used, the table must exist, must have column names identical to those in the input data, and columns must have data types that are compatible with (though not necessarily identical to) those in the input data. If neither the NEW or REPLACEMENT keywords are used, the input data is not scanned to determine the data type of each column.
1982
+ <a id="import_existing_table_types"></a>
1983
+ If neither the NEW or REPLACEMENT keywords are used, the table must exist, must have column names identical to those in the input data, and columns must have data types that are compatible with (though not necessarily identical to) those in the input data. Each value is converted for the column it goes into:
1984
+
1985
+ - A text column (`CHAR`, `VARCHAR`, `TEXT`, …) gets the value as written in the file: `yes`, `00701`, `01/05/2024` and `1e3` are stored as those strings. A missing value is NULL.
1986
+ - An integer column (`INTEGER`, `BIGINT`, `DECIMAL(10,0)`, `NUMERIC(10)`, Oracle `NUMBER(10)`, …) gets whole numbers: `7`, `+7`, `7.0` and `7e0` load as 7. A value with a fraction, such as `1.5`, stops the IMPORT with an error that names the value and the column, and nothing is loaded; it is not rounded.
1987
+ - A number column gets a column of true/false values (`yes`/`no`, `t`/`f`, `1`/`0`; see [BOOLEAN_INT](#boolean_int) and [BOOLEAN_WORDS](#boolean_words)) as 1 and 0.
1988
+ - Any other column (a date, a timestamp, a Boolean, a decimal with places for a fraction) gets the value converted to the data type found by scanning that column of the input, as with NEW, and the database converts it to the column's type.
1989
+
1990
+ On PostgreSQL and MySQL a delimited text file goes to the server's own loader (`COPY`, `LOAD DATA`, or on PostgreSQL an `INSERT` of each value as text), and the server converts each value as written; the rules above apply to the other file formats, and to delimited files on the other DBMSs. On MySQL a value that the server can't store as given (out of range, an invalid date, text longer than the column) stops the IMPORT with an error, and nothing is loaded; see [MySQL strict mode](../guides/sql_syntax.md#mysql_strict_mode).
1972
1991
 
1973
1992
  If a table is scanned to determine data types, any column that is completely empty (all null) will be created with the text data type. This provides the greatest flexibility for subsequent addition of data to the table. However, if that column ought to have a different data type, and a WHERE clause is applied to that column assuming a different data type, the DBMS may report an error because of incomparable data types.
1974
1993
 
@@ -2032,7 +2051,7 @@ When MS-Excel saves an OpenDocument spreadsheet, it may create an additional emp
2032
2051
  Because the data addition to the target table is always committed except when [AUTOCOMMIT](#autocommit) is off, the IMPORT metacommand should be used with care within transactions that are managed with explicit SQL statements or with [BATCHes](#batch).
2033
2052
 
2034
2053
 
2035
- In general, if an error occurs while importing data, none of the new data should be in the target table (the operation is not committed). However, MySQL/MariaDB may issue messages about data type incompatibility to the standard error device (ordinarily the terminal), yet load some or all of the data. If the NEW or REPLACEMENT keywords are used, depending on the DBMS and where the error occurred, the target table may be created even if the data are not loaded.
2054
+ In general, if an error occurs while importing data, none of the new data should be in the target table (the operation is not committed). On MySQL/MariaDB a value that the server would change to store it is an error too, unless the script has turned off [strict mode](../guides/sql_syntax.md#mysql_strict_mode). If the NEW or REPLACEMENT keywords are used, depending on the DBMS and where the error occurred, the target table may be created even if the data are not loaded.
2036
2055
 
2037
2056
  The name, size, and date of the IMPORTed file are written to the [execsql.log](../guides/logging.md#logging) file.
2038
2057
 
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "execsql-syntax",
3
- "version": "2.33.0",
3
+ "version": "2.34.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "execsql-syntax",
9
- "version": "2.33.0",
9
+ "version": "2.34.0",
10
10
  "license": "GPL-3.0-or-later",
11
11
  "dependencies": {
12
12
  "vscode-languageclient": "10.1.2"
@@ -2,7 +2,7 @@
2
2
  "name": "execsql-syntax",
3
3
  "displayName": "execsql",
4
4
  "description": "Syntax highlighting, lint findings, completion, hover, navigation, quick fixes and formatting for execsql scripts.",
5
- "version": "2.33.0",
5
+ "version": "2.34.0",
6
6
  "publisher": "geocoug",
7
7
  "license": "GPL-3.0-or-later",
8
8
  "icon": "icon.png",
@@ -118,7 +118,7 @@
118
118
  },
119
119
  "export-formats": {
120
120
  "comment": "Export/import format names",
121
- "match": "(?i)\\b(json_tableschema|cgi-html|markdown|feather|json_ts|txt-and|unitsep|duckdb|sqlite|values|latex|plain|hdf5|html|json|tabq|tsvq|xlsx|yaml|b64|csv|ods|raw|tab|tsv|txt|xml|md|us)\\b",
121
+ "match": "(?i)\\b(json_tableschema|cgi-html|markdown|feather|json_ts|txt-and|unitsep|duckdb|sqlite|values|jsonl|latex|plain|hdf5|html|json|tabq|tsvq|xlsx|yaml|b64|csv|ods|raw|tab|tsv|txt|xml|md|us)\\b",
122
122
  "name": "support.constant.execsql"
123
123
  },
124
124
  "secondary-operators": {
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "execsql2"
7
- version = "2.33.0"
7
+ version = "2.34.0"
8
8
  description = "Runs a SQL script against a PostgreSQL, SQLite, MariaDB/MySQL, DuckDB, Firebird, MS-Access, MS-SQL-Server, or Oracle database, or an ODBC DSN. Provides metacommands to import and export data, copy data between databases, conditionally execute SQL and metacommands, and dynamically alter SQL and metacommands with substitution variables."
9
9
  readme = { file = "README.md", content-type = "text/markdown" }
10
10
  license = { file = "LICENSE.txt" }
@@ -188,7 +188,7 @@ line-ending = "auto"
188
188
  exclude = ["*.md"]
189
189
 
190
190
  [tool.bumpversion]
191
- current_version = "2.33.0"
191
+ current_version = "2.34.0"
192
192
  commit = true
193
193
  tag = true
194
194
  tag_name = "v{new_version}"
@@ -24,7 +24,9 @@ from collections.abc import Callable, Generator, Iterator
24
24
 
25
25
  from execsql.exceptions import ErrInfo
26
26
  from execsql.utils.errors import exception_desc
27
+ from execsql.db.target_types import ColumnKinds, existing_table_converter, kind_for_python_type
27
28
  from execsql.db.tiers import SupportTier, announce_tier
29
+ from execsql.types import DataType
28
30
  import execsql.state as _state
29
31
 
30
32
  __all__ = ["Database", "DatabasePool"]
@@ -604,6 +606,42 @@ class Database(ABC):
604
606
  rows = curs.fetchall()
605
607
  return [row[0] for row in rows]
606
608
 
609
+ def column_kinds(self, sq_name: str) -> ColumnKinds:
610
+ """The execsql data type each column of table *sq_name* converts values to (see ``db.target_types``).
611
+
612
+ *sq_name* is schema-qualified and quoted, as for an ``INSERT``, and
613
+ is looked up the way the ``INSERT`` finds it. Keys are lower-cased
614
+ column names; a column with no fitting execsql type maps to ``None``.
615
+ This default reads the driver's description of an empty query on the
616
+ table; adapters whose catalog names types more exactly override it.
617
+ """
618
+ description, _rows = self._column_type_query(sq_name, f"select * from {sq_name} where 1=0;")
619
+ return {d[0].lower(): self._described_kind(d) for d in description}
620
+
621
+ def _described_kind(self, description: Any) -> type[DataType] | None:
622
+ """The execsql type for one column of a cursor description (a DB-API 7-item sequence)."""
623
+ return kind_for_python_type(description[1], description[5])
624
+
625
+ def _column_type_query(self, sq_name: str, sql: str, params: list | None = None) -> tuple[Any, list]:
626
+ """Run *sql* for :meth:`column_kinds` of *sq_name*; return the cursor description and the rows."""
627
+ with self._cursor() as curs:
628
+ try:
629
+ if params is None:
630
+ curs.execute(sql)
631
+ else:
632
+ curs.execute(sql, params)
633
+ except ErrInfo:
634
+ raise
635
+ except Exception as e:
636
+ self.rollback()
637
+ raise ErrInfo(
638
+ type="db",
639
+ command_text=sql,
640
+ exception_msg=exception_desc(),
641
+ other_msg=f"Failed to get the column types of table {sq_name} of {self.name()}",
642
+ ) from e
643
+ return curs.description, curs.fetchall()
644
+
607
645
  def view_exists(self, view_name: str, schema_name: str | None = None) -> bool:
608
646
  """Return ``True`` if *view_name* (optionally in *schema_name*) exists."""
609
647
  with self._cursor() as curs:
@@ -689,11 +727,17 @@ class Database(ABC):
689
727
  rowsource: Any,
690
728
  column_list: list[str],
691
729
  tablespec_src: Callable,
730
+ *,
731
+ existing_table: bool = False,
692
732
  ) -> None:
693
733
  """Bulk-insert rows from *rowsource* into *table_name* using the columns in *column_list*.
694
734
 
695
735
  *rowsource* must be a generator yielding lists of values in column order.
696
736
  *tablespec_src* is a zero-argument callable that returns the table's type specification.
737
+ Each value is converted to the type scanned for its column; with
738
+ *existing_table* (the table was there before the IMPORT or COPY), the
739
+ type of the column it goes into decides instead (see
740
+ ``db.target_types.existing_table_converter``).
697
741
  """
698
742
  # The rowsource argument must be a generator yielding a list of values for the columns of the table.
699
743
  # The column_list argument must an iterable containing column names. This may be a subset of
@@ -713,8 +757,13 @@ class Database(ABC):
713
757
  sel_cols = [col for col in ts_colnames if col in column_list]
714
758
  incl_col = [col in column_list for col in ts_colnames]
715
759
  # Type conversion functions for the rowsource.
716
- type_objs = [col.column_type()[1]() for col in tablespec.cols]
717
- type_mod_fn = [self.type.dialect[col.column_type()[1]][3] for col in tablespec.cols]
760
+ converters = [self._scanned_type_converter(col.column_type()[1]) for col in tablespec.cols]
761
+ if existing_table:
762
+ kinds = self.column_kinds(sq_name)
763
+ converters = [
764
+ existing_table_converter(kinds.get(col.name.lower()), col.column_type()[1], convert, col.name, sq_name)
765
+ for col, convert in zip(tablespec.cols, converters)
766
+ ]
718
767
  # Construct INSERT statement.
719
768
  columns = [self.type.quoted(col) for col in sel_cols]
720
769
  colspec = ",".join(columns)
@@ -812,10 +861,7 @@ class Database(ABC):
812
861
  line[i] = None
813
862
  # Trailing values left off the line are NULL.
814
863
  line = list(line) + [None] * (len(ts_colnames) - len(line))
815
- lt = [
816
- type_objs[i].from_data(val) if val is not None else None for i, val in enumerate(line)
817
- ]
818
- lt = [type_mod_fn[i](v) if type_mod_fn[i] else v for i, v in enumerate(lt)]
864
+ lt = [converters[i](val) for i, val in enumerate(line)]
819
865
  row = []
820
866
  for i, v in enumerate(lt):
821
867
  if incl_col[i]:
@@ -869,14 +915,31 @@ class Database(ABC):
869
915
  f"IMPORT into {sq_name} complete: {total_rows} rows imported.",
870
916
  )
871
917
 
918
+ def _scanned_type_converter(self, data_type: type[DataType]) -> Callable[[Any], Any]:
919
+ """Convert a value to *data_type*, then apply this DBMS's adjustment for that type, if any.
920
+
921
+ NULL is not converted, but it is adjusted: MySQL's adjustment for
922
+ dates turns it into ``""``.
923
+ """
924
+ from_data = data_type().from_data
925
+ adjust = self.type.dialect[data_type][3]
926
+ if adjust is None:
927
+ return lambda value: None if value is None else from_data(value)
928
+ return lambda value: adjust(None if value is None else from_data(value))
929
+
872
930
  def import_tabular_file(
873
931
  self,
874
932
  schema_name: str | None,
875
933
  table_name: str,
876
934
  csv_file_obj: Any,
877
935
  skipheader: bool,
936
+ *,
937
+ existing_table: bool = False,
878
938
  ) -> None:
879
- """Import a CSV/tabular file into *table_name*; column names must be compatible."""
939
+ """Import a CSV/tabular file into *table_name*; column names must be compatible.
940
+
941
+ *existing_table*: the table was there before the IMPORT (see :meth:`populate_table`).
942
+ """
880
943
  # Import a text (CSV) file containing tabular data to a table. Columns must be compatible.
881
944
  if not self.table_exists(table_name, schema_name):
882
945
  raise ErrInfo(
@@ -912,7 +975,7 @@ class Database(ABC):
912
975
 
913
976
  f = csv_file_obj.reader()
914
977
  next(f)
915
- self.populate_table(schema_name, table_name, f, import_cols, get_ts)
978
+ self.populate_table(schema_name, table_name, f, import_cols, get_ts, existing_table=existing_table)
916
979
 
917
980
  def import_entire_file(
918
981
  self,
@@ -18,6 +18,8 @@ from pathlib import Path
18
18
  from typing import Any
19
19
 
20
20
  from execsql.db.base import Database, statement_start
21
+ from execsql.db.target_types import kind_for_type_name
22
+ from execsql.types import DataType
21
23
  from execsql.exceptions import ErrInfo
22
24
  from execsql.utils.errors import exception_desc, fatal_error, install_hint
23
25
  import execsql.state as _state
@@ -320,6 +322,10 @@ class DuckDBDatabase(Database):
320
322
 
321
323
  return insert
322
324
 
325
+ def _described_kind(self, description: Any) -> type[DataType] | None:
326
+ """DuckDB describes a column by its type's name (``DECIMAL(18,3)``, ``TIMESTAMP WITH TIME ZONE``)."""
327
+ return kind_for_type_name(str(description[1]))
328
+
323
329
  def view_exists(self, view_name: str, schema_name: str | None = None) -> bool:
324
330
  """Return True if the named view exists in the DuckDB database."""
325
331
  # DuckDB information_schema has no 'views' table; views are listed in 'tables'