fix(grid): reuse prepared row copy statements
This commit is contained in:
parent
ae4bbe1564
commit
f7eec00c8e
|
|
@ -6185,9 +6185,8 @@ const {
|
|||
copyRowAsInsert,
|
||||
copyRowAsInsertWithoutPrimaryKeys,
|
||||
prefetchRowAsInsertStatement,
|
||||
canCopyPreparedInsert,
|
||||
canCopyRowAsInsert,
|
||||
prefetchRowAsUpdateStatement,
|
||||
canCopyPreparedUpdate,
|
||||
copyRowAsUpdate,
|
||||
canCopyRowAsInsertWithoutPrimaryKeys,
|
||||
canCopyRowAsUpdate,
|
||||
|
|
@ -8359,33 +8358,33 @@ function copySubmenu(): ContextMenuItem {
|
|||
items.push({ label: singleRowSelected ? t("grid.copySelectedRowTsvWithHeaders") : t("grid.copySelectedRowsTsvWithHeaders", { count: selectedRowCount.value }), action: copySelectedRowsTsvWithHeaders });
|
||||
}
|
||||
if (isMultiRow.value) {
|
||||
items.push({ label: labels.insertMerged, action: () => copyRowAsInsert("merged"), disabled: !canCopyPreparedInsert(false, "merged") });
|
||||
items.push({ label: labels.insertRowByRow, action: () => copyRowAsInsert("row-by-row"), disabled: !canCopyPreparedInsert(false, "row-by-row") });
|
||||
items.push({ label: labels.insertMerged, action: () => copyRowAsInsert("merged"), disabled: !canCopyRowAsInsert.value });
|
||||
items.push({ label: labels.insertRowByRow, action: () => copyRowAsInsert("row-by-row"), disabled: !canCopyRowAsInsert.value });
|
||||
} else {
|
||||
items.push({ label: labels.insert, action: () => copyRowAsInsert(), disabled: !canCopyPreparedInsert(false) });
|
||||
items.push({ label: labels.insert, action: () => copyRowAsInsert(), disabled: !canCopyRowAsInsert.value });
|
||||
}
|
||||
if (canCopyRowAsInsertWithoutPrimaryKeys.value) {
|
||||
if (isMultiRow.value) {
|
||||
items.push({
|
||||
label: labels.insertNoPkMerged,
|
||||
action: () => copyRowAsInsertWithoutPrimaryKeys("merged"),
|
||||
disabled: !canCopyPreparedInsert(true, "merged"),
|
||||
disabled: !canCopyRowAsInsertWithoutPrimaryKeys.value,
|
||||
});
|
||||
items.push({
|
||||
label: labels.insertNoPkRowByRow,
|
||||
action: () => copyRowAsInsertWithoutPrimaryKeys("row-by-row"),
|
||||
disabled: !canCopyPreparedInsert(true, "row-by-row"),
|
||||
disabled: !canCopyRowAsInsertWithoutPrimaryKeys.value,
|
||||
});
|
||||
} else {
|
||||
items.push({
|
||||
label: labels.insertNoPk,
|
||||
action: () => copyRowAsInsertWithoutPrimaryKeys(),
|
||||
disabled: !canCopyPreparedInsert(true),
|
||||
disabled: !canCopyRowAsInsertWithoutPrimaryKeys.value,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (canCopyRowAsUpdate.value) {
|
||||
items.push({ label: labels.update, action: copyRowAsUpdate, disabled: !canCopyPreparedUpdate() });
|
||||
items.push({ label: labels.update, action: copyRowAsUpdate });
|
||||
}
|
||||
items.push({ label: t("grid.copyAll"), action: copyAll });
|
||||
items.push({ label: t("grid.copyColumnNames"), action: copyColumnNames });
|
||||
|
|
|
|||
|
|
@ -0,0 +1,169 @@
|
|||
import { computed, ref } from "vue";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { useDataGridExport, type UseDataGridExportOptions } from "@/composables/useDataGridExport";
|
||||
import { buildDataGridCopyInsertStatement, buildDataGridCopyUpdateStatements } from "@/lib/dataGrid/dataGridSql";
|
||||
import { copyToClipboard } from "@/lib/common/clipboard";
|
||||
import type { DataGridTableMeta } from "@/lib/dataGrid/dataGridSql";
|
||||
|
||||
const toast = vi.fn();
|
||||
|
||||
vi.mock("vue-i18n", () => ({
|
||||
useI18n: () => ({ t: (key: string, params?: { message?: string }) => (params?.message ? `${key}: ${params.message}` : key) }),
|
||||
}));
|
||||
|
||||
vi.mock("@/composables/useToast", () => ({
|
||||
useToast: () => ({ toast }),
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/common/clipboard", () => ({
|
||||
copyToClipboard: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/dataGrid/dataGridSql", async (importOriginal) => {
|
||||
const original = await importOriginal<typeof import("@/lib/dataGrid/dataGridSql")>();
|
||||
return {
|
||||
...original,
|
||||
buildDataGridCopyInsertStatement: vi.fn(),
|
||||
buildDataGridCopyUpdateStatements: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
interface Deferred<T> {
|
||||
promise: Promise<T>;
|
||||
resolve: (value: T) => void;
|
||||
reject: (error: unknown) => void;
|
||||
}
|
||||
|
||||
function deferred<T>(): Deferred<T> {
|
||||
let resolve!: (value: T) => void;
|
||||
let reject!: (error: unknown) => void;
|
||||
const promise = new Promise<T>((resolvePromise, rejectPromise) => {
|
||||
resolve = resolvePromise;
|
||||
reject = rejectPromise;
|
||||
});
|
||||
return { promise, resolve, reject };
|
||||
}
|
||||
|
||||
function row(data: unknown[]) {
|
||||
return {
|
||||
id: 1,
|
||||
data,
|
||||
isNew: false,
|
||||
isDeleted: false,
|
||||
isDirtyCol: data.map(() => false),
|
||||
status: "",
|
||||
};
|
||||
}
|
||||
|
||||
function createExportState(tableMeta: DataGridTableMeta, columns = tableMeta.columns?.map((column) => column.name) ?? ["id", "name"]) {
|
||||
const item = row(columns.map((column, index) => (column === "id" ? 1 : `value-${index}`)));
|
||||
const options: UseDataGridExportOptions = {
|
||||
columns: computed(() => columns),
|
||||
displayItems: computed(() => [item]),
|
||||
sql: computed(() => undefined),
|
||||
tableMeta: computed(() => tableMeta),
|
||||
databaseType: computed(() => "mysql"),
|
||||
connectionId: computed(() => "connection-1"),
|
||||
database: computed(() => "dbx"),
|
||||
context: computed(() => "table-data"),
|
||||
sourceColumns: computed(() => columns),
|
||||
columnTypes: computed(() => columns.map(() => "varchar")),
|
||||
whereInput: computed(() => undefined),
|
||||
orderBy: computed(() => undefined),
|
||||
exportBatchSize: computed(() => 1000),
|
||||
hasCellSelection: computed(() => false),
|
||||
selectedCells: computed(() => ({ columns: [], rows: [] })),
|
||||
selectedRange: computed(() => null),
|
||||
contextCell: ref({ rowId: item.id, rowIndex: 0, col: -1 }),
|
||||
getRowItem: (rowId) => (rowId === item.id ? item : undefined),
|
||||
selectedRowIds: ref(new Set<number>()),
|
||||
hasRowSelection: computed(() => false),
|
||||
};
|
||||
return useDataGridExport(options);
|
||||
}
|
||||
|
||||
const editableTable: DataGridTableMeta = {
|
||||
tableName: "users",
|
||||
primaryKeys: ["id"],
|
||||
columns: [
|
||||
{ name: "id", data_type: "int", is_nullable: false, is_primary_key: true },
|
||||
{ name: "name", data_type: "varchar", is_nullable: false },
|
||||
],
|
||||
};
|
||||
|
||||
describe("useDataGridExport prepared row statements", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("reuses an in-flight INSERT prefetch when the copy action runs", async () => {
|
||||
const pending = deferred<string | undefined>();
|
||||
vi.mocked(buildDataGridCopyInsertStatement).mockReturnValueOnce(pending.promise);
|
||||
const state = createExportState(editableTable);
|
||||
|
||||
const prefetch = state.prefetchRowAsInsertStatement(false);
|
||||
const copy = state.copyRowAsInsert();
|
||||
await vi.waitFor(() => expect(buildDataGridCopyInsertStatement).toHaveBeenCalledTimes(1));
|
||||
pending.resolve("INSERT INTO users VALUES (1, 'Alice');");
|
||||
|
||||
await Promise.all([prefetch, copy]);
|
||||
expect(copyToClipboard).toHaveBeenCalledWith("INSERT INTO users VALUES (1, 'Alice');");
|
||||
});
|
||||
|
||||
it("reuses an in-flight UPDATE prefetch on the first copy action", async () => {
|
||||
const pending = deferred<string[]>();
|
||||
vi.mocked(buildDataGridCopyUpdateStatements).mockReturnValueOnce(pending.promise);
|
||||
const state = createExportState(editableTable);
|
||||
|
||||
const prefetch = state.prefetchRowAsUpdateStatement();
|
||||
const copy = state.copyRowAsUpdate();
|
||||
await vi.waitFor(() => expect(buildDataGridCopyUpdateStatements).toHaveBeenCalledTimes(1));
|
||||
pending.resolve(["UPDATE users SET name = 'Alice' WHERE id = 1;"]);
|
||||
|
||||
await Promise.all([prefetch, copy]);
|
||||
expect(copyToClipboard).toHaveBeenCalledWith("UPDATE users SET name = 'Alice' WHERE id = 1;");
|
||||
});
|
||||
|
||||
it.each(["GENERATED ALWAYS AS (1)", "IDENTITY(1, 1)"])("disables copy-as-insert when every result column is non-insertable (%s)", (extra) => {
|
||||
const state = createExportState(
|
||||
{
|
||||
tableName: "generated_values",
|
||||
primaryKeys: [],
|
||||
columns: [{ name: "computed_value", data_type: "int", is_nullable: true, extra }],
|
||||
},
|
||||
["computed_value"],
|
||||
);
|
||||
|
||||
expect(state.canCopyRowAsInsert.value).toBe(false);
|
||||
});
|
||||
|
||||
it("reports a shared builder failure when the user invokes copy", async () => {
|
||||
const pending = deferred<string | undefined>();
|
||||
vi.mocked(buildDataGridCopyInsertStatement).mockReturnValueOnce(pending.promise);
|
||||
const state = createExportState(editableTable);
|
||||
|
||||
const prefetch = state.prefetchRowAsInsertStatement(false);
|
||||
const copy = state.copyRowAsInsert();
|
||||
await vi.waitFor(() => expect(buildDataGridCopyInsertStatement).toHaveBeenCalledTimes(1));
|
||||
pending.reject(new Error("builder unavailable"));
|
||||
|
||||
await Promise.all([prefetch, copy]);
|
||||
expect(toast).toHaveBeenCalledWith("grid.copyFailed: builder unavailable", 5000);
|
||||
expect(copyToClipboard).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("reports an UPDATE builder failure from the first copy action", async () => {
|
||||
const pending = deferred<string[]>();
|
||||
vi.mocked(buildDataGridCopyUpdateStatements).mockReturnValueOnce(pending.promise);
|
||||
const state = createExportState(editableTable);
|
||||
|
||||
const prefetch = state.prefetchRowAsUpdateStatement();
|
||||
const copy = state.copyRowAsUpdate();
|
||||
await vi.waitFor(() => expect(buildDataGridCopyUpdateStatements).toHaveBeenCalledTimes(1));
|
||||
pending.reject(new Error("update builder unavailable"));
|
||||
|
||||
await Promise.all([prefetch, copy]);
|
||||
expect(toast).toHaveBeenCalledWith("grid.copyFailed: update builder unavailable", 5000);
|
||||
expect(copyToClipboard).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
@ -15,6 +15,7 @@ import { expandNestedJsonStringsForCopy } from "@/lib/common/jsonCopyValue";
|
|||
import { buildMongoCopyInsertDocument, formatMongoShellLiteral, type MongoInputValue } from "@/lib/mongo/mongoDocumentValues";
|
||||
import type { DatabaseType, QueryResult } from "@/types/database";
|
||||
import type { QueryResultExportRequest } from "@/lib/backend/api";
|
||||
import { DBX_ROWID_COLUMN } from "@/lib/table/tableEditing";
|
||||
|
||||
interface RowItem {
|
||||
id: number;
|
||||
|
|
@ -90,6 +91,7 @@ interface CopyStatementCache {
|
|||
text: string;
|
||||
loading: boolean;
|
||||
ready: boolean;
|
||||
promise?: Promise<string | undefined>;
|
||||
}
|
||||
|
||||
export function useDataGridExport(options: UseDataGridExportOptions) {
|
||||
|
|
@ -276,6 +278,14 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
|
|||
}
|
||||
|
||||
async function prefetchRowAsInsertStatement(excludePrimaryKeys: boolean, insertMode: DataGridCopyInsertMode = "merged") {
|
||||
try {
|
||||
await prepareRowAsInsertStatement(excludePrimaryKeys, insertMode);
|
||||
} catch {
|
||||
// Prefetch failures are reported only if the user invokes the copy action.
|
||||
}
|
||||
}
|
||||
|
||||
async function prepareRowAsInsertStatement(excludePrimaryKeys: boolean, insertMode: DataGridCopyInsertMode = "merged"): Promise<string | undefined> {
|
||||
const rows = insertEligibleRows();
|
||||
if (!rows.length) {
|
||||
setInsertCopyCache(excludePrimaryKeys, insertMode, {
|
||||
|
|
@ -288,16 +298,10 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
|
|||
}
|
||||
const key = insertCopyKey(excludePrimaryKeys, insertMode);
|
||||
const current = insertCopyCache(excludePrimaryKeys, insertMode);
|
||||
if ((current.loading || current.ready) && current.key === key) return;
|
||||
if (current.ready && current.key === key) return current.text;
|
||||
if (current.loading && current.key === key && current.promise) return current.promise;
|
||||
|
||||
setInsertCopyCache(excludePrimaryKeys, insertMode, {
|
||||
key,
|
||||
text: "",
|
||||
loading: true,
|
||||
ready: false,
|
||||
});
|
||||
|
||||
try {
|
||||
const promise = Promise.resolve().then(async () => {
|
||||
const statement =
|
||||
databaseType.value === "mongodb"
|
||||
? buildMongoCopyInsertStatement({
|
||||
|
|
@ -319,22 +323,37 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
|
|||
insertMode,
|
||||
});
|
||||
const latest = insertCopyCache(excludePrimaryKeys, insertMode);
|
||||
if (latest.key !== key) return;
|
||||
if (latest.key !== key || latest.promise !== promise) return undefined;
|
||||
setInsertCopyCache(excludePrimaryKeys, insertMode, {
|
||||
key,
|
||||
text: statement ?? "",
|
||||
loading: false,
|
||||
ready: !!statement,
|
||||
});
|
||||
} catch {
|
||||
return statement;
|
||||
});
|
||||
|
||||
setInsertCopyCache(excludePrimaryKeys, insertMode, {
|
||||
key,
|
||||
text: "",
|
||||
loading: true,
|
||||
ready: false,
|
||||
promise,
|
||||
});
|
||||
|
||||
try {
|
||||
return await promise;
|
||||
} catch (error) {
|
||||
const latest = insertCopyCache(excludePrimaryKeys, insertMode);
|
||||
if (latest.key !== key) return;
|
||||
setInsertCopyCache(excludePrimaryKeys, insertMode, {
|
||||
key,
|
||||
text: "",
|
||||
loading: false,
|
||||
ready: false,
|
||||
});
|
||||
if (latest.key === key && latest.promise === promise) {
|
||||
setInsertCopyCache(excludePrimaryKeys, insertMode, {
|
||||
key,
|
||||
text: "",
|
||||
loading: false,
|
||||
ready: false,
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -343,14 +362,29 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
|
|||
return cache.ready && cache.key === insertCopyKey(excludePrimaryKeys, insertMode);
|
||||
}
|
||||
|
||||
function copyPreparedRowAsInsert(excludePrimaryKeys: boolean, insertMode: DataGridCopyInsertMode = "merged"): boolean {
|
||||
if (!canCopyPreparedInsert(excludePrimaryKeys, insertMode)) return false;
|
||||
void copyText(insertCopyCache(excludePrimaryKeys, insertMode).text);
|
||||
return true;
|
||||
async function copyPreparedRowAsInsert(excludePrimaryKeys: boolean, insertMode: DataGridCopyInsertMode = "merged"): Promise<boolean> {
|
||||
try {
|
||||
const statement = await prepareRowAsInsertStatement(excludePrimaryKeys, insertMode);
|
||||
if (!statement) return false;
|
||||
await copyText(statement);
|
||||
return true;
|
||||
} catch (error: any) {
|
||||
toast(t("grid.copyFailed", { message: error?.message || String(error) }), 5000);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function prefetchRowAsUpdateStatement() {
|
||||
if (!tableMeta.value?.primaryKeys.length) {
|
||||
try {
|
||||
await prepareRowAsUpdateStatement();
|
||||
} catch {
|
||||
// Prefetch failures are reported only if the user invokes the copy action.
|
||||
}
|
||||
}
|
||||
|
||||
async function prepareRowAsUpdateStatement(): Promise<string | undefined> {
|
||||
const currentTableMeta = tableMeta.value;
|
||||
if (!currentTableMeta?.primaryKeys.length) {
|
||||
setUpdateCopyCache({
|
||||
key: "",
|
||||
text: "",
|
||||
|
|
@ -371,25 +405,19 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
|
|||
}
|
||||
const key = updateCopyKey();
|
||||
const current = copyRowUpdateCache.value;
|
||||
if ((current.loading || current.ready) && current.key === key) return;
|
||||
if (current.ready && current.key === key) return current.text;
|
||||
if (current.loading && current.key === key && current.promise) return current.promise;
|
||||
|
||||
setUpdateCopyCache({
|
||||
key,
|
||||
text: "",
|
||||
loading: true,
|
||||
ready: false,
|
||||
});
|
||||
|
||||
try {
|
||||
const promise = Promise.resolve().then(async () => {
|
||||
const statements = await buildDataGridCopyUpdateStatements({
|
||||
databaseType: databaseType.value,
|
||||
tableMeta: tableMeta.value,
|
||||
tableMeta: currentTableMeta,
|
||||
columns: columns.value,
|
||||
sourceColumns: sourceColumns.value,
|
||||
rows: rows.map((item) => item.data),
|
||||
});
|
||||
const latest = copyRowUpdateCache.value;
|
||||
if (latest.key !== key) return;
|
||||
if (latest.key !== key || latest.promise !== promise) return undefined;
|
||||
const text = statements.join("\n");
|
||||
setUpdateCopyCache({
|
||||
key,
|
||||
|
|
@ -397,15 +425,30 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
|
|||
loading: false,
|
||||
ready: statements.length > 0,
|
||||
});
|
||||
} catch {
|
||||
return text || undefined;
|
||||
});
|
||||
|
||||
setUpdateCopyCache({
|
||||
key,
|
||||
text: "",
|
||||
loading: true,
|
||||
ready: false,
|
||||
promise,
|
||||
});
|
||||
|
||||
try {
|
||||
return await promise;
|
||||
} catch (error) {
|
||||
const latest = copyRowUpdateCache.value;
|
||||
if (latest.key !== key) return;
|
||||
setUpdateCopyCache({
|
||||
key,
|
||||
text: "",
|
||||
loading: false,
|
||||
ready: false,
|
||||
});
|
||||
if (latest.key === key && latest.promise === promise) {
|
||||
setUpdateCopyCache({
|
||||
key,
|
||||
text: "",
|
||||
loading: false,
|
||||
ready: false,
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -414,10 +457,16 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
|
|||
return cache.ready && cache.key === updateCopyKey();
|
||||
}
|
||||
|
||||
function copyPreparedRowAsUpdate(): boolean {
|
||||
if (!canCopyPreparedUpdate()) return false;
|
||||
void copyText(copyRowUpdateCache.value.text);
|
||||
return true;
|
||||
async function copyPreparedRowAsUpdate(): Promise<boolean> {
|
||||
try {
|
||||
const statement = await prepareRowAsUpdateStatement();
|
||||
if (!statement) return false;
|
||||
await copyText(statement);
|
||||
return true;
|
||||
} catch (error: any) {
|
||||
toast(t("grid.copyFailed", { message: error?.message || String(error) }), 5000);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Selection copy functions ---
|
||||
|
|
@ -512,15 +561,15 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
|
|||
}
|
||||
|
||||
async function copyRowAsInsert(insertMode: DataGridCopyInsertMode = "merged") {
|
||||
copyPreparedRowAsInsert(false, insertMode);
|
||||
await copyPreparedRowAsInsert(false, insertMode);
|
||||
}
|
||||
|
||||
async function copyRowAsInsertWithoutPrimaryKeys(insertMode: DataGridCopyInsertMode = "merged") {
|
||||
copyPreparedRowAsInsert(true, insertMode);
|
||||
await copyPreparedRowAsInsert(true, insertMode);
|
||||
}
|
||||
|
||||
async function copyRowAsUpdate() {
|
||||
copyPreparedRowAsUpdate();
|
||||
await copyPreparedRowAsUpdate();
|
||||
}
|
||||
|
||||
const canCopyRowAsUpdate = computed(() => {
|
||||
|
|
@ -535,14 +584,19 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
|
|||
return saveColumns.some((column) => column && !primaryKeySet.has(normalizeColumnName(column)));
|
||||
});
|
||||
|
||||
function insertableCopyColumnCount(excludePrimaryKeys: boolean): number {
|
||||
const primaryKeySet = new Set((tableMeta.value?.primaryKeys ?? []).map(normalizeColumnName));
|
||||
return effectiveColumns(sourceColumns.value, columns.value).filter((column): column is string => !!column && !isCopyInsertOmittedColumn(databaseType.value, column, tableMeta.value) && (!excludePrimaryKeys || !primaryKeySet.has(normalizeColumnName(column)))).length;
|
||||
}
|
||||
|
||||
const canCopyRowAsInsert = computed(() => insertEligibleRows().length > 0 && insertableCopyColumnCount(false) > 0);
|
||||
|
||||
const canCopyRowAsInsertWithoutPrimaryKeys = computed(() => {
|
||||
if (!tableMeta.value?.primaryKeys.length) return false;
|
||||
const rows = insertEligibleRows();
|
||||
if (!rows.length) return false;
|
||||
const saveColumns = effectiveColumns(sourceColumns.value, columns.value);
|
||||
const primaryKeySet = new Set(tableMeta.value.primaryKeys.map(normalizeColumnName));
|
||||
const insertableCount = saveColumns.filter(Boolean).length;
|
||||
const insertColumnsCount = saveColumns.filter((column) => column && !primaryKeySet.has(normalizeColumnName(column))).length;
|
||||
const insertableCount = insertableCopyColumnCount(false);
|
||||
const insertColumnsCount = insertableCopyColumnCount(true);
|
||||
return insertColumnsCount > 0 && insertColumnsCount < insertableCount;
|
||||
});
|
||||
|
||||
|
|
@ -1108,6 +1162,7 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
|
|||
prefetchRowAsInsertStatement,
|
||||
canCopyPreparedInsert,
|
||||
copyPreparedRowAsInsert,
|
||||
canCopyRowAsInsert,
|
||||
prefetchRowAsUpdateStatement,
|
||||
canCopyPreparedUpdate,
|
||||
copyPreparedRowAsUpdate,
|
||||
|
|
@ -1213,6 +1268,15 @@ function effectiveColumns(sourceColumns: Array<string | undefined> | undefined,
|
|||
return sourceColumns;
|
||||
}
|
||||
|
||||
function isCopyInsertOmittedColumn(databaseType: DatabaseType | undefined, column: string, tableMeta: DataGridTableMeta | undefined): boolean {
|
||||
if (databaseType === "oracle" && column.toUpperCase() === DBX_ROWID_COLUMN) return true;
|
||||
const columnInfo = tableMeta?.columns?.find((item) => normalizeColumnName(item.name) === normalizeColumnName(column));
|
||||
const normalizedType = columnInfo?.data_type.trim().replace(/^"|"$/g, "").toLowerCase();
|
||||
if (databaseType === "postgres" && (normalizedType === "tsvector" || normalizedType?.endsWith(".tsvector"))) return true;
|
||||
const extra = columnInfo?.extra?.toLowerCase() ?? "";
|
||||
return /\b(auto_increment|autoincrement|identity)\b/.test(extra) || (extra.includes("generated always as") && !extra.includes("identity"));
|
||||
}
|
||||
|
||||
function findColumnIndex(columns: Array<string | undefined>, target: string): number {
|
||||
const normalizedTarget = normalizeColumnName(target);
|
||||
return columns.findIndex((column) => (column ? normalizeColumnName(column) : "") === normalizedTarget);
|
||||
|
|
|
|||
Loading…
Reference in New Issue