execsql2 2.16.0__py3-none-any.whl → 2.16.2__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 (26) hide show
  1. execsql/cli/run.py +333 -173
  2. execsql/config.py +1 -1
  3. execsql/script/executor.py +145 -47
  4. execsql/script/parser.py +9 -1
  5. execsql/state.py +72 -51
  6. {execsql2-2.16.0.dist-info → execsql2-2.16.2.dist-info}/METADATA +38 -29
  7. {execsql2-2.16.0.dist-info → execsql2-2.16.2.dist-info}/RECORD +26 -26
  8. {execsql2-2.16.0.data → execsql2-2.16.2.data}/data/execsql2_extras/README.md +0 -0
  9. {execsql2-2.16.0.data → execsql2-2.16.2.data}/data/execsql2_extras/config_settings.sqlite +0 -0
  10. {execsql2-2.16.0.data → execsql2-2.16.2.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
  11. {execsql2-2.16.0.data → execsql2-2.16.2.data}/data/execsql2_extras/execsql.conf +0 -0
  12. {execsql2-2.16.0.data → execsql2-2.16.2.data}/data/execsql2_extras/make_config_db.sql +0 -0
  13. {execsql2-2.16.0.data → execsql2-2.16.2.data}/data/execsql2_extras/md_compare.sql +0 -0
  14. {execsql2-2.16.0.data → execsql2-2.16.2.data}/data/execsql2_extras/md_glossary.sql +0 -0
  15. {execsql2-2.16.0.data → execsql2-2.16.2.data}/data/execsql2_extras/md_upsert.sql +0 -0
  16. {execsql2-2.16.0.data → execsql2-2.16.2.data}/data/execsql2_extras/pg_compare.sql +0 -0
  17. {execsql2-2.16.0.data → execsql2-2.16.2.data}/data/execsql2_extras/pg_glossary.sql +0 -0
  18. {execsql2-2.16.0.data → execsql2-2.16.2.data}/data/execsql2_extras/pg_upsert.sql +0 -0
  19. {execsql2-2.16.0.data → execsql2-2.16.2.data}/data/execsql2_extras/script_template.sql +0 -0
  20. {execsql2-2.16.0.data → execsql2-2.16.2.data}/data/execsql2_extras/ss_compare.sql +0 -0
  21. {execsql2-2.16.0.data → execsql2-2.16.2.data}/data/execsql2_extras/ss_glossary.sql +0 -0
  22. {execsql2-2.16.0.data → execsql2-2.16.2.data}/data/execsql2_extras/ss_upsert.sql +0 -0
  23. {execsql2-2.16.0.dist-info → execsql2-2.16.2.dist-info}/WHEEL +0 -0
  24. {execsql2-2.16.0.dist-info → execsql2-2.16.2.dist-info}/entry_points.txt +0 -0
  25. {execsql2-2.16.0.dist-info → execsql2-2.16.2.dist-info}/licenses/LICENSE.txt +0 -0
  26. {execsql2-2.16.0.dist-info → execsql2-2.16.2.dist-info}/licenses/NOTICE +0 -0
execsql/cli/run.py CHANGED
@@ -192,138 +192,151 @@ def _ping_db(db: Any) -> None:
192
192
 
193
193
 
194
194
  # ---------------------------------------------------------------------------
195
- # Core execution (split from argument parsing for testability)
195
+ # ---------------------------------------------------------------------------
196
+ # Extracted phases — pure computation, no global state coupling
196
197
  # ---------------------------------------------------------------------------
197
198
 
198
199
 
199
- def _run(
200
- positional: list,
201
- sub_vars: list[str] | None,
202
- boolean_int: str | None,
203
- make_dirs: str | None,
204
- database_encoding: str | None,
205
- script_encoding: str | None,
206
- output_encoding: str | None,
207
- import_encoding: str | None,
208
- user_logfile: bool,
209
- new_db: bool,
210
- port: int | None,
211
- scanlines: int | None,
212
- db_type: str | None,
213
- user: str | None,
214
- use_gui: str | None,
215
- gui_framework: str | None = None,
216
- no_passwd: bool = False,
217
- import_buffer: int | None = None,
218
- script_name: str | None = None,
219
- command: str | None = None,
220
- dry_run: bool = False,
221
- dsn: str | None = None,
222
- output_dir: str | None = None,
223
- progress: bool = False,
224
- profile: bool = False,
225
- profile_limit: int = 20,
226
- ping: bool = False,
227
- lint: bool = False,
228
- debug: bool = False,
229
- config_file: str | None = None,
230
- ) -> None:
231
- """Initialise state, connect to the database, load the script, and run it.
232
-
233
- Separated from argument parsing so it can be called directly in tests
234
- without going through the Typer CLI layer. All parameters mirror the
235
- corresponding CLI options; see [Syntax & Options](../syntax.md) for
236
- descriptions.
237
-
238
- When *ping* is ``True``, the function connects to the database, prints
239
- connection details (DBMS name, server version, and location), and calls
240
- :func:`_ping_db` which raises ``SystemExit(0)``. No script is loaded or
241
- executed. *script_name* and *command* may both be ``None`` in ping mode.
200
+ def _seed_early_subvars() -> SubVarSet:
201
+ """Create and populate the initial substitution variable pool.
242
202
 
243
- When *lint* is ``True``, the script is parsed and statically analysed for
244
- structural issues (unmatched IF/ENDIF/LOOP/BATCH blocks, potentially
245
- undefined variables, missing INCLUDE files, empty scripts) without
246
- connecting to a database or executing anything. Exits with code 0 if no
247
- errors were found, or code 1 if errors were found.
203
+ Seeds environment variables (filtering secrets), timestamps, system info,
204
+ and placeholder variables. Called once at the start of :func:`_run`.
248
205
  """
249
- import execsql.state as _state
206
+ subvars = SubVarSet()
250
207
 
251
- # ------------------------------------------------------------------
252
- # Early setup: substitution variables seeded before arg parsing
253
- # ------------------------------------------------------------------
254
- _state.subvars = SubVarSet()
255
-
256
- # Environment variables are exposed as &-prefixed substitution variables.
257
- # Variables whose names contain common secret-indicating substrings are
258
- # excluded to reduce accidental credential leakage into scripts and logs.
259
208
  _SENSITIVE_SUBSTRINGS = ("SECRET", "TOKEN", "PASSWORD", "PASSWD", "PRIVATE_KEY", "CREDENTIAL")
260
209
  for k in os.environ:
261
210
  if any(s in k.upper() for s in _SENSITIVE_SUBSTRINGS):
262
211
  continue
263
212
  try:
264
- _state.subvars.add_substitution("&" + k, os.environ[k])
213
+ subvars.add_substitution("&" + k, os.environ[k])
265
214
  except Exception:
266
215
  pass # Skip env vars with names that can't be substitution keys.
267
- _state.subvars.add_substitution("$LAST_ROWCOUNT", None)
216
+ subvars.add_substitution("$LAST_ROWCOUNT", None)
268
217
 
269
218
  dt_now = datetime.datetime.now()
270
219
  dt_now_utc = datetime.datetime.now(tz=datetime.timezone.utc)
271
220
 
272
- _state.subvars.add_substitution("$SCRIPT_START_TIME", dt_now.strftime("%Y-%m-%d %H:%M"))
273
- _state.subvars.add_substitution("$SCRIPT_START_TIME_UTC", dt_now_utc.strftime("%Y-%m-%d %H:%M"))
274
- _state.subvars.add_substitution("$DATE_TAG", dt_now.strftime("%Y%m%d"))
275
- _state.subvars.add_substitution("$DATETIME_TAG", dt_now.strftime("%Y%m%d_%H%M"))
276
- _state.subvars.add_substitution("$DATETIME_UTC_TAG", dt_now_utc.strftime("%Y%m%d_%H%M"))
277
- _state.subvars.add_substitution("$LAST_SQL", "")
278
- _state.subvars.add_substitution("$LAST_ERROR", "")
279
- _state.subvars.add_substitution("$ERROR_MESSAGE", "")
280
- _state.subvars.add_substitution("$USER", getpass.getuser())
281
- _state.subvars.add_substitution("$STARTING_PATH", os.getcwd() + os.sep)
282
- _state.subvars.add_substitution("$PATHSEP", os.sep)
221
+ subvars.add_substitution("$SCRIPT_START_TIME", dt_now.strftime("%Y-%m-%d %H:%M"))
222
+ subvars.add_substitution("$SCRIPT_START_TIME_UTC", dt_now_utc.strftime("%Y-%m-%d %H:%M"))
223
+ subvars.add_substitution("$DATE_TAG", dt_now.strftime("%Y%m%d"))
224
+ subvars.add_substitution("$DATETIME_TAG", dt_now.strftime("%Y%m%d_%H%M"))
225
+ subvars.add_substitution("$DATETIME_UTC_TAG", dt_now_utc.strftime("%Y%m%d_%H%M"))
226
+ subvars.add_substitution("$LAST_SQL", "")
227
+ subvars.add_substitution("$LAST_ERROR", "")
228
+ subvars.add_substitution("$ERROR_MESSAGE", "")
229
+ subvars.add_substitution("$USER", getpass.getuser())
230
+ subvars.add_substitution("$STARTING_PATH", os.getcwd() + os.sep)
231
+ subvars.add_substitution("$PATHSEP", os.sep)
283
232
  osys = sys.platform
284
233
  if osys.startswith("linux"):
285
234
  osys = "linux"
286
235
  elif osys.startswith("win"):
287
236
  osys = "windows"
288
- _state.subvars.add_substitution("$OS", osys)
289
- _state.subvars.add_substitution("$HOSTNAME", platform.node())
290
- _state.subvars.add_substitution("$PYTHON_EXECUTABLE", sys.executable)
237
+ subvars.add_substitution("$OS", osys)
238
+ subvars.add_substitution("$HOSTNAME", platform.node())
239
+ subvars.add_substitution("$PYTHON_EXECUTABLE", sys.executable)
240
+ return subvars
291
241
 
292
- # ------------------------------------------------------------------
293
- # Read configuration file
294
- # ------------------------------------------------------------------
242
+
243
+ def _load_config(
244
+ script_name: str | None,
245
+ subvars: SubVarSet,
246
+ config_file: str | None,
247
+ ) -> ConfigData:
248
+ """Load and merge configuration files for the given script.
249
+
250
+ Returns a fully populated :class:`ConfigData` instance.
251
+ """
295
252
  script_path = str(Path(script_name).resolve().parent) if script_name else os.getcwd()
296
- _state.conf = ConfigData(script_path, _state.subvars, config_file=config_file)
297
- conf = _state.conf
253
+ return ConfigData(script_path, subvars, config_file=config_file)
298
254
 
299
- # ------------------------------------------------------------------
300
- # Connection string (--dsn / --connection-string): overrides -t/-u/-p
301
- # and positional server/db args when provided.
302
- # ------------------------------------------------------------------
303
- if dsn:
304
- try:
305
- parsed_dsn = _parse_connection_string(dsn)
306
- except ConfigError as exc:
307
- _err_console.print(f"[bold red]Error:[/bold red] {exc}")
308
- raise SystemExit(1) from None
309
- db_type = db_type or parsed_dsn["db_type"]
310
- conf.db_type = db_type
311
- # DSN values override conf-file values — the CLI flag is explicit.
312
- if parsed_dsn["server"]:
313
- conf.server = parsed_dsn["server"]
314
- if parsed_dsn["db"]:
315
- conf.db = parsed_dsn["db"]
316
- if parsed_dsn["db_file"]:
317
- conf.db_file = parsed_dsn["db_file"]
318
- if parsed_dsn["user"]:
319
- user = parsed_dsn["user"]
320
- if parsed_dsn["password"]:
321
- conf.db_password = parsed_dsn["password"]
322
- conf.passwd_prompt = False
323
- if parsed_dsn["port"]:
324
- port = parsed_dsn["port"]
325
-
326
- # Apply CLI options over config-file values
255
+
256
+ def _seed_script_subvars(subvars: SubVarSet, script_name: str | None) -> None:
257
+ """Add substitution variables that depend on the script path."""
258
+ from execsql.utils.errors import file_size_date
259
+
260
+ if script_name is not None:
261
+ subvars.add_substitution("$STARTING_SCRIPT", script_name)
262
+ subvars.add_substitution("$STARTING_SCRIPT_NAME", Path(script_name).name)
263
+ subvars.add_substitution("$STARTING_SCRIPT_REVTIME", file_size_date(script_name)[1])
264
+ else:
265
+ subvars.add_substitution("$STARTING_SCRIPT", "<inline>")
266
+ subvars.add_substitution("$STARTING_SCRIPT_NAME", "<inline>")
267
+ subvars.add_substitution("$STARTING_SCRIPT_REVTIME", "")
268
+
269
+
270
+ def _load_script(
271
+ command: str | None,
272
+ script_name: str | None,
273
+ encoding: str,
274
+ ) -> Any:
275
+ """Parse the SQL script (or inline command) into an AST.
276
+
277
+ Returns the AST tree, or ``None`` if no script is provided.
278
+ """
279
+ from execsql.script.parser import parse_script, parse_string
280
+
281
+ if command is not None:
282
+ return parse_string(
283
+ command.replace("\\n", "\n").replace("\\t", "\t"),
284
+ "<inline>",
285
+ )
286
+ return parse_script(script_name, encoding=encoding)
287
+
288
+
289
+ def _apply_dsn(dsn: str, conf: ConfigData, db_type: str | None) -> tuple[str | None, str | None, int | None]:
290
+ """Parse a DSN connection string and apply overrides to *conf*.
291
+
292
+ Returns ``(db_type, user_override, port_override)`` — values extracted
293
+ from the DSN that must also be applied to CLI-level variables in the
294
+ caller.
295
+ """
296
+ try:
297
+ parsed = _parse_connection_string(dsn)
298
+ except ConfigError as exc:
299
+ _err_console.print(f"[bold red]Error:[/bold red] {exc}")
300
+ raise SystemExit(1) from None
301
+ db_type = db_type or parsed["db_type"]
302
+ conf.db_type = db_type
303
+ if parsed["server"]:
304
+ conf.server = parsed["server"]
305
+ if parsed["db"]:
306
+ conf.db = parsed["db"]
307
+ if parsed["db_file"]:
308
+ conf.db_file = parsed["db_file"]
309
+ user = parsed["user"] # may be None
310
+ if parsed["password"]:
311
+ conf.db_password = parsed["password"]
312
+ conf.passwd_prompt = False
313
+ port = parsed["port"] # may be None
314
+ return db_type, user, port
315
+
316
+
317
+ def _apply_cli_options(
318
+ conf: ConfigData,
319
+ *,
320
+ user: str | None,
321
+ no_passwd: bool,
322
+ database_encoding: str | None,
323
+ script_encoding: str | None,
324
+ output_encoding: str | None,
325
+ import_encoding: str | None,
326
+ import_buffer: int | None,
327
+ make_dirs: str | None,
328
+ boolean_int: str | None,
329
+ scanlines: int | None,
330
+ use_gui: str | None,
331
+ gui_framework: str | None,
332
+ db_type: str | None,
333
+ user_logfile: bool,
334
+ port: int | None,
335
+ new_db: bool,
336
+ output_dir: str | None,
337
+ progress: bool,
338
+ ) -> None:
339
+ """Merge CLI flags into *conf*, overriding config-file values."""
327
340
  if user:
328
341
  conf.username = user
329
342
  if no_passwd:
@@ -363,7 +376,7 @@ def _run(
363
376
  if db_type:
364
377
  conf.db_type = db_type
365
378
  if conf.db_type is None:
366
- conf.db_type = "a"
379
+ conf.db_type = "l"
367
380
  if user_logfile:
368
381
  conf.user_logfile = True
369
382
  if port:
@@ -377,9 +390,15 @@ def _run(
377
390
  if progress:
378
391
  conf.show_progress = True
379
392
 
380
- # Positional arguments after the script name (or all positionals in inline/ping mode)
381
- # off=1: script file occupies positional[0]; connection args start at [1]
382
- # off=0: no script file; all positionals are connection args (inline -c or --ping)
393
+
394
+ def _route_positionals(
395
+ positional: list,
396
+ conf: ConfigData,
397
+ *,
398
+ command: str | None,
399
+ ping: bool,
400
+ ) -> None:
401
+ """Apply remaining positional CLI arguments to *conf* as server/db/db_file."""
383
402
  off = 0 if (command is not None or ping) else 1
384
403
  if len(positional) == off + 1:
385
404
  if conf.db_type in ("a", "l", "k"):
@@ -399,50 +418,32 @@ def _run(
399
418
 
400
419
  fatal_error("Incorrect number of command-line arguments.")
401
420
 
402
- # ------------------------------------------------------------------
403
- # Script substitution variables that depend on the script path
404
- # ------------------------------------------------------------------
405
- from execsql.utils.errors import file_size_date
406
-
407
- if script_name is not None:
408
- _state.subvars.add_substitution("$STARTING_SCRIPT", script_name)
409
- _state.subvars.add_substitution("$STARTING_SCRIPT_NAME", Path(script_name).name)
410
- _state.subvars.add_substitution("$STARTING_SCRIPT_REVTIME", file_size_date(script_name)[1])
411
- else:
412
- _state.subvars.add_substitution("$STARTING_SCRIPT", "<inline>")
413
- _state.subvars.add_substitution("$STARTING_SCRIPT_NAME", "<inline>")
414
- _state.subvars.add_substitution("$STARTING_SCRIPT_REVTIME", "")
415
-
416
- # ------------------------------------------------------------------
417
- # Initialise state objects
418
- # ------------------------------------------------------------------
419
- from execsql.metacommands import DISPATCH_TABLE
420
- from execsql.metacommands.conditions import CONDITIONAL_TABLE
421
-
422
- _state.initialize(conf, DISPATCH_TABLE, CONDITIONAL_TABLE)
423
421
 
424
- # Local-only objects that require CLI-specific args or class definitions
425
- _state.status = StatObj()
426
-
427
- from execsql.config import WriteHooks
428
-
429
- _state.output = WriteHooks()
430
-
431
- import execsql.utils.fileio as _fileio
432
-
433
- if _state.filewriter is None or not _state.filewriter.is_alive():
434
- _fileio.filewriter = _state.filewriter = FileWriter(
435
- _fileio.fw_input,
436
- _fileio.fw_output,
437
- file_encoding=conf.output_encoding,
438
- open_timeout=getattr(conf, "outfile_open_timeout", 10),
439
- )
440
- _state.filewriter.start()
441
- atexit.register(filewriter_end)
422
+ def _setup_logging(
423
+ conf: ConfigData,
424
+ subvars: SubVarSet,
425
+ script_name: str | None,
426
+ sub_vars: list[str] | None,
427
+ *,
428
+ boolean_int: str | None,
429
+ make_dirs: str | None,
430
+ database_encoding: str | None,
431
+ script_encoding: str | None,
432
+ output_encoding: str | None,
433
+ import_encoding: str | None,
434
+ user_logfile: bool,
435
+ new_db: bool,
436
+ port: int | None,
437
+ scanlines: int | None,
438
+ db_type: str | None,
439
+ user: str | None,
440
+ use_gui: str | None,
441
+ no_passwd: bool,
442
+ import_buffer: int | None,
443
+ ) -> Logger:
444
+ """Create the execution logger, log initial info, and seed ``$RUN_ID``."""
445
+ from execsql.utils.errors import file_size_date
442
446
 
443
- # ------------------------------------------------------------------
444
- # Logging
445
- # ------------------------------------------------------------------
446
447
  opts_dict = {
447
448
  k: v
448
449
  for k, v in {
@@ -465,48 +466,207 @@ def _run(
465
466
  }.items()
466
467
  if v
467
468
  }
468
- _state.exec_log = Logger(
469
+ logger = Logger(
469
470
  script_name or "<inline>",
470
471
  conf.db,
471
472
  conf.server,
472
473
  opts_dict,
473
474
  conf.user_logfile,
474
475
  )
475
- _state.exec_log.log_status_info(
476
+ logger.log_status_info(
476
477
  f"Python version {sys.version_info[0]}.{sys.version_info[1]}.{sys.version_info[2]} {sys.version_info[3]}",
477
478
  )
478
- _state.exec_log.log_status_info(f"execsql version {__version__}")
479
- _state.exec_log.log_status_info(f"System user: {getpass.getuser()}")
479
+ logger.log_status_info(f"execsql version {__version__}")
480
+ logger.log_status_info(f"System user: {getpass.getuser()}")
480
481
  for configfile in conf.files_read:
481
482
  sz, dt = file_size_date(configfile)
482
- _state.exec_log.log_status_info(
483
+ logger.log_status_info(
483
484
  f"Read configuration file {configfile} (size: {sz}, date: {dt}).",
484
485
  )
485
486
 
486
- _state.subvars.add_substitution("$RUN_ID", _state.exec_log.run_id)
487
+ subvars.add_substitution("$RUN_ID", logger.run_id)
487
488
 
488
489
  if sub_vars:
489
490
  for n, repl in enumerate(sub_vars):
490
491
  var = f"$ARG_{n + 1}"
491
- _state.subvars.add_substitution(var, repl)
492
- _state.exec_log.log_status_info(
492
+ subvars.add_substitution(var, repl)
493
+ logger.log_status_info(
493
494
  f"Command-line substitution variable assignment: {var} set to {{{repl}}}",
494
495
  )
495
496
 
497
+ return logger
498
+
499
+
500
+ # ---------------------------------------------------------------------------
501
+ # Core execution (split from argument parsing for testability)
502
+ # ---------------------------------------------------------------------------
503
+
504
+
505
+ def _run(
506
+ positional: list,
507
+ sub_vars: list[str] | None,
508
+ boolean_int: str | None,
509
+ make_dirs: str | None,
510
+ database_encoding: str | None,
511
+ script_encoding: str | None,
512
+ output_encoding: str | None,
513
+ import_encoding: str | None,
514
+ user_logfile: bool,
515
+ new_db: bool,
516
+ port: int | None,
517
+ scanlines: int | None,
518
+ db_type: str | None,
519
+ user: str | None,
520
+ use_gui: str | None,
521
+ gui_framework: str | None = None,
522
+ no_passwd: bool = False,
523
+ import_buffer: int | None = None,
524
+ script_name: str | None = None,
525
+ command: str | None = None,
526
+ dry_run: bool = False,
527
+ dsn: str | None = None,
528
+ output_dir: str | None = None,
529
+ progress: bool = False,
530
+ profile: bool = False,
531
+ profile_limit: int = 20,
532
+ ping: bool = False,
533
+ lint: bool = False,
534
+ debug: bool = False,
535
+ config_file: str | None = None,
536
+ ) -> None:
537
+ """Initialise state, connect to the database, load the script, and run it.
538
+
539
+ Separated from argument parsing so it can be called directly in tests
540
+ without going through the Typer CLI layer. All parameters mirror the
541
+ corresponding CLI options; see [Syntax & Options](../syntax.md) for
542
+ descriptions.
543
+
544
+ When *ping* is ``True``, the function connects to the database, prints
545
+ connection details (DBMS name, server version, and location), and calls
546
+ :func:`_ping_db` which raises ``SystemExit(0)``. No script is loaded or
547
+ executed. *script_name* and *command* may both be ``None`` in ping mode.
548
+
549
+ When *lint* is ``True``, the script is parsed and statically analysed for
550
+ structural issues (unmatched IF/ENDIF/LOOP/BATCH blocks, potentially
551
+ undefined variables, missing INCLUDE files, empty scripts) without
552
+ connecting to a database or executing anything. Exits with code 0 if no
553
+ errors were found, or code 1 if errors were found.
554
+ """
555
+ import execsql.state as _state
556
+
496
557
  # ------------------------------------------------------------------
497
- # Load the SQL script (skipped in --ping and --dry-run with no script)
558
+ # Early setup: substitution variables seeded before config loading
498
559
  # ------------------------------------------------------------------
499
- _ast_tree = None
500
- if not ping:
501
- from execsql.script.parser import parse_script, parse_string
502
-
503
- if command is not None:
504
- _ast_tree = parse_string(
505
- command.replace("\\n", "\n").replace("\\t", "\t"),
506
- "<inline>",
507
- )
508
- else:
509
- _ast_tree = parse_script(script_name, encoding=conf.script_encoding)
560
+ _state.subvars = _seed_early_subvars()
561
+
562
+ # ------------------------------------------------------------------
563
+ # Read configuration file
564
+ # ------------------------------------------------------------------
565
+ _state.conf = _load_config(script_name, _state.subvars, config_file)
566
+ conf = _state.conf
567
+
568
+ # ------------------------------------------------------------------
569
+ # Connection string (--dsn / --connection-string): overrides -t/-u/-p
570
+ # and positional server/db args when provided.
571
+ # ------------------------------------------------------------------
572
+ if dsn:
573
+ db_type, dsn_user, dsn_port = _apply_dsn(dsn, conf, db_type)
574
+ if dsn_user:
575
+ user = dsn_user
576
+ if dsn_port:
577
+ port = dsn_port
578
+
579
+ # ------------------------------------------------------------------
580
+ # Merge CLI options over config-file values
581
+ # ------------------------------------------------------------------
582
+ _apply_cli_options(
583
+ conf,
584
+ user=user,
585
+ no_passwd=no_passwd,
586
+ database_encoding=database_encoding,
587
+ script_encoding=script_encoding,
588
+ output_encoding=output_encoding,
589
+ import_encoding=import_encoding,
590
+ import_buffer=import_buffer,
591
+ make_dirs=make_dirs,
592
+ boolean_int=boolean_int,
593
+ scanlines=scanlines,
594
+ use_gui=use_gui,
595
+ gui_framework=gui_framework,
596
+ db_type=db_type,
597
+ user_logfile=user_logfile,
598
+ port=port,
599
+ new_db=new_db,
600
+ output_dir=output_dir,
601
+ progress=progress,
602
+ )
603
+
604
+ # ------------------------------------------------------------------
605
+ # Positional arguments → server/db/db_file
606
+ # ------------------------------------------------------------------
607
+ _route_positionals(positional, conf, command=command, ping=ping)
608
+
609
+ # ------------------------------------------------------------------
610
+ # Script substitution variables that depend on the script path
611
+ # ------------------------------------------------------------------
612
+ _seed_script_subvars(_state.subvars, script_name)
613
+
614
+ # ------------------------------------------------------------------
615
+ # Initialise state objects
616
+ # ------------------------------------------------------------------
617
+ from execsql.metacommands import DISPATCH_TABLE
618
+ from execsql.metacommands.conditions import CONDITIONAL_TABLE
619
+
620
+ _state.initialize(conf, DISPATCH_TABLE, CONDITIONAL_TABLE)
621
+
622
+ # Local-only objects that require CLI-specific args or class definitions
623
+ _state.status = StatObj()
624
+
625
+ from execsql.config import WriteHooks
626
+
627
+ _state.output = WriteHooks()
628
+
629
+ import execsql.utils.fileio as _fileio
630
+
631
+ if _state.filewriter is None or not _state.filewriter.is_alive():
632
+ _fileio.filewriter = _state.filewriter = FileWriter(
633
+ _fileio.fw_input,
634
+ _fileio.fw_output,
635
+ file_encoding=conf.output_encoding,
636
+ open_timeout=getattr(conf, "outfile_open_timeout", 10),
637
+ )
638
+ _state.filewriter.start()
639
+ atexit.register(filewriter_end)
640
+
641
+ # ------------------------------------------------------------------
642
+ # Logging
643
+ # ------------------------------------------------------------------
644
+ _state.exec_log = _setup_logging(
645
+ conf,
646
+ _state.subvars,
647
+ script_name,
648
+ sub_vars,
649
+ boolean_int=boolean_int,
650
+ make_dirs=make_dirs,
651
+ database_encoding=database_encoding,
652
+ script_encoding=script_encoding,
653
+ output_encoding=output_encoding,
654
+ import_encoding=import_encoding,
655
+ user_logfile=user_logfile,
656
+ new_db=new_db,
657
+ port=port,
658
+ scanlines=scanlines,
659
+ db_type=db_type,
660
+ user=user,
661
+ use_gui=use_gui,
662
+ no_passwd=no_passwd,
663
+ import_buffer=import_buffer,
664
+ )
665
+
666
+ # ------------------------------------------------------------------
667
+ # Load the SQL script (skipped in --ping mode)
668
+ # ------------------------------------------------------------------
669
+ _ast_tree = None if ping else _load_script(command, script_name, conf.script_encoding)
510
670
 
511
671
  # ------------------------------------------------------------------
512
672
  # Dry-run: print command list and exit without connecting to DB
execsql/config.py CHANGED
@@ -218,7 +218,7 @@ class ConfigData:
218
218
  precedence over system, user, script, and working-directory
219
219
  config files.
220
220
  """
221
- self.db_type = "a"
221
+ self.db_type = "l"
222
222
  self.server = None
223
223
  self.port = None
224
224
  self.db = None