mirror of
https://github.com/jhd3197/ServerKit.git
synced 2026-10-31 09:06:52 +09:00
Comments cited plan sections as "(§G3)" or "(§8 unification)", which point into the same git-ignored plans the plan-number citations did. References next to a real document (docs/REDESIGN_MAP.md §3) stay. Code and runtime strings are unchanged (AST and token comparison).
95 lines
3.9 KiB
Python
95 lines
3.9 KiB
Python
"""One SQL-exec dispatcher.
|
||
|
||
`engine × host` dispatch — mysql/postgres crossed with native/docker — was
|
||
written three times: `db_config_tuner_service`, `db_process_service`, and
|
||
`managed_db_user_service`. Only the last delegated fully to `DatabaseService`'s
|
||
four executors; the other two grew private copies that had already drifted (one
|
||
never learned about `ON_ERROR_STOP=1`, so psql reported success for statements
|
||
the server rejected).
|
||
|
||
This module is the dispatch, and nothing else. The four executors stay in
|
||
`DatabaseService` — this does not become a second place where SQL is run, only
|
||
the single place where "which executor" is decided.
|
||
|
||
Every executor returns the same shape, and so does this::
|
||
|
||
{'success': bool, 'output': str, 'error': str|None}
|
||
|
||
A target is a plain dict so callers can build one from a `ManagedDatabase` row,
|
||
a container name, or a tuner probe without a common base class:
|
||
|
||
{'engine': 'mysql'|'postgresql', 'container': str|None,
|
||
'user': str|None, 'password': str|None, 'database': str|None}
|
||
|
||
`container` is what decides native-vs-docker. `None`/absent means native.
|
||
"""
|
||
|
||
from typing import Optional
|
||
|
||
from app.services.database_service import DatabaseService
|
||
|
||
# The names the rest of the codebase spells these engines with. Kept here so an
|
||
# unknown engine is *reported*, never silently routed to mysql.
|
||
MYSQL_ALIASES = ('mysql', 'mariadb')
|
||
POSTGRES_ALIASES = ('postgresql', 'postgres', 'pgsql')
|
||
|
||
|
||
def target(engine: str, *, container: Optional[str] = None,
|
||
user: Optional[str] = None, password: Optional[str] = None,
|
||
database: Optional[str] = None) -> dict:
|
||
"""Build a target dict. Keyword-only so a call site reads at a glance."""
|
||
return {'engine': engine, 'container': container, 'user': user,
|
||
'password': password, 'database': database}
|
||
|
||
|
||
def target_from_managed(managed, *, password: Optional[str] = None) -> dict:
|
||
"""A target for a ``ManagedDatabase`` row.
|
||
|
||
The admin secret is passed in rather than decrypted here: decryption needs
|
||
app context, and this module deliberately does not reach for it.
|
||
"""
|
||
return target(
|
||
managed.engine,
|
||
container=managed.container_ref if managed.host_kind == 'docker' else None,
|
||
user=managed.admin_username or None,
|
||
password=password,
|
||
)
|
||
|
||
|
||
def exec_sql(tgt: dict, sql: str, *, machine_readable: bool = False,
|
||
timeout: int = 30) -> dict:
|
||
"""Run *sql* against *tgt*'s engine, native or containerised.
|
||
|
||
``machine_readable`` asks the client for parse-friendly output (no headers,
|
||
tab-separated). Pass it when the caller splits the output; leave it off
|
||
when the output is shown to a person.
|
||
"""
|
||
engine = (tgt.get('engine') or '').strip().lower()
|
||
container = tgt.get('container')
|
||
user = tgt.get('user')
|
||
password = tgt.get('password')
|
||
database = tgt.get('database')
|
||
|
||
if engine in MYSQL_ALIASES:
|
||
if container:
|
||
return DatabaseService.docker_mysql_execute(
|
||
container, sql, database=database, user=user or 'root',
|
||
password=password, machine_readable=machine_readable,
|
||
timeout=timeout)
|
||
return DatabaseService.mysql_execute(
|
||
sql, database=database, root_password=password)
|
||
|
||
if engine in POSTGRES_ALIASES:
|
||
if container:
|
||
return DatabaseService.docker_pg_execute(
|
||
container, sql, database=database or 'postgres',
|
||
user=user or 'postgres', password=password,
|
||
timeout=timeout, machine_readable=machine_readable)
|
||
return DatabaseService.pg_execute(
|
||
sql, database=database or 'postgres', user=user or 'postgres')
|
||
|
||
# Not a default. An unrecognised engine that quietly ran as mysql would
|
||
# produce a syntax error blamed on the caller's SQL.
|
||
return {'success': False, 'output': '',
|
||
'error': f"unsupported engine: {tgt.get('engine')!r}"}
|