fix: address lineage review feedback
This commit is contained in:
1 parent
5a37f584aa
commit
4d6cd5351c
26 files changed
+671
-139
No files matched your search
@@ -0,0 +1,81 @@
|
||||
import type { DatabaseType } from "@/types/database";
|
||||
import { qualifiedTableName, quoteTableIdentifier } from "./tableSelectSql.ts";
|
||||
|
||||
export type GridCellValue = string | number | boolean | null;
|
||||
|
||||
export interface DataGridTableMeta {
|
||||
schema?: string;
|
||||
tableName: string;
|
||||
primaryKeys: string[];
|
||||
}
|
||||
|
||||
export interface DataGridSaveStatementOptions {
|
||||
databaseType?: DatabaseType;
|
||||
tableMeta: DataGridTableMeta;
|
||||
columns: string[];
|
||||
rows: GridCellValue[][];
|
||||
dirtyRows: Array<[number, Array<[number, GridCellValue]>]>;
|
||||
deletedRows: number[];
|
||||
newRows: GridCellValue[][];
|
||||
}
|
||||
|
||||
export function buildDataGridSaveStatements(options: DataGridSaveStatementOptions): string[] {
|
||||
const table = qualifiedTableName({
|
||||
databaseType: options.databaseType,
|
||||
schema: options.tableMeta.schema,
|
||||
tableName: options.tableMeta.tableName,
|
||||
});
|
||||
const statements: string[] = [];
|
||||
|
||||
for (const [rowIndex, changes] of options.dirtyRows) {
|
||||
const row = options.rows[rowIndex];
|
||||
if (!row) continue;
|
||||
const sets = changes
|
||||
.map(([columnIndex, value]) => `${quoteIdent(options.databaseType, options.columns[columnIndex])} = ${formatGridSqlLiteral(value)}`)
|
||||
.join(", ");
|
||||
const where = buildPrimaryKeyWhere(options.databaseType, options.tableMeta.primaryKeys, options.columns, row);
|
||||
statements.push(`UPDATE ${table} SET ${sets} WHERE ${where};`);
|
||||
}
|
||||
|
||||
for (const rowIndex of options.deletedRows) {
|
||||
const row = options.rows[rowIndex];
|
||||
if (!row) continue;
|
||||
const where = buildPrimaryKeyWhere(options.databaseType, options.tableMeta.primaryKeys, options.columns, row);
|
||||
statements.push(`DELETE FROM ${table} WHERE ${where};`);
|
||||
}
|
||||
|
||||
for (const row of options.newRows) {
|
||||
const columns = options.columns.map((column) => quoteIdent(options.databaseType, column)).join(", ");
|
||||
const values = row.map(formatGridSqlLiteral).join(", ");
|
||||
statements.push(`INSERT INTO ${table} (${columns}) VALUES (${values});`);
|
||||
}
|
||||
|
||||
return statements;
|
||||
}
|
||||
|
||||
export function formatGridSqlLiteral(value: GridCellValue): string {
|
||||
if (value === null || value === undefined) return "NULL";
|
||||
if (typeof value === "boolean") return value ? "TRUE" : "FALSE";
|
||||
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
||||
const text = String(value);
|
||||
if (text === "") return "''";
|
||||
return `'${text.replace(/\\/g, "\\\\").replace(/'/g, "''")}'`;
|
||||
}
|
||||
|
||||
function buildPrimaryKeyWhere(
|
||||
databaseType: DatabaseType | undefined,
|
||||
primaryKeys: string[],
|
||||
columns: string[],
|
||||
row: GridCellValue[],
|
||||
): string {
|
||||
return primaryKeys
|
||||
.map((primaryKey) => {
|
||||
const value = row[columns.indexOf(primaryKey)];
|
||||
return `${quoteIdent(databaseType, primaryKey)} = ${formatGridSqlLiteral(value)}`;
|
||||
})
|
||||
.join(" AND ");
|
||||
}
|
||||
|
||||
function quoteIdent(databaseType: DatabaseType | undefined, name: string): string {
|
||||
return quoteTableIdentifier(databaseType, name);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export function shouldDeleteHistoryEntry(confirmDelete: () => boolean): boolean {
|
||||
return confirmDelete();
|
||||
}
|
||||
|
||||
export function shouldClearHistory(entryCount: number, confirmClear: () => boolean): boolean {
|
||||
return entryCount > 0 && confirmClear();
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import type { GridCellValue } from "./dataGridSql.ts";
|
||||
|
||||
export interface MarkdownTableData {
|
||||
columns: string[];
|
||||
rows: GridCellValue[][];
|
||||
}
|
||||
|
||||
export function formatMarkdownTable(data: MarkdownTableData): string {
|
||||
const normalizedColumns = data.columns.map(markdownCell);
|
||||
const normalizedRows = data.rows.map((row) => row.map((cell) => markdownCell(displayCell(cell))));
|
||||
const widths = normalizedColumns.map((column, index) =>
|
||||
Math.max(column.length, ...normalizedRows.map((row) => row[index]?.length ?? 0), 3)
|
||||
);
|
||||
const header = `| ${normalizedColumns.map((column, index) => pad(column, widths[index])).join(" | ")} |`;
|
||||
const separator = `| ${widths.map((width) => "-".repeat(width)).join(" | ")} |`;
|
||||
const body = normalizedRows
|
||||
.map((row) => `| ${row.map((cell, index) => pad(cell, widths[index])).join(" | ")} |`)
|
||||
.join("\n");
|
||||
return `${[header, separator, body].filter(Boolean).join("\n")}\n`;
|
||||
}
|
||||
|
||||
function displayCell(value: GridCellValue): string {
|
||||
if (value === null) return "NULL";
|
||||
if (typeof value === "boolean") return value ? "true" : "false";
|
||||
return String(value);
|
||||
}
|
||||
|
||||
function markdownCell(value: string): string {
|
||||
return value.replace(/\|/g, "\\|").replace(/\r?\n/g, "<br>");
|
||||
}
|
||||
|
||||
function pad(value: string, width: number): string {
|
||||
return value.padEnd(width);
|
||||
}
|
||||
@@ -5,6 +5,7 @@ export interface BuildTableSelectSqlOptions {
|
||||
schema?: string;
|
||||
tableName: string;
|
||||
primaryKeys?: string[];
|
||||
fallbackOrderColumns?: string[];
|
||||
orderBy?: string;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
@@ -13,6 +14,7 @@ export interface BuildTableSelectSqlOptions {
|
||||
|
||||
export function quoteTableIdentifier(databaseType: DatabaseType | undefined, name: string): string {
|
||||
if (databaseType === "mysql") return `\`${name.replace(/`/g, "``")}\``;
|
||||
if (databaseType === "sqlserver") return `[${name.replace(/\]/g, "]]")}]`;
|
||||
return `"${name.replace(/"/g, '""')}"`;
|
||||
}
|
||||
|
||||
@@ -37,6 +39,8 @@ export function buildTableSelectSql(options: BuildTableSelectSqlOptions): string
|
||||
const where = predicate ? ` WHERE (${predicate})` : "";
|
||||
const defaultOrderBy = options.primaryKeys?.length
|
||||
? options.primaryKeys.map((pk) => `${quoteTableIdentifier(databaseType, pk)} ASC`).join(", ")
|
||||
: options.fallbackOrderColumns?.length
|
||||
? options.fallbackOrderColumns.map((column) => `${quoteTableIdentifier(databaseType, column)} ASC`).join(", ")
|
||||
: undefined;
|
||||
const orderBy = options.orderBy ?? defaultOrderBy;
|
||||
const order = orderBy ? ` ORDER BY ${orderBy}` : "";
|
||||
@@ -47,7 +51,8 @@ export function buildTableSelectSql(options: BuildTableSelectSqlOptions): string
|
||||
}
|
||||
|
||||
if (databaseType === "sqlserver") {
|
||||
return `SELECT TOP ${limit} * FROM ${table}${where}${order}`;
|
||||
const stableOrder = order || " ORDER BY (SELECT NULL)";
|
||||
return `SELECT * FROM ${table}${where}${stableOrder} OFFSET ${options.offset ?? 0} ROWS FETCH NEXT ${limit} ROWS ONLY`;
|
||||
}
|
||||
|
||||
const offset = options.offset ? ` OFFSET ${options.offset}` : "";
|
||||
|
||||
+5
-3
@@ -146,6 +146,10 @@ export async function executeBatch(connectionId: string, database: string, state
|
||||
return invoke("execute_batch", { connectionId, database, statements });
|
||||
}
|
||||
|
||||
export async function executeScript(connectionId: string, database: string, sql: string): Promise<QueryResult> {
|
||||
return invoke("execute_script", { connectionId, database, sql });
|
||||
}
|
||||
|
||||
export async function listIndexes(connectionId: string, database: string, schema: string, table: string): Promise<IndexInfo[]> {
|
||||
return invoke("list_indexes", { connectionId, database, schema, table });
|
||||
}
|
||||
@@ -394,9 +398,7 @@ export async function startTransfer(
|
||||
if (event.payload.transferId === request.transferId) {
|
||||
onProgress(event.payload);
|
||||
if (event.payload.status === "done" || event.payload.status === "error" || event.payload.status === "cancelled") {
|
||||
if (event.payload.tableIndex === event.payload.totalTables - 1 || event.payload.status !== "error") {
|
||||
unlisten();
|
||||
}
|
||||
unlisten();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export function isTauriRuntime(globalObject: Record<string, unknown> = globalThis as Record<string, unknown>): boolean {
|
||||
return Boolean(globalObject.__TAURI_INTERNALS__ || globalObject.__TAURI__);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
export interface TransferTerminalState {
|
||||
done: boolean;
|
||||
cancelled: boolean;
|
||||
error: boolean;
|
||||
}
|
||||
|
||||
export interface TransferStatusLike {
|
||||
status: "running" | "tableDone" | "done" | "error" | "cancelled";
|
||||
}
|
||||
|
||||
export function nextTransferTerminalState(
|
||||
state: TransferTerminalState,
|
||||
progress: TransferStatusLike,
|
||||
): TransferTerminalState {
|
||||
if (progress.status === "done") return { ...state, done: true };
|
||||
if (progress.status === "cancelled") return { ...state, cancelled: true };
|
||||
if (progress.status === "error") return { ...state, error: true };
|
||||
return state;
|
||||
}
|
||||
Reference in new issue
Block a user