/** * Pure CLI-client template construction for the supported database types. * Everything here is a function of (type, connection, optional overrides) — * no process, no I/O — so the CLI injection-safety surface is unit-testable: * argv stays an array (never shell-interpreted), the SQL itself always * travels on stdin, and passwords only ever appear in the environment * entries (`MYSQL_PWD` / `PGPASSWORD` / `SQLCMDPASSWORD`) or in a stdin * connect prefix (Oracle `connect`, Hive `!connect`) — never in argv, logs, * or returns. ClickHouse HTTP authentication lives in the shared runner. * * Metadata (schemas / tables / describe) queries and their per-type output * parsers live here too, so the /schemas /tables /describe routes stay thin. * @module @yejiming/dsh-data-agent/clients */ import type { DatabaseConnection, DatabaseType } from './connections.ts'; import z from 'schemastery'; import { assertSingleStatement, assertSqlServerSafeInput, hasTopLevelKeyword, stripTrailingTerminator } from './sql.ts'; export { assertSingleStatement, assertSqlServerSafeInput, hasTopLevelKeyword, stripTrailingTerminator }; /** * Classify a SQL text as a read or write statement by its FIRST effective * token (a conservative read whitelist, not a parser). `with` is read only * when its body's first token is `select`. SQLite `pragma` is read in its * query form and write when a value is assigned. */ export declare function classifyStatement(sql: string, type: DatabaseType): 'read' | 'write'; /** * Enforce the configured `maxRows` on a read query instead of relying on the * prompt. SELECT/CTE-read statements get a real top-level LIMIT (Oracle uses * a ROWNUM wrapper because it has no LIMIT); SHOW/DESCRIBE/EXPLAIN/PRAGMA are * left untouched here and are capped while parsing structured output. * * An existing numeric top-level LIMIT is rewritten when it is larger than * `maxRows`; a smaller existing LIMIT is preserved, and a non-numeric or * unparseable LIMIT is left for the client (structured tools still truncate). */ export declare function enforceReadRowLimit(sql: string, type: DatabaseType, maxRows: number): string; /** Rare control separator keeps ordinary tabs/pipes inside sqlcmd values intact. */ export declare const SQLSERVER_COLUMN_SEPARATOR = "\u001F"; /** * Validate and quote one schema/table identifier for a safe metadata query. * Identifiers accept Unicode letters, combining marks and numbers plus `_`/`$` * without normalizing or case-folding the database-provided text. Each value is * then wrapped for its dialect. Whitespace, controls, punctuation and quoting * delimiters remain outside the deliberately narrow metadata-input boundary. */ export declare function sanitizeIdentifier(type: DatabaseType, identifier: string): string; /** One deployment override for a database type's CLI client. */ export interface ClientConfig { /** Executable name (resolved through PATH) or absolute path. */ command?: string; /** Extra flag arguments prepended before the built-in flags. */ args?: readonly string[]; /** Absolute directories searched after the current subprocess PATH. */ searchPaths?: readonly string[]; } /** Database types backed by a locally resolved CLI executable. */ export type CliDatabaseType = Exclude; /** Loader schema for one client override (all fields optional at input). */ export declare const clientConfigSchema: z; args: z; searchPaths: z; }>, Schemastery.ObjectT<{ command: z; args: z; searchPaths: z; }>>; export declare const clientsSchema: z, import("cosmokit").Dict; args: z; searchPaths: z; }>, "mysql" | "postgres" | "sqlite" | "oracle" | "hive" | "impala" | "doris" | "sqlserver">>; /** * A fully constructed client invocation: argv (command + flags, no SQL), * the credential env entries, and the stdin prefix (Oracle/Hive connect * lines) the runner writes before the SQL text. */ export interface ClientTemplate { /** Executable to resolve through {@link SubprocessService.resolveExecutable}. */ command: string; /** Flag arguments only; the SQL text is written to stdin by the runner. */ args: readonly string[]; /** Credential env entries (e.g. `{ MYSQL_PWD }`), never argv. */ env: Readonly>; /** stdin text written BEFORE the SQL (Oracle SET/connect, Hive !connect); '' otherwise. */ stdinPrefix: string; } /** * Compose one complete client stdin payload. Oracle's structured SQL*Plus * mode is a script protocol rather than an EOF-delimited command: normalize * the already-validated statement to one terminator and exit explicitly. * Raw/introspection modes and every other client preserve the legacy payload. */ export declare function buildClientStdin(type: DatabaseType, mode: 'query' | 'introspect' | 'structured', prefix: string, sql: string): string; /** * Build one client invocation for a query execution (plain output). Flags * come BEFORE the connection arguments everywhere: sqlite3 takes * `[options] `, and putting flags first is harmless for the others. */ export declare function buildClientTemplate(type: DatabaseType, connection: DatabaseConnection, override?: ClientConfig): ClientTemplate; /** Build one client invocation for metadata runs (machine-readable flags). */ export declare function buildIntrospectTemplate(type: DatabaseType, connection: DatabaseConnection, override?: ClientConfig): ClientTemplate; /** * Build one client invocation for the structured `sql-query` tool: every * supported client prints a header row followed by one row per line (mysql * tab, postgres pipe, sqlite csv, oracle pipe, hive/impala tsv). */ export declare function buildStructuredQueryTemplate(type: DatabaseType, connection: DatabaseConnection, override?: ClientConfig): ClientTemplate; /** * The table-listing SQL per type, run at /connect time to verify * connectivity: the connected database's own tables (mysql uses the * connection's database as the schema; postgres lists `public`; oracle lists * the connected user's tables; hive/impala list the default database). */ export declare function tableListingSql(type: DatabaseType, connection?: DatabaseConnection): string; /** * Metadata query per kind × type. `schema`/`table` are validated by the shared * connection service before they reach here; builders still quote identifier * positions or escape value positions rather than concatenating raw text. */ export declare function metadataQuery(kind: 'schemas' | 'tables' | 'describe', type: DatabaseType, schema?: string, table?: string): string; /** * Split one type's machine-readable listing output into trimmed lines. * Header lines are stripped per type: mysql `--batch` prints a header row * (skip 1); postgres `-t`, sqlite `-noheader`, oracle `SET HEADING OFF`, * hive/impala batch modes print none (skip 0). */ export declare function parseListing(type: DatabaseType, stdout: string): string[]; /** Parse one type's table-listing output (the /connect connectivity check). */ export declare function parseTableListing(type: DatabaseType, stdout: string): string[]; /** One described column (nullable absent when the client reports none). */ export interface ColumnInfo { name: string; type: string; nullable?: boolean; } /** Remove only terminal sqlcmd row-count footer lines, never matching data in the middle. */ export declare function stripSqlServerRowCountFooter(stdout: string): string; /** * Parse one type's describe output into columns. Formats: * - mysql `--batch`: `Field\tType\tNull\tKey\t...` (skip header); * - postgres `-t -A`: `name|type|is_nullable`; * - sqlite `-noheader -list`: `cid|name|type|notnull|dflt|pk` (name is part 1); * - oracle (`SET COLSEP '|'`, heading off): `NAME|TYPE|NULLABLE`; * - hive/impala batch: `name\ttype\tcomment`. */ export declare function parseColumns(type: DatabaseType, stdout: string): ColumnInfo[];