Files
dbx/apps/desktop/src/lib/table/mysqlTableEngine.ts
T

89 lines
3.8 KiB
TypeScript

import type { ConnectionConfig, QueryResult } from "@/types/database";
export interface MysqlTableEngineMetadata {
currentEngine?: string;
defaultEngine?: string;
engines: string[];
}
export interface MysqlTableEngineDraft {
value: string;
originalValue: string;
}
export const MYSQL_STORAGE_ENGINES_SQL = "SHOW ENGINES";
/** Encodes a schema/table name as a byte literal so non-ASCII names survive whatever
* charset the session happens to use. Shared with the table collation lookup. */
export function mysqlUtf8Literal(value: string): string {
const bytes = new TextEncoder().encode(value);
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
return `CONVERT(X'${hex}' USING utf8mb4)`;
}
export function mysqlTableEngineSql(database: string, table: string): string {
return ["SELECT ENGINE AS engine", "FROM information_schema.TABLES", `WHERE TABLE_SCHEMA = ${mysqlUtf8Literal(database)}`, ` AND TABLE_NAME = ${mysqlUtf8Literal(table)}`, "LIMIT 1"].join("\n");
}
function columnIndex(result: QueryResult, name: string): number {
return result.columns.findIndex((column) => column.trim().toLowerCase() === name.toLowerCase());
}
function cellText(result: QueryResult, rowIndex: number, index: number): string {
if (index < 0) return "";
const value = result.rows[rowIndex]?.[index];
return value === null || value === undefined ? "" : String(value).trim();
}
export function parseMysqlTableEngineMetadata(enginesResult: QueryResult, tableResult?: QueryResult): MysqlTableEngineMetadata {
const engineIndex = columnIndex(enginesResult, "Engine");
const supportIndex = columnIndex(enginesResult, "Support");
const engines: string[] = [];
let defaultEngine: string | undefined;
const seen = new Set<string>();
for (let rowIndex = 0; rowIndex < enginesResult.rows.length; rowIndex += 1) {
const engine = cellText(enginesResult, rowIndex, engineIndex);
const support = cellText(enginesResult, rowIndex, supportIndex).toUpperCase();
if (!engine || (support !== "YES" && support !== "DEFAULT")) continue;
const key = engine.toLowerCase();
if (!seen.has(key)) {
engines.push(engine);
seen.add(key);
}
if (support === "DEFAULT") defaultEngine = engine;
}
let currentEngine: string | undefined;
if (tableResult) {
currentEngine = cellText(tableResult, 0, columnIndex(tableResult, "engine")) || undefined;
if (currentEngine && !seen.has(currentEngine.toLowerCase())) engines.unshift(currentEngine);
}
return { currentEngine, defaultEngine, engines };
}
export function mysqlTableEngineDraft(metadata: MysqlTableEngineMetadata, isCreateMode: boolean): MysqlTableEngineDraft {
const current = (isCreateMode ? metadata.defaultEngine || metadata.engines[0] : metadata.currentEngine) || "";
return { value: current, originalValue: current };
}
export function refreshMysqlTableEngineDraft(metadata: MysqlTableEngineMetadata, current: MysqlTableEngineDraft, isCreateMode: boolean, preserveDraft: boolean): MysqlTableEngineDraft {
const server = mysqlTableEngineDraft(metadata, isCreateMode);
if (!preserveDraft || current.value === current.originalValue) return server;
return { value: current.value, originalValue: server.originalValue };
}
export function mysqlTableEngineSqlOption(draft: MysqlTableEngineDraft, isCreateMode: boolean, supported: boolean): string | undefined {
const value = draft.value.trim();
if (!supported || !value) return undefined;
if (!isCreateMode && value.toLowerCase() === draft.originalValue.trim().toLowerCase()) return undefined;
return value;
}
export function supportsMysqlTableEngine(connection: Pick<ConnectionConfig, "db_type" | "driver_profile"> | undefined): boolean {
if (connection?.db_type !== "mysql") return false;
const profile = connection.driver_profile?.trim().toLowerCase();
return !profile || profile === "mysql";
}