feat(query): move grid save prep to Rust

This commit is contained in:
t8y2
2026-05-22 15:31:50 +08:00
parent 56aeed79fc
commit 209eff528d
14 changed files with 1448 additions and 1092 deletions
@@ -1,12 +1,6 @@
import { ref, computed, nextTick, watch, onBeforeUnmount, type ComputedRef, type Ref } from "vue";
import * as api from "@/lib/api";
import {
buildDataGridRollbackStatements,
buildDataGridSaveStatements,
dataGridSaveExecutionSchema,
normalizeDataGridSaveError,
validateDataGridSave,
} from "@/lib/dataGridSql";
import { normalizeDataGridSaveError } from "@/lib/dataGridSql";
import { rowStatusFilterAfterAddingRow, type RowStatusFilter } from "@/lib/gridRowStatus";
import { supportsDataGridTransaction } from "@/lib/tableEditing";
import { useConnectionStore } from "@/stores/connectionStore";
@@ -636,30 +630,28 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
}
const stmtOptions = saveStatementOptions();
const validationError = stmtOptions
? validateDataGridSave({
databaseType: databaseType.value,
tableMeta: tableMeta.value,
columns: stmtOptions.columns,
sourceColumns: stmtOptions.sourceColumns,
rows: stmtOptions.rows,
columnInfo: tableMeta.value?.columns,
dirtyRows: stmtOptions.dirtyRows,
newRows: stmtOptions.newRows,
})
: undefined;
if (validationError) {
saveError.value = validationError;
let preparedSave: Awaited<ReturnType<typeof api.prepareDataGridSave>> | undefined;
if (stmtOptions) {
try {
preparedSave = await api.prepareDataGridSave(stmtOptions);
} catch (e: any) {
saveError.value = normalizeDataGridSaveError(databaseType.value, e);
isSaving.value = false;
return;
}
}
if (preparedSave?.validationError) {
saveError.value = preparedSave.validationError;
isSaving.value = false;
return;
}
const stmts = stmtOptions ? buildDataGridSaveStatements(stmtOptions) : [];
const stmts = preparedSave?.statements ?? [];
if (stmts.length === 0) {
isSaving.value = false;
return;
}
const rollbackStmts = stmtOptions ? buildDataGridRollbackStatements(stmtOptions) : [];
const rollbackStmts = preparedSave?.rollbackStatements ?? [];
const start = Date.now();
let apiResult: { affected_rows?: number } | undefined;
console.info("[DBX][dataGrid:save-statements]", {
@@ -677,7 +669,7 @@ export function useDataGridEditor(options: UseDataGridEditorOptions) {
connectionId.value,
database.value,
stmts,
dataGridSaveExecutionSchema(databaseType.value, tableMeta.value),
preparedSave?.executionSchema,
);
} catch (e: any) {
saveError.value = normalizeDataGridSaveError(databaseType.value, e);
+1
View File
@@ -88,6 +88,7 @@ export const cancelQuery = forward("cancelQuery");
export const closeQuerySession = forward("closeQuerySession");
export const analyzeSqlReferences = forward("analyzeSqlReferences");
export const analyzeEditableQueryEditability = forward("analyzeEditableQueryEditability");
export const prepareDataGridSave = forward("prepareDataGridSave");
// AI
export const aiComplete = forward("aiComplete");
+1 -500
View File
@@ -1,10 +1,5 @@
import type { DatabaseType } from "@/types/database";
import {
DBX_NEO4J_ELEMENT_ID_COLUMN,
DBX_ROWID_COLUMN,
DBX_TDENGINE_TBNAME_COLUMN,
usesKeylessRowPredicate,
} from "./tableEditing.ts";
import { DBX_ROWID_COLUMN } from "./tableEditing.ts";
import { qualifiedTableName, quoteTableIdentifier } from "./tableSelectSql.ts";
export type GridCellValue = string | number | boolean | null;
@@ -54,155 +49,6 @@ export interface DataGridCopyInsertStatementOptions {
excludePrimaryKeys?: boolean;
}
export interface DataGridSaveValidationOptions {
databaseType?: DatabaseType;
tableMeta?: DataGridTableMeta;
columns: string[];
sourceColumns?: Array<string | undefined>;
rows?: GridCellValue[][];
columnInfo?: DataGridColumnInfo[];
dirtyRows: Array<[number, Array<[number, GridCellValue]>]>;
newRows: GridCellValue[][];
}
export function validateDataGridSave(options: DataGridSaveValidationOptions): string | undefined {
const duplicateKeyError = validateInsertedPrimaryKeys(options);
if (duplicateKeyError) return duplicateKeyError;
const notNullColumns = new Set(
(options.columnInfo ?? [])
.filter(
(column) =>
!column.is_nullable &&
!column.column_default &&
!isAutoGeneratedColumn(column) &&
!isOracleRowId(options.databaseType, column.name),
)
.map((column) => normalizeColumnName(column.name)),
);
if (notNullColumns.size === 0) return undefined;
for (const [, changes] of options.dirtyRows) {
for (const [columnIndex, value] of changes) {
const sourceColumn = effectiveColumn(options.sourceColumns, options.columns, columnIndex);
if (!sourceColumn) continue;
if (isNullWriteToNotNullColumn(options.databaseType, notNullColumns, sourceColumn, value)) {
return nullWriteError(sourceColumn);
}
}
}
for (const row of options.newRows) {
for (const columnIndex of options.columns.keys()) {
const sourceColumn = effectiveColumn(options.sourceColumns, options.columns, columnIndex);
if (!sourceColumn) continue;
if (isNullWriteToNotNullColumn(options.databaseType, notNullColumns, sourceColumn, row[columnIndex])) {
return nullWriteError(sourceColumn);
}
}
}
return undefined;
}
function validateInsertedPrimaryKeys(options: DataGridSaveValidationOptions): string | undefined {
const primaryKeys = options.tableMeta?.primaryKeys ?? [];
if (primaryKeys.length === 0 || options.newRows.length === 0) return undefined;
const saveColumns = effectiveColumns(options.sourceColumns, options.columns);
const primaryKeyIndexes = primaryKeys.map((primaryKey) => findColumnIndex(saveColumns, primaryKey));
if (primaryKeyIndexes.some((index) => index === -1)) return undefined;
const existingKeys = new Set<string>();
for (const row of options.rows ?? []) {
const key = primaryKeyValueKey(primaryKeyIndexes, row);
if (key) existingKeys.add(key);
}
const newKeys = new Set<string>();
for (const row of options.newRows) {
const key = primaryKeyValueKey(primaryKeyIndexes, row);
if (!key) continue;
if (existingKeys.has(key) || newKeys.has(key)) {
return duplicatePrimaryKeyError(primaryKeys, primaryKeyIndexes, row, existingKeys.has(key));
}
newKeys.add(key);
}
return undefined;
}
export function buildDataGridSaveStatements(options: DataGridSaveStatementOptions): string[] {
if (options.databaseType === "neo4j") return buildNeo4jDataGridSaveStatements(options);
if (options.databaseType === "tdengine") return buildTdengineDataGridSaveStatements(options);
const saveColumns = effectiveColumns(options.sourceColumns, options.columns);
const columnInfo = columnInfoByName(options.tableMeta.columns);
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
.filter(([columnIndex]) => !!saveColumns[columnIndex])
.filter(([columnIndex]) => !isOracleRowId(options.databaseType, saveColumns[columnIndex]))
.map(
([columnIndex, value]) =>
`${quoteIdent(options.databaseType, saveColumns[columnIndex]!)} = ${formatGridSqlLiteral(
value,
options.databaseType,
columnInfo.get(normalizeColumnName(saveColumns[columnIndex]!)),
)}`,
)
.join(", ");
if (!sets) continue;
const where = buildPrimaryKeyWhere(
options.databaseType,
options.tableMeta.primaryKeys,
saveColumns,
row,
columnInfo,
);
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,
saveColumns,
row,
columnInfo,
);
statements.push(`DELETE FROM ${table} WHERE ${where};`);
}
for (const row of options.newRows) {
const insertPairs = saveColumns
.map((column, index) => ({ column, value: row[index] }))
.filter((pair): pair is { column: string; value: GridCellValue } => !!pair.column)
.filter((pair) => !isOracleRowId(options.databaseType, pair.column))
.filter((pair) => pair.value !== null && pair.value !== undefined);
if (!insertPairs.length) continue;
const columns = insertPairs.map((pair) => quoteIdent(options.databaseType, pair.column)).join(", ");
const values = insertPairs
.map((pair) =>
formatGridSqlLiteral(pair.value, options.databaseType, columnInfo.get(normalizeColumnName(pair.column))),
)
.join(", ");
statements.push(`INSERT INTO ${table} (${columns}) VALUES (${values});`);
}
return statements;
}
export function buildDataGridCopyUpdateStatements(options: DataGridCopyUpdateStatementOptions): string[] {
if (options.databaseType === "neo4j" || options.databaseType === "tdengine") return [];
const primaryKeys = options.tableMeta.primaryKeys;
@@ -295,154 +141,6 @@ export function buildDataGridCopyInsertStatement(options: DataGridCopyInsertStat
return `INSERT INTO ${table} (${columns}) VALUES${valueRows.length === 1 ? " " : "\n"}${valueRows.join(",\n")};`;
}
function buildTdengineDataGridSaveStatements(options: DataGridSaveStatementOptions): string[] {
const saveColumns = effectiveColumns(options.sourceColumns, options.columns);
const statements: string[] = [];
const canOverwriteExistingRows = saveColumns.some((column) => column?.toLowerCase() === DBX_TDENGINE_TBNAME_COLUMN);
if (canOverwriteExistingRows) {
for (const [rowIndex, changes] of options.dirtyRows) {
const row = options.rows[rowIndex];
if (!row) continue;
const afterRow = [...row];
for (const [columnIndex, value] of changes) {
afterRow[columnIndex] = value;
}
const statement = buildTdengineInsertStatement(options, saveColumns, afterRow);
if (statement) statements.push(statement);
}
}
for (const row of options.newRows) {
const statement = buildTdengineInsertStatement(options, saveColumns, row);
if (statement) statements.push(statement);
}
return statements;
}
function buildTdengineInsertStatement(
options: DataGridSaveStatementOptions,
saveColumns: Array<string | undefined>,
row: GridCellValue[],
): string | undefined {
const tbname = tdengineTbnameValue(saveColumns, row);
const table = qualifiedTableName({
databaseType: "tdengine",
schema: options.tableMeta.schema,
tableName: options.tableMeta.tableName,
});
const tagColumns = tdengineTagColumns(options.tableMeta.columns);
const insertPairs = saveColumns
.map((column, index) => ({ column, value: row[index] }))
.filter((pair): pair is { column: string; value: GridCellValue } => !!pair.column)
.filter((pair) => pair.value !== null && pair.value !== undefined)
.filter((pair) => tdengineCanInsertColumn(pair.column, options.tableMeta.tableName, tbname, tagColumns));
if (!insertPairs.length) return undefined;
const columns = insertPairs.map((pair) => quoteIdent("tdengine", pair.column)).join(", ");
const values = insertPairs.map((pair) => formatGridSqlLiteral(pair.value, "tdengine")).join(", ");
return `INSERT INTO ${table} (${columns}) VALUES (${values});`;
}
function tdengineTbnameValue(saveColumns: Array<string | undefined>, row: GridCellValue[]): string | undefined {
const index = saveColumns.findIndex((column) => column?.toLowerCase() === DBX_TDENGINE_TBNAME_COLUMN);
if (index < 0) return undefined;
const value = row[index];
return value === null || value === undefined ? undefined : String(value);
}
function tdengineCanInsertColumn(
column: string,
tableName: string,
tbname: string | undefined,
tagColumns: Set<string>,
): boolean {
const normalized = column.toLowerCase();
const targetIsChildTable = !tbname || tbname === tableName;
if (!targetIsChildTable) return true;
if (normalized === DBX_TDENGINE_TBNAME_COLUMN) return false;
return !tagColumns.has(normalized);
}
function tdengineTagColumns(columns: DataGridColumnInfo[] | undefined): Set<string> {
return new Set(
(columns ?? []).filter((column) => /\btag\b/i.test(column.extra ?? "")).map((column) => column.name.toLowerCase()),
);
}
export function buildDataGridRollbackStatements(options: DataGridSaveStatementOptions): string[] {
if (options.databaseType === "neo4j") return buildNeo4jDataGridRollbackStatements(options);
const saveColumns = effectiveColumns(options.sourceColumns, options.columns);
const columnInfo = columnInfoByName(options.tableMeta.columns);
const table = qualifiedTableName({
databaseType: options.databaseType,
schema: options.tableMeta.schema,
tableName: options.tableMeta.tableName,
});
const statements: string[] = [];
for (const row of options.newRows) {
const where = buildRowWhere(options.databaseType, saveColumns, row, columnInfo);
if (where) statements.push(`DELETE FROM ${table} WHERE ${where};`);
}
for (const rowIndex of options.deletedRows) {
const row = options.rows[rowIndex];
if (!row) continue;
const insertPairs = saveColumns
.map((column, index) => ({ column, value: row[index] }))
.filter((pair): pair is { column: string; value: GridCellValue } => !!pair.column)
.filter((pair) => !isOracleRowId(options.databaseType, pair.column));
const columns = insertPairs.map((pair) => quoteIdent(options.databaseType, pair.column)).join(", ");
const values = insertPairs
.map((pair) =>
formatGridSqlLiteral(pair.value, options.databaseType, columnInfo.get(normalizeColumnName(pair.column))),
)
.join(", ");
statements.push(`INSERT INTO ${table} (${columns}) VALUES (${values});`);
}
for (const [rowIndex, changes] of options.dirtyRows) {
const row = options.rows[rowIndex];
if (!row) continue;
const afterRow = [...row];
for (const [columnIndex, value] of changes) {
afterRow[columnIndex] = value;
}
const writableChanges = changes.filter(
([columnIndex]) => !!saveColumns[columnIndex] && !isOracleRowId(options.databaseType, saveColumns[columnIndex]),
);
const sets = writableChanges
.map(
([columnIndex]) =>
`${quoteIdent(options.databaseType, saveColumns[columnIndex]!)} = ${formatGridSqlLiteral(
row[columnIndex],
options.databaseType,
columnInfo.get(normalizeColumnName(saveColumns[columnIndex]!)),
)}`,
)
.join(", ");
if (!sets) continue;
const where = [
buildPrimaryKeyWhere(options.databaseType, options.tableMeta.primaryKeys, saveColumns, afterRow, columnInfo),
...writableChanges.map(([columnIndex, value]) =>
buildColumnPredicate(
options.databaseType,
saveColumns[columnIndex]!,
value,
columnInfo.get(normalizeColumnName(saveColumns[columnIndex]!)),
),
),
]
.filter(Boolean)
.join(" AND ");
statements.push(`UPDATE ${table} SET ${sets} WHERE ${where};`);
}
return statements;
}
function effectiveColumns(
sourceColumns: Array<string | undefined> | undefined,
columns: string[],
@@ -451,27 +149,10 @@ function effectiveColumns(
return sourceColumns;
}
function effectiveColumn(
sourceColumns: Array<string | undefined> | undefined,
columns: string[],
index: number,
): string | undefined {
return effectiveColumns(sourceColumns, columns)[index];
}
function columnInfoByName(columns: DataGridColumnInfo[] | undefined): Map<string, DataGridColumnInfo> {
return new Map((columns ?? []).map((column) => [normalizeColumnName(column.name), column]));
}
export function dataGridSaveExecutionSchema(
databaseType: DatabaseType | undefined,
tableMeta: DataGridTableMeta | undefined,
): string | undefined {
if (databaseType === "neo4j") return undefined;
if (databaseType === "oracle") return undefined;
return tableMeta?.schema;
}
export function normalizeDataGridSaveError(databaseType: DatabaseType | undefined, error: unknown): string {
const message = error instanceof Error ? error.message : String(error);
if (databaseType === "hive" && /Attempt to do update or delete|Error 10294/i.test(message)) {
@@ -560,43 +241,6 @@ function localTimezoneOffsetSuffix(text: string): string {
return `${sign}${hours}:${minutes}`;
}
function buildPrimaryKeyWhere(
databaseType: DatabaseType | undefined,
primaryKeys: string[],
columns: Array<string | undefined>,
row: GridCellValue[],
columnInfo: Map<string, DataGridColumnInfo> = new Map(),
): string {
if (primaryKeys.length === 0 && usesKeylessRowPredicate(databaseType))
return buildRowWhere(databaseType, columns, row, columnInfo);
return primaryKeys
.map((primaryKey) => {
const value = row[columns.indexOf(primaryKey)];
return `${predicateIdent(databaseType, primaryKey)} = ${formatGridSqlLiteral(
value,
databaseType,
columnInfo.get(normalizeColumnName(primaryKey)),
)}`;
})
.join(" AND ");
}
function buildRowWhere(
databaseType: DatabaseType | undefined,
columns: Array<string | undefined>,
row: GridCellValue[],
columnInfo: Map<string, DataGridColumnInfo> = new Map(),
): string {
return columns
.map((column, index) =>
!column || isOracleRowId(databaseType, column)
? ""
: buildColumnPredicate(databaseType, column, row[index], columnInfo.get(normalizeColumnName(column))),
)
.filter(Boolean)
.join(" AND ");
}
function buildColumnPredicate(
databaseType: DatabaseType | undefined,
column: string,
@@ -612,63 +256,15 @@ function isOracleRowId(databaseType: DatabaseType | undefined, name: string | un
return databaseType === "oracle" && name?.toUpperCase() === DBX_ROWID_COLUMN;
}
function isNeo4jElementId(databaseType: DatabaseType | undefined, name: string | undefined): boolean {
return databaseType === "neo4j" && name === DBX_NEO4J_ELEMENT_ID_COLUMN;
}
function isAutoGeneratedColumn(column: DataGridColumnInfo): boolean {
return /\b(auto_increment|autoincrement|identity)\b/i.test(column.extra ?? "");
}
function isNullWriteToNotNullColumn(
databaseType: DatabaseType | undefined,
notNullColumns: Set<string>,
column: string | undefined,
value: GridCellValue,
): boolean {
if (!column || isOracleRowId(databaseType, column)) return false;
if (isNeo4jElementId(databaseType, column)) return false;
return value === null && notNullColumns.has(normalizeColumnName(column));
}
function findColumnIndex(columns: Array<string | undefined>, target: string): number {
const normalizedTarget = normalizeColumnName(target);
return columns.findIndex((column) => (column ? normalizeColumnName(column) : "") === normalizedTarget);
}
function primaryKeyValueKey(primaryKeyIndexes: number[], row: GridCellValue[]): string | undefined {
const values = primaryKeyIndexes.map((index) => row[index]);
if (values.some((value) => value === null || value === undefined)) return undefined;
return JSON.stringify(values);
}
function duplicatePrimaryKeyError(
primaryKeys: string[],
primaryKeyIndexes: number[],
row: GridCellValue[],
matchesExistingRow: boolean,
): string {
const keySummary = primaryKeys
.map((primaryKey, index) => `${primaryKey} = ${formatKeyValueForMessage(row[primaryKeyIndexes[index]])}`)
.join(", ");
const source = matchesExistingRow ? "the existing primary key" : "another new row's primary key";
return `New row duplicates ${source} (${keySummary}). Change the key before saving.`;
}
function formatKeyValueForMessage(value: GridCellValue): string {
if (value === null || value === undefined) return "NULL";
if (typeof value === "string") return `"${value.replace(/"/g, '\\"')}"`;
return String(value);
}
function normalizeColumnName(name: string): string {
return name.toUpperCase();
}
function nullWriteError(column: string): string {
return `Column "${column}" does not allow NULL.`;
}
function predicateIdent(databaseType: DatabaseType | undefined, name: string): string {
return isOracleRowId(databaseType, name) ? "ROWIDTOCHAR(ROWID)" : quoteIdent(databaseType, name);
}
@@ -676,98 +272,3 @@ function predicateIdent(databaseType: DatabaseType | undefined, name: string): s
function quoteIdent(databaseType: DatabaseType | undefined, name: string): string {
return quoteTableIdentifier(databaseType, name);
}
function buildNeo4jDataGridSaveStatements(options: DataGridSaveStatementOptions): string[] {
const label = quoteIdent("neo4j", options.tableMeta.tableName);
const statements: string[] = [];
for (const [rowIndex, changes] of options.dirtyRows) {
const row = options.rows[rowIndex];
if (!row) continue;
const sets = changes
.filter(([columnIndex]) => !isNeo4jElementId("neo4j", options.columns[columnIndex]))
.map(
([columnIndex, value]) =>
`n.${quoteIdent("neo4j", options.columns[columnIndex])} = ${formatGridSqlLiteral(value, "neo4j")}`,
)
.join(", ");
if (!sets) continue;
statements.push(`MATCH (n:${label}) WHERE ${neo4jElementIdPredicate(options.columns, row)} SET ${sets};`);
}
for (const rowIndex of options.deletedRows) {
const row = options.rows[rowIndex];
if (!row) continue;
statements.push(`MATCH (n:${label}) WHERE ${neo4jElementIdPredicate(options.columns, row)} DETACH DELETE n;`);
}
for (const row of options.newRows) {
const props = options.columns
.map((column, index) => ({ column, value: row[index] }))
.filter((pair) => !isNeo4jElementId("neo4j", pair.column))
.filter((pair) => pair.value !== null && pair.value !== undefined)
.map((pair) => `${quoteIdent("neo4j", pair.column)}: ${formatGridSqlLiteral(pair.value, "neo4j")}`)
.join(", ");
statements.push(props ? `CREATE (n:${label} {${props}});` : `CREATE (n:${label});`);
}
return statements;
}
function buildNeo4jDataGridRollbackStatements(options: DataGridSaveStatementOptions): string[] {
const label = quoteIdent("neo4j", options.tableMeta.tableName);
const statements: string[] = [];
for (const row of options.newRows) {
const where = neo4jRowPropertyWhere(options.columns, row);
statements.push(
where ? `MATCH (n:${label}) WHERE ${where} DETACH DELETE n;` : `MATCH (n:${label}) DETACH DELETE n;`,
);
}
for (const rowIndex of options.deletedRows) {
const row = options.rows[rowIndex];
if (!row) continue;
const props = options.columns
.map((column, index) => ({ column, value: row[index] }))
.filter((pair) => !isNeo4jElementId("neo4j", pair.column))
.filter((pair) => pair.value !== null && pair.value !== undefined)
.map((pair) => `${quoteIdent("neo4j", pair.column)}: ${formatGridSqlLiteral(pair.value, "neo4j")}`)
.join(", ");
statements.push(props ? `CREATE (n:${label} {${props}});` : `CREATE (n:${label});`);
}
for (const [rowIndex, changes] of options.dirtyRows) {
const row = options.rows[rowIndex];
if (!row) continue;
const sets = changes
.filter(([columnIndex]) => !isNeo4jElementId("neo4j", options.columns[columnIndex]))
.map(
([columnIndex]) =>
`n.${quoteIdent("neo4j", options.columns[columnIndex])} = ${formatGridSqlLiteral(row[columnIndex], "neo4j")}`,
)
.join(", ");
if (!sets) continue;
statements.push(`MATCH (n:${label}) WHERE ${neo4jElementIdPredicate(options.columns, row)} SET ${sets};`);
}
return statements;
}
function neo4jElementIdPredicate(columns: string[], row: GridCellValue[]): string {
const value = row[columns.indexOf(DBX_NEO4J_ELEMENT_ID_COLUMN)];
return `elementId(n) = ${formatGridSqlLiteral(value, "neo4j")}`;
}
function neo4jRowPropertyWhere(columns: string[], row: GridCellValue[]): string {
return columns
.map((column, index) => {
if (isNeo4jElementId("neo4j", column)) return "";
const value = row[index];
const ident = `n.${quoteIdent("neo4j", column)}`;
if (value === null || value === undefined) return `${ident} IS NULL`;
return `${ident} = ${formatGridSqlLiteral(value, "neo4j")}`;
})
.filter(Boolean)
.join(" AND ");
}
+6
View File
@@ -50,6 +50,8 @@ import type {
XlsxCellValue,
} from "./tauri";
import type { QueryEditability } from "@/lib/sqlAnalysis";
import type { DataGridSaveStatementOptions } from "@/lib/dataGridSql";
import type { DataGridSavePreparation } from "./tauri";
// ---------------------------------------------------------------------------
// Helpers
@@ -409,6 +411,10 @@ export async function analyzeEditableQueryEditability(sql: string): Promise<Quer
return post("/api/query/analyze-editability", { sql });
}
export async function prepareDataGridSave(options: DataGridSaveStatementOptions): Promise<DataGridSavePreparation> {
return post("/api/query/prepare-data-grid-save", { options });
}
// ---------------------------------------------------------------------------
// AI
// ---------------------------------------------------------------------------
+12
View File
@@ -22,6 +22,7 @@ import type {
} from "@/types/database";
import type { AiConfig } from "@/stores/settingsStore";
import type { QueryEditability } from "@/lib/sqlAnalysis";
import type { DataGridSaveStatementOptions } from "@/lib/dataGridSql";
export interface AgentDriverInfo {
db_type: string;
@@ -317,6 +318,17 @@ export async function analyzeEditableQueryEditability(sql: string): Promise<Quer
return invoke("analyze_editable_query_editability", { sql });
}
export interface DataGridSavePreparation {
validationError?: string;
statements: string[];
rollbackStatements: string[];
executionSchema?: string;
}
export async function prepareDataGridSave(options: DataGridSaveStatementOptions): Promise<DataGridSavePreparation> {
return invoke("prepare_data_grid_save", { options });
}
export async function listIndexes(
connectionId: string,
database: string,