execsql2 2.4.4__py3-none-any.whl → 2.5.0__py3-none-any.whl

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 (62) hide show
  1. execsql/cli/__init__.py +14 -0
  2. execsql/cli/dsn.py +2 -0
  3. execsql/cli/help.py +2 -0
  4. execsql/cli/run.py +4 -2
  5. execsql/constants.py +11 -0
  6. execsql/db/access.py +20 -0
  7. execsql/db/base.py +4 -0
  8. execsql/db/dsn.py +11 -8
  9. execsql/db/duckdb.py +12 -8
  10. execsql/db/firebird.py +17 -8
  11. execsql/db/mysql.py +13 -8
  12. execsql/db/oracle.py +22 -8
  13. execsql/db/postgres.py +21 -9
  14. execsql/db/sqlite.py +18 -8
  15. execsql/db/sqlserver.py +14 -8
  16. execsql/exporters/__init__.py +6 -1
  17. execsql/exporters/base.py +2 -0
  18. execsql/exporters/delimited.py +10 -0
  19. execsql/exporters/protocol.py +128 -0
  20. execsql/exporters/xls.py +8 -0
  21. execsql/format.py +3 -1
  22. execsql/gui/__init__.py +2 -0
  23. execsql/gui/base.py +2 -0
  24. execsql/gui/console.py +2 -0
  25. execsql/gui/desktop.py +1 -0
  26. execsql/gui/tui.py +2 -0
  27. execsql/importers/base.py +1 -0
  28. execsql/importers/csv.py +2 -0
  29. execsql/importers/feather.py +2 -0
  30. execsql/importers/ods.py +1 -0
  31. execsql/importers/xls.py +1 -0
  32. execsql/metacommands/__init__.py +206 -0
  33. execsql/metacommands/dispatch.py +93 -2
  34. execsql/metacommands/io.py +41 -0
  35. execsql/models.py +17 -0
  36. execsql/parser.py +41 -0
  37. execsql/script/control.py +2 -0
  38. execsql/script/engine.py +19 -0
  39. execsql/script/variables.py +9 -5
  40. execsql/state.py +52 -0
  41. execsql/types.py +46 -0
  42. {execsql2-2.4.4.dist-info → execsql2-2.5.0.dist-info}/METADATA +31 -4
  43. {execsql2-2.4.4.dist-info → execsql2-2.5.0.dist-info}/RECORD +62 -61
  44. {execsql2-2.4.4.data → execsql2-2.5.0.data}/data/execsql2_extras/README.md +0 -0
  45. {execsql2-2.4.4.data → execsql2-2.5.0.data}/data/execsql2_extras/config_settings.sqlite +0 -0
  46. {execsql2-2.4.4.data → execsql2-2.5.0.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
  47. {execsql2-2.4.4.data → execsql2-2.5.0.data}/data/execsql2_extras/execsql.conf +0 -0
  48. {execsql2-2.4.4.data → execsql2-2.5.0.data}/data/execsql2_extras/make_config_db.sql +0 -0
  49. {execsql2-2.4.4.data → execsql2-2.5.0.data}/data/execsql2_extras/md_compare.sql +0 -0
  50. {execsql2-2.4.4.data → execsql2-2.5.0.data}/data/execsql2_extras/md_glossary.sql +0 -0
  51. {execsql2-2.4.4.data → execsql2-2.5.0.data}/data/execsql2_extras/md_upsert.sql +0 -0
  52. {execsql2-2.4.4.data → execsql2-2.5.0.data}/data/execsql2_extras/pg_compare.sql +0 -0
  53. {execsql2-2.4.4.data → execsql2-2.5.0.data}/data/execsql2_extras/pg_glossary.sql +0 -0
  54. {execsql2-2.4.4.data → execsql2-2.5.0.data}/data/execsql2_extras/pg_upsert.sql +0 -0
  55. {execsql2-2.4.4.data → execsql2-2.5.0.data}/data/execsql2_extras/script_template.sql +0 -0
  56. {execsql2-2.4.4.data → execsql2-2.5.0.data}/data/execsql2_extras/ss_compare.sql +0 -0
  57. {execsql2-2.4.4.data → execsql2-2.5.0.data}/data/execsql2_extras/ss_glossary.sql +0 -0
  58. {execsql2-2.4.4.data → execsql2-2.5.0.data}/data/execsql2_extras/ss_upsert.sql +0 -0
  59. {execsql2-2.4.4.dist-info → execsql2-2.5.0.dist-info}/WHEEL +0 -0
  60. {execsql2-2.4.4.dist-info → execsql2-2.5.0.dist-info}/entry_points.txt +0 -0
  61. {execsql2-2.4.4.dist-info → execsql2-2.5.0.dist-info}/licenses/LICENSE.txt +0 -0
  62. {execsql2-2.4.4.dist-info → execsql2-2.5.0.dist-info}/licenses/NOTICE +0 -0
@@ -212,6 +212,212 @@ from execsql.utils.regex import (
212
212
  )
213
213
  from execsql.script import MetaCommandList # noqa: F401
214
214
 
215
+ __all__ = [
216
+ # execsql.state
217
+ "_state",
218
+ # connect handlers
219
+ "x_connect_pg",
220
+ "x_connect_user_pg",
221
+ "x_connect_ssvr",
222
+ "x_connect_user_ssvr",
223
+ "x_connect_mysql",
224
+ "x_connect_user_mysql",
225
+ "x_connect_access",
226
+ "x_connect_fb",
227
+ "x_connect_user_fb",
228
+ "x_connect_ora",
229
+ "x_connect_user_ora",
230
+ "x_connect_duckdb",
231
+ "x_connect_sqlite",
232
+ "x_connect_dsn",
233
+ "x_use",
234
+ "x_disconnect",
235
+ "x_autocommit_on",
236
+ "x_autocommit_off",
237
+ "x_pg_vacuum",
238
+ "x_daoflushdelay",
239
+ # control handlers
240
+ "x_if",
241
+ "x_if_orif",
242
+ "x_if_andif",
243
+ "x_if_elseif",
244
+ "x_if_else",
245
+ "x_if_block",
246
+ "x_if_end",
247
+ "x_loop",
248
+ "x_halt",
249
+ "x_halt_msg",
250
+ "x_error_halt",
251
+ "x_metacommand_error_halt",
252
+ "x_begin_batch",
253
+ "x_end_batch",
254
+ "x_rollback",
255
+ "x_break",
256
+ "x_wait_until",
257
+ # data handlers
258
+ "x_sub",
259
+ "x_sub_add",
260
+ "x_sub_append",
261
+ "x_sub_empty",
262
+ "x_rm_sub",
263
+ "x_sub_local",
264
+ "x_sub_tempfile",
265
+ "x_sub_ini",
266
+ "x_sub_querystring",
267
+ "x_sub_encrypt",
268
+ "x_sub_decrypt",
269
+ "x_subdata",
270
+ "x_selectsub",
271
+ "x_prompt_selectsub",
272
+ "x_empty_strings",
273
+ "x_trim_strings",
274
+ "x_replace_newlines",
275
+ "x_empty_rows",
276
+ "x_only_strings",
277
+ "x_boolean_int",
278
+ "x_boolean_words",
279
+ "x_fold_col_hdrs",
280
+ "x_trim_col_hdrs",
281
+ "x_clean_col_hdrs",
282
+ "x_del_empty_cols",
283
+ "x_create_col_hdrs",
284
+ "x_dedup_col_hdrs",
285
+ "x_import_common_cols_only",
286
+ "x_quote_all_text",
287
+ "x_reset_counter",
288
+ "x_reset_counters",
289
+ "x_set_counter",
290
+ "x_max_int",
291
+ # debug handlers
292
+ "x_debug_write_metacommands",
293
+ "x_debug_commandliststack",
294
+ "x_debug_iflevels",
295
+ "x_debug_write_odbc_drivers",
296
+ "x_debug_log_subvars",
297
+ "x_debug_log_config",
298
+ "x_debug_write_subvars",
299
+ "x_debug_write_config",
300
+ # io handlers
301
+ "x_export",
302
+ "x_export_query",
303
+ "x_export_query_with_template",
304
+ "x_export_with_template",
305
+ "x_export_ods_multiple",
306
+ "x_export_metadata",
307
+ "x_export_metadata_table",
308
+ "x_import",
309
+ "x_import_file",
310
+ "x_import_ods",
311
+ "x_import_ods_pattern",
312
+ "x_import_xls",
313
+ "x_import_xls_pattern",
314
+ "x_import_parquet",
315
+ "x_import_feather",
316
+ "x_import_row_buffer",
317
+ "x_show_progress",
318
+ "x_export_row_buffer",
319
+ "x_write",
320
+ "x_write_create_table",
321
+ "x_write_create_table_ods",
322
+ "x_write_create_table_xls",
323
+ "x_write_create_table_alias",
324
+ "x_write_prefix",
325
+ "x_write_suffix",
326
+ "x_writescript",
327
+ "x_include",
328
+ "x_copy",
329
+ "x_copy_query",
330
+ "x_zip",
331
+ "x_zip_buffer_mb",
332
+ "x_rm_file",
333
+ "x_make_export_dirs",
334
+ "x_cd",
335
+ "x_scan_lines",
336
+ "x_hdf5_text_len",
337
+ "x_serve",
338
+ # prompt handlers
339
+ "x_prompt",
340
+ "x_prompt_enter",
341
+ "x_prompt_entryform",
342
+ "x_prompt_pause",
343
+ "x_prompt_compare",
344
+ "x_prompt_ask_compare",
345
+ "x_prompt_ask",
346
+ "x_prompt_map",
347
+ "x_prompt_action",
348
+ "x_prompt_savefile",
349
+ "x_prompt_openfile",
350
+ "x_prompt_directory",
351
+ "x_prompt_select_rows",
352
+ "x_prompt_credentials",
353
+ "x_prompt_connect",
354
+ "x_ask",
355
+ "x_pause",
356
+ "x_msg",
357
+ "x_reset_dialog_canceled",
358
+ # script_ext handlers
359
+ "x_extendscript",
360
+ "x_extendscript_metacommand",
361
+ "x_extendscript_sql",
362
+ "x_executescript",
363
+ # system handlers
364
+ "x_system_cmd",
365
+ "x_email",
366
+ "x_timer",
367
+ "x_log",
368
+ "x_logwritemessages",
369
+ "x_log_datavars",
370
+ "x_log_sql",
371
+ "x_console",
372
+ "x_consoleprogress",
373
+ "x_consolewait",
374
+ "x_consolewait_onerror",
375
+ "x_consolewait_whendone",
376
+ "x_console_hideshow",
377
+ "x_consolewidth",
378
+ "x_consoleheight",
379
+ "x_consolestatus",
380
+ "x_consolesave",
381
+ "x_cancel_halt",
382
+ "x_cancel_halt_write_clear",
383
+ "x_cancel_halt_write",
384
+ "x_cancel_halt_email_clear",
385
+ "x_cancel_halt_email",
386
+ "x_cancel_halt_exec",
387
+ "x_cancel_halt_exec_clear",
388
+ "x_error_halt_write_clear",
389
+ "x_error_halt_write",
390
+ "x_error_halt_email_clear",
391
+ "x_error_halt_email",
392
+ "x_error_halt_exec",
393
+ "x_error_halt_exec_clear",
394
+ "x_write_warnings",
395
+ "x_gui_level",
396
+ "x_execute",
397
+ # regex helpers
398
+ "ins_rxs",
399
+ "ins_quoted_rx",
400
+ "ins_schema_rxs",
401
+ "ins_table_rxs",
402
+ "ins_table_list_rxs",
403
+ "ins_fn_rxs",
404
+ # MetaCommandList
405
+ "MetaCommandList",
406
+ # format constants
407
+ "DELIMITED_FORMATS",
408
+ "TEXT_FORMATS",
409
+ "JSON_VARIANT_FORMATS",
410
+ "QUERY_EXPORT_FORMATS",
411
+ "TABLE_EXPORT_FORMATS",
412
+ "SERVE_FORMATS",
413
+ "METADATA_FORMATS",
414
+ "ALL_EXPORT_FORMATS",
415
+ "DATABASE_TYPES",
416
+ # dispatch
417
+ "build_dispatch_table",
418
+ "DISPATCH_TABLE",
419
+ ]
420
+
215
421
  # ---------------------------------------------------------------------------
216
422
  # Export format constants — single source of truth.
217
423
  # Used in dispatch table regex patterns and by io_export.py for validation.
@@ -169,6 +169,7 @@ from execsql.metacommands.system import (
169
169
  x_cancel_halt,
170
170
  x_cancel_halt_email,
171
171
  x_cancel_halt_email_clear,
172
+ x_cancel_halt_exec,
172
173
  x_cancel_halt_exec_clear,
173
174
  x_cancel_halt_write,
174
175
  x_cancel_halt_write_clear,
@@ -185,6 +186,7 @@ from execsql.metacommands.system import (
185
186
  x_email,
186
187
  x_error_halt_email,
187
188
  x_error_halt_email_clear,
189
+ x_error_halt_exec,
188
190
  x_error_halt_exec_clear,
189
191
  x_error_halt_write,
190
192
  x_error_halt_write_clear,
@@ -219,6 +221,8 @@ from execsql.metacommands import (
219
221
  TABLE_EXPORT_FORMATS,
220
222
  )
221
223
 
224
+ __all__ = ["build_dispatch_table"]
225
+
222
226
 
223
227
  def build_dispatch_table() -> MetaCommandList:
224
228
  """Construct and return the complete metacommand dispatch table."""
@@ -1030,6 +1034,10 @@ def build_dispatch_table() -> MetaCommandList:
1030
1034
  x_error_halt_email,
1031
1035
  )
1032
1036
  mcl.add(r"^\s*ON\s+ERROR_HALT\s+EXEC\s+CLEAR\s*$", x_error_halt_exec_clear)
1037
+ mcl.add(
1038
+ r'^\s*ON\s+ERROR_HALT\s+EXEC(?:UTE)?\s+SCRIPT(?:\s+(?P<exists>IF\s+EXISTS))?\s+(?P<script_id>\w+)(?:(?:\s+WITH)?(?:\s+ARG(?:UMENT)?S?)?\s*\(\s*(?P<argexp>#?\w+\s*=\s*(?:(?:[^"\'\[][^,\)]*)|(?:"[^"]*")|(?:\'[^\']*\')|(?:\[[^\]]*\]))(?:\s*,\s*#?\w+\s*=\s*(?:(?:[^"\'\[][^,\)]*)|(?:"[^"]*")|(?:\'[^\']*\')|(?:\[[^\]]*\])))*)\s*\))?(?:\s+(?P<looptype>WHILE|UNTIL)\s*\(\s*(?P<loopcond>.+)\s*\))?\s*$',
1039
+ x_error_halt_exec,
1040
+ )
1033
1041
  mcl.add(
1034
1042
  r"^\s*ON\s+CANCEL_HALT\s+WRITE\s+CLEAR\s*$",
1035
1043
  x_cancel_halt_write_clear,
@@ -1077,6 +1085,10 @@ def build_dispatch_table() -> MetaCommandList:
1077
1085
  x_cancel_halt_email,
1078
1086
  )
1079
1087
  mcl.add(r"^\s*ON\s+CANCEL_HALT\s+EXEC\s+CLEAR\s*$", x_cancel_halt_exec_clear)
1088
+ mcl.add(
1089
+ r'^\s*ON\s+CANCEL_HALT\s+EXEC(?:UTE)?\s+SCRIPT(?:\s+(?P<exists>IF\s+EXISTS))?\s+(?P<script_id>\w+)(?:(?:\s+WITH)?(?:\s+ARG(?:UMENT)?S?)?\s*\(\s*(?P<argexp>#?\w+\s*=\s*(?:(?:[^"\'\[][^,\)]*)|(?:"[^"]*")|(?:\'[^\']*\')|(?:\[[^\]]*\]))(?:\s*,\s*#?\w+\s*=\s*(?:(?:[^"\'\[][^,\)]*)|(?:"[^"]*")|(?:\'[^\']*\')|(?:\[[^\]]*\])))*)\s*\))?(?:\s+(?P<looptype>WHILE|UNTIL)\s*\(\s*(?P<loopcond>.+)\s*\))?\s*$',
1090
+ x_cancel_halt_exec,
1091
+ )
1080
1092
 
1081
1093
  # ------------------------------------------------------------------
1082
1094
  # SUB_TEMPFILE
@@ -1680,16 +1692,54 @@ def build_dispatch_table() -> MetaCommandList:
1680
1692
  )
1681
1693
  mcl.add(
1682
1694
  (
1695
+ # SERVER unquoted, DB unquoted, PASSWORD unquoted
1683
1696
  r"^CONNECT\s+TO\s+SQLSERVER\s*\(\s*SERVER\s*=\s*(?P<server>[A-Z0-9][A-Z0-9_\/\\\-\.]*)\s*,\s*"
1684
1697
  r"DB\s*=\s*(?P<db_name>[A-Z][A-Z0-9_\-]*)(?:\s*,\s*USER\s*=\s*(?P<user>[A-Z][A-Z0-9_~`!@#$%^&\*\+=\/\?\.-]*)"
1685
1698
  r"\s*,\s*NEED_PWD\s*=\s*(?P<need_pwd>TRUE|FALSE))?(?:\s*,\s*PORT\s*=\s*(?P<port>\d+))?"
1686
1699
  r"(?:\s*,\s+PASSWORD\s*=\s*(?P<password>[^\s\)]+))?"
1687
1700
  r"(?:\s*,\s*ENCODING\s*=\s*(?P<encoding>[A-Z][A-Z0-9_-]+))?\s*\)\s+AS\s+(?P<db_alias>[A-Z][A-Z0-9_]*)\s*$",
1701
+ # SERVER quoted, DB quoted, PASSWORD unquoted
1688
1702
  r'^CONNECT\s+TO\s+SQLSERVER\s*\(\s*SERVER\s*=\s*"(?P<server>[A-Z0-9][A-Z0-9_\/\\\s\-\.]*)"\s*,\s*'
1689
1703
  r'DB\s*=\s*"(?P<db_name>[A-Z][A-Z0-9_\-\s]*)"(?:\s*,\s*USER\s*=\s*(?P<user>[A-Z][A-Z0-9_~`!@#$%^&\*\+=\/\?\.-]*)'
1690
1704
  r"\s*,\s*NEED_PWD\s*=\s*(?P<need_pwd>TRUE|FALSE))?(?:\s*,\s*PORT\s*=\s*(?P<port>\d+))?"
1691
1705
  r"(?:\s*,\s+PASSWORD\s*=\s*(?P<password>[^\s\)]+))?"
1692
1706
  r"(?:\s*,\s*ENCODING\s*=\s*(?P<encoding>[A-Z][A-Z0-9_-]+))?\s*\)\s+AS\s+(?P<db_alias>[A-Z][A-Z0-9_]*)\s*$",
1707
+ # SERVER quoted, DB unquoted, PASSWORD unquoted
1708
+ r'^CONNECT\s+TO\s+SQLSERVER\s*\(\s*SERVER\s*=\s*"(?P<server>[A-Z0-9][A-Z0-9_\/\\\s\-\.]*)"\s*,\s*'
1709
+ r"DB\s*=\s*(?P<db_name>[A-Z][A-Z0-9_\-]*)(?:\s*,\s*USER\s*=\s*(?P<user>[A-Z][A-Z0-9_~`!@#$%^&\*\+=\/\?\.-]*)"
1710
+ r"\s*,\s*NEED_PWD\s*=\s*(?P<need_pwd>TRUE|FALSE))?(?:\s*,\s*PORT\s*=\s*(?P<port>\d+))?"
1711
+ r"(?:\s*,\s+PASSWORD\s*=\s*(?P<password>[^\s\)]+))?"
1712
+ r"(?:\s*,\s*ENCODING\s*=\s*(?P<encoding>[A-Z][A-Z0-9_-]+))?\s*\)\s+AS\s+(?P<db_alias>[A-Z][A-Z0-9_]*)\s*$",
1713
+ # SERVER unquoted, DB quoted, PASSWORD unquoted
1714
+ r"^CONNECT\s+TO\s+SQLSERVER\s*\(\s*SERVER\s*=\s*(?P<server>[A-Z0-9][A-Z0-9_\/\\\-\.]*)\s*,\s*"
1715
+ r'DB\s*=\s*"(?P<db_name>[A-Z][A-Z0-9_\- ]*)"(?:\s*,\s*USER\s*=\s*(?P<user>[A-Z][A-Z0-9_~`!@#$%^&\*\+=\/\?\.-]*)'
1716
+ r"\s*,\s*NEED_PWD\s*=\s*(?P<need_pwd>TRUE|FALSE))?(?:\s*,\s*PORT\s*=\s*(?P<port>\d+))?"
1717
+ r"(?:\s*,\s+PASSWORD\s*=\s*(?P<password>[^\s\)]+))?"
1718
+ r"(?:\s*,\s*ENCODING\s*=\s*(?P<encoding>[A-Z][A-Z0-9_-]+))?\s*\)\s+AS\s+(?P<db_alias>[A-Z][A-Z0-9_]*)\s*$",
1719
+ # SERVER unquoted, DB unquoted, PASSWORD quoted
1720
+ r"^CONNECT\s+TO\s+SQLSERVER\s*\(\s*SERVER\s*=\s*(?P<server>[A-Z0-9][A-Z0-9_\/\\\-\.]*)\s*,\s*"
1721
+ r"DB\s*=\s*(?P<db_name>[A-Z][A-Z0-9_\-]*)(?:\s*,\s*USER\s*=\s*(?P<user>[A-Z][A-Z0-9_~`!@#$%^&\*\+=\/\?\.-]*)"
1722
+ r"\s*,\s*NEED_PWD\s*=\s*(?P<need_pwd>TRUE|FALSE))?(?:\s*,\s*PORT\s*=\s*(?P<port>\d+))?"
1723
+ r'(?:\s*,\s+PASSWORD\s*=\s*(?P<password>"[^\s\)]+"))?'
1724
+ r"(?:\s*,\s*ENCODING\s*=\s*(?P<encoding>[A-Z][A-Z0-9_-]+))?\s*\)\s+AS\s+(?P<db_alias>[A-Z][A-Z0-9_]*)\s*$",
1725
+ # SERVER quoted, DB quoted, PASSWORD quoted
1726
+ r'^CONNECT\s+TO\s+SQLSERVER\s*\(\s*SERVER\s*=\s*"(?P<server>[A-Z0-9][A-Z0-9_\/\\\s\-\.]*)"\s*,\s*'
1727
+ r'DB\s*=\s*"(?P<db_name>[A-Z][A-Z0-9_\-\s]*)"(?:\s*,\s*USER\s*=\s*(?P<user>[A-Z][A-Z0-9_~`!@#$%^&\*\+=\/\?\.-]*)'
1728
+ r"\s*,\s*NEED_PWD\s*=\s*(?P<need_pwd>TRUE|FALSE))?(?:\s*,\s*PORT\s*=\s*(?P<port>\d+))?"
1729
+ r'(?:\s*,\s+PASSWORD\s*=\s*(?P<password>"[^\s\)]+"))?'
1730
+ r"(?:\s*,\s*ENCODING\s*=\s*(?P<encoding>[A-Z][A-Z0-9_-]+))?\s*\)\s+AS\s+(?P<db_alias>[A-Z][A-Z0-9_]*)\s*$",
1731
+ # SERVER quoted, DB unquoted, PASSWORD quoted
1732
+ r'^CONNECT\s+TO\s+SQLSERVER\s*\(\s*SERVER\s*=\s*"(?P<server>[A-Z0-9][A-Z0-9_\/\\\s\-\.]*)"\s*,\s*'
1733
+ r"DB\s*=\s*(?P<db_name>[A-Z][A-Z0-9_\-]*)(?:\s*,\s*USER\s*=\s*(?P<user>[A-Z][A-Z0-9_~`!@#$%^&\*\+=\/\?\.-]*)"
1734
+ r"\s*,\s*NEED_PWD\s*=\s*(?P<need_pwd>TRUE|FALSE))?(?:\s*,\s*PORT\s*=\s*(?P<port>\d+))?"
1735
+ r'(?:\s*,\s+PASSWORD\s*=\s*(?P<password>"[^\s\)]+"))?'
1736
+ r"(?:\s*,\s*ENCODING\s*=\s*(?P<encoding>[A-Z][A-Z0-9_-]+))?\s*\)\s+AS\s+(?P<db_alias>[A-Z][A-Z0-9_]*)\s*$",
1737
+ # SERVER unquoted, DB quoted, PASSWORD quoted
1738
+ r"^CONNECT\s+TO\s+SQLSERVER\s*\(\s*SERVER\s*=\s*(?P<server>[A-Z0-9][A-Z0-9_\/\\\-\.]*)\s*,\s*"
1739
+ r'DB\s*=\s*"(?P<db_name>[A-Z][A-Z0-9_\- ]*)"(?:\s*,\s*USER\s*=\s*(?P<user>[A-Z][A-Z0-9_~`!@#$%^&\*\+=\/\?\.-]*)'
1740
+ r"\s*,\s*NEED_PWD\s*=\s*(?P<need_pwd>TRUE|FALSE))?(?:\s*,\s*PORT\s*=\s*(?P<port>\d+))?"
1741
+ r'(?:\s*,\s+PASSWORD\s*=\s*(?P<password>"[^\s\)]+"))?'
1742
+ r"(?:\s*,\s*ENCODING\s*=\s*(?P<encoding>[A-Z][A-Z0-9_-]+))?\s*\)\s+AS\s+(?P<db_alias>[A-Z][A-Z0-9_]*)\s*$",
1693
1743
  ),
1694
1744
  x_connect_ssvr,
1695
1745
  )
@@ -1722,6 +1772,12 @@ def build_dispatch_table() -> MetaCommandList:
1722
1772
  # ------------------------------------------------------------------
1723
1773
  # APPEND/EXTEND SCRIPT
1724
1774
  # ------------------------------------------------------------------
1775
+ mcl.add(
1776
+ r"\s*EXTEND\s+SCRIPT\s+(?P<script2>\w+)\s+WITH\s+SCRIPT\s+(?P<script1>\w+)\s*$",
1777
+ x_extendscript,
1778
+ description="EXTEND SCRIPT",
1779
+ category="action",
1780
+ )
1725
1781
  mcl.add(
1726
1782
  r"\s*APPEND\s+SCRIPT\s+(?P<script1>\w+)\s+TO\s+(?P<script2>\w+)\s*$",
1727
1783
  x_extendscript,
@@ -1897,13 +1953,48 @@ def build_dispatch_table() -> MetaCommandList:
1897
1953
  # ------------------------------------------------------------------
1898
1954
  mcl.add(
1899
1955
  ins_table_rxs(
1900
- r'^\s*PROMPT\s+ASK\s+"(?P<question>[^"]+)"\s+SUB\s+(?P<match>~?\w+)(?:\s+DISPLAY\s+',
1901
- r')?(?:\s+HELP\s+"(?P<help>[^"]+)")?\s*$',
1956
+ r"^\s*PROMPT\s+ASK\s+'(?P<question>[^']+)'\s+SUB\s+(?P<match>~?\w+)(?:\s+DISPLAY\s+",
1957
+ r")?(?:\s+HELP\s+(?P<help>[^\s]+))?\s*$",
1902
1958
  ),
1903
1959
  x_prompt_ask,
1904
1960
  description="PROMPT ASK",
1905
1961
  category="prompt",
1906
1962
  )
1963
+ mcl.add(
1964
+ ins_table_rxs(
1965
+ r"^\s*PROMPT\s+ASK\s+'(?P<question>[^']+)'\s+SUB\s+(?P<match>~?\w+)(?:\s+DISPLAY\s+",
1966
+ r')?(?:\s+HELP\s+"(?P<help>[^"]+)")?\s*$',
1967
+ ),
1968
+ x_prompt_ask,
1969
+ )
1970
+ mcl.add(
1971
+ ins_table_rxs(
1972
+ r"^\s*PROMPT\s+ASK\s+\[(?P<question>[^\]]+)\]\s+SUB\s+(?P<match>~?\w+)(?:\s+DISPLAY\s+",
1973
+ r")?(?:\s+HELP\s+(?P<help>[^\s]+))?\s*$",
1974
+ ),
1975
+ x_prompt_ask,
1976
+ )
1977
+ mcl.add(
1978
+ ins_table_rxs(
1979
+ r"^\s*PROMPT\s+ASK\s+\[(?P<question>[^\]]+)\]\s+SUB\s+(?P<match>~?\w+)(?:\s+DISPLAY\s+",
1980
+ r')?(?:\s+HELP\s+"(?P<help>[^"]+)")?\s*$',
1981
+ ),
1982
+ x_prompt_ask,
1983
+ )
1984
+ mcl.add(
1985
+ ins_table_rxs(
1986
+ r'^\s*PROMPT\s+ASK\s+"(?P<question>[^"]+)"\s+SUB\s+(?P<match>~?\w+)(?:\s+DISPLAY\s+',
1987
+ r")?(?:\s+HELP\s+(?P<help>[^\s]+))?\s*$",
1988
+ ),
1989
+ x_prompt_ask,
1990
+ )
1991
+ mcl.add(
1992
+ ins_table_rxs(
1993
+ r'^\s*PROMPT\s+ASK\s+"(?P<question>[^"]+)"\s+SUB\s+(?P<match>~?\w+)(?:\s+DISPLAY\s+',
1994
+ r')?(?:\s+HELP\s+"(?P<help>[^"]+)")?\s*$',
1995
+ ),
1996
+ x_prompt_ask,
1997
+ )
1907
1998
 
1908
1999
  # ------------------------------------------------------------------
1909
2000
  # PROMPT DISPLAY (table viewer)
@@ -68,3 +68,44 @@ from execsql.metacommands.io_fileops import ( # noqa: F401
68
68
  x_zip,
69
69
  x_zip_buffer_mb,
70
70
  )
71
+
72
+ __all__ = [
73
+ "_apply_output_dir",
74
+ "x_cd",
75
+ "x_copy",
76
+ "x_copy_query",
77
+ "x_export",
78
+ "x_export_metadata",
79
+ "x_export_metadata_table",
80
+ "x_export_ods_multiple",
81
+ "x_export_query",
82
+ "x_export_query_with_template",
83
+ "x_export_row_buffer",
84
+ "x_export_with_template",
85
+ "x_hdf5_text_len",
86
+ "x_import",
87
+ "x_import_feather",
88
+ "x_import_file",
89
+ "x_import_ods",
90
+ "x_import_ods_pattern",
91
+ "x_import_parquet",
92
+ "x_import_row_buffer",
93
+ "x_import_xls",
94
+ "x_import_xls_pattern",
95
+ "x_include",
96
+ "x_make_export_dirs",
97
+ "x_rm_file",
98
+ "x_scan_lines",
99
+ "x_serve",
100
+ "x_show_progress",
101
+ "x_write",
102
+ "x_write_create_table",
103
+ "x_write_create_table_alias",
104
+ "x_write_create_table_ods",
105
+ "x_write_create_table_xls",
106
+ "x_write_prefix",
107
+ "x_write_suffix",
108
+ "x_writescript",
109
+ "x_zip",
110
+ "x_zip_buffer_mb",
111
+ ]
execsql/models.py CHANGED
@@ -49,13 +49,18 @@ __all__ = [
49
49
 
50
50
 
51
51
  class Column:
52
+ """Compile data-type match statistics for a single column of imported data."""
53
+
52
54
  # Column objects are used to compile information about the data types that a set of data
53
55
  # values may match. A Column object is intended to be used to identify the data type of a column
54
56
  # when scanning a data stream (such as a CSV file) to create a new data table.
55
57
 
56
58
  class Accum:
59
+ """Accumulate match counts and length statistics for a single data type."""
60
+
57
61
  # Accumulates the count of matches for each data type, plus the maximum length if appropriate.
58
62
  def __init__(self, data_type_obj: DataType) -> None:
63
+ """Initialise the accumulator for the given data type."""
59
64
  self.dt = data_type_obj
60
65
  self.failed = False
61
66
  self.count = 0
@@ -72,6 +77,7 @@ class Column:
72
77
  )
73
78
 
74
79
  def check(self, datavalue: Any) -> None:
80
+ """Test whether a non-null value matches this data type and update statistics."""
75
81
  # datavalue must be non-null
76
82
  if not self.failed:
77
83
  is_match = self.dt.matches(datavalue)
@@ -105,6 +111,7 @@ class Column:
105
111
  self.failed = True
106
112
 
107
113
  def __init__(self, colname: str) -> None:
114
+ """Create a column characteriser for the named column."""
108
115
  from execsql.exceptions import ErrInfo
109
116
  import execsql.state as _state
110
117
 
@@ -150,6 +157,7 @@ class Column:
150
157
  return f"Column({self.name!r})"
151
158
 
152
159
  def eval_types(self, column_value: Any) -> None:
160
+ """Evaluate which data types the value matches and update counters."""
153
161
  # Evaluate which data type(s) the value matches, and increment the appropriate counter(s).
154
162
  import execsql.state as _state
155
163
 
@@ -170,6 +178,7 @@ class Column:
170
178
  dt.check(column_value)
171
179
 
172
180
  def column_type(self) -> tuple:
181
+ """Return the inferred type of this column as a 6-tuple."""
173
182
  # Return the type of this column as a tuple of:
174
183
  # column name, data type class, max length or None, bool for null values,
175
184
  # precision or None, scale or None.
@@ -212,7 +221,10 @@ class Column:
212
221
 
213
222
 
214
223
  class DataTable:
224
+ """Scan a row source and infer column types for CREATE TABLE generation."""
225
+
215
226
  def __init__(self, column_names: list[str], rowsource: Any) -> None:
227
+ """Scan all rows from the source and infer a column type for each column."""
216
228
  import execsql.state as _state
217
229
 
218
230
  self.inputrows = 0 # Total number of rows in the row source.
@@ -264,6 +276,7 @@ class DataTable:
264
276
  return f"DataTable({[col.name for col in self.cols]!r}, rowsource)"
265
277
 
266
278
  def column_declarations(self, database_type: DbType) -> list[str]:
279
+ """Return a list of SQL column-declaration strings for the given DBMS."""
267
280
  # Returns a list of column specifications.
268
281
  spec = []
269
282
  for col in self.cols:
@@ -277,6 +290,7 @@ class DataTable:
277
290
  tablename: str,
278
291
  pretty: bool = False,
279
292
  ) -> str:
293
+ """Generate a CREATE TABLE statement for the given DBMS and table name."""
280
294
  tb = (
281
295
  f"{database_type.quoted(schemaname)}.{database_type.quoted(tablename)}"
282
296
  if schemaname
@@ -292,7 +306,10 @@ class DataTable:
292
306
 
293
307
 
294
308
  class JsonDatatype:
309
+ """Namespace mapping Python DataType subclasses to JSON Schema type strings."""
310
+
295
311
  def __init__(self) -> None:
312
+ """Create an empty JsonDatatype namespace instance."""
296
313
  pass
297
314
 
298
315