feat(grid): copy insert without primary keys

This commit is contained in:
t8y2
2026-05-22 11:55:08 +08:00
parent c1fc1c9b70
commit 66d180f729
7 changed files with 152 additions and 31 deletions
@@ -2155,7 +2155,9 @@ const {
copyCell,
copyRow,
copyRowAsInsert,
copyRowAsInsertWithoutPrimaryKeys,
copyRowAsUpdate,
canCopyRowAsInsertWithoutPrimaryKeys,
canCopyRowAsUpdate,
copyAll,
copySelectionTsv,
@@ -2179,8 +2181,6 @@ const {
selectedRange,
contextCell: exportContextCell,
getRowItem: (rowId: number) => visibleDisplayItems.value.find((item) => item.id === rowId),
quoteIdent,
escapeVal,
selectedRowIds,
hasRowSelection,
});
@@ -4473,6 +4473,13 @@ defineExpose({
<ContextMenuItem @click="copyRowAsInsert">
{{ isMultiRow ? t("grid.copyRowsInsert", { count: multiRowCount }) : t("grid.copyRowInsert") }}
</ContextMenuItem>
<ContextMenuItem v-if="canCopyRowAsInsertWithoutPrimaryKeys" @click="copyRowAsInsertWithoutPrimaryKeys">
{{
isMultiRow
? t("grid.copyRowsInsertWithoutPrimaryKeys", { count: multiRowCount })
: t("grid.copyRowInsertWithoutPrimaryKeys")
}}
</ContextMenuItem>
<ContextMenuItem v-if="canCopyRowAsUpdate" @click="copyRowAsUpdate">
{{ isMultiRow ? t("grid.copyRowsUpdate", { count: multiRowCount }) : t("grid.copyRowUpdate") }}
</ContextMenuItem>
@@ -14,7 +14,7 @@ import { useToast } from "@/composables/useToast";
import { displayCellValue, type CellValue } from "@/lib/cellValue";
import { tryStartExclusiveActivation, type ActionActivationGuard } from "@/lib/actionActivation";
import { copyToClipboard } from "@/lib/clipboard";
import { buildDataGridCopyUpdateStatements } from "@/lib/dataGridSql";
import { buildDataGridCopyInsertStatement, buildDataGridCopyUpdateStatements } from "@/lib/dataGridSql";
import type { DatabaseType } from "@/types/database";
interface RowItem {
@@ -42,8 +42,6 @@ export interface UseDataGridExportOptions {
| Ref<{ rowId: number; rowIndex: number; col: number } | null>
| ComputedRef<{ rowId: number; rowIndex: number; col: number } | null>;
getRowItem: (rowId: number) => RowItem | undefined;
quoteIdent: (name: string) => string;
escapeVal: (value: CellValue) => string;
selectedRowIds: Ref<Set<number>> | ComputedRef<Set<number>>;
hasRowSelection: ComputedRef<boolean>;
}
@@ -65,8 +63,6 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
selectedRange,
contextCell,
getRowItem,
quoteIdent,
escapeVal,
selectedRowIds,
hasRowSelection,
} = options;
@@ -168,32 +164,29 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
await copyText(JSON.stringify(obj, null, 2));
}
function insertEligibleRows(): RowItem[] {
return targetedRows();
}
async function copyRowAsInsertStatement(excludePrimaryKeys: boolean) {
const statement = buildDataGridCopyInsertStatement({
databaseType: databaseType.value,
tableMeta: tableMeta.value,
columns: columns.value,
sourceColumns: sourceColumns.value,
rows: insertEligibleRows().map((item) => item.data),
excludePrimaryKeys,
});
if (!statement) return;
await copyText(statement);
}
async function copyRowAsInsert() {
const table = tableMeta.value
? (tableMeta.value.schema ? `${quoteIdent(tableMeta.value.schema)}.` : "") + quoteIdent(tableMeta.value.tableName)
: "table_name";
const cols = columns.value.map((c) => quoteIdent(c)).join(", ");
await copyRowAsInsertStatement(false);
}
if (hasRowSelection.value && selectedRowIds.value.size > 0) {
const items = displayItems.value.filter((item) => selectedRowIds.value.has(item.id));
const valueRows = items.map((item) => `(${item.data.map((v) => escapeVal(v)).join(", ")})`);
await copyText(`INSERT INTO ${table} (${cols}) VALUES\n${valueRows.join(",\n")};`);
return;
}
const range = selectedRange.value;
if (range && range.startRow !== range.endRow) {
const items = displayItems.value.slice(range.startRow, range.endRow + 1);
const valueRows = items.map((item) => `(${item.data.map((v) => escapeVal(v)).join(", ")})`);
await copyText(`INSERT INTO ${table} (${cols}) VALUES\n${valueRows.join(",\n")};`);
return;
}
if (!contextCell.value) return;
const item = getRowItem(contextCell.value.rowId);
if (!item) return;
const vals = item.data.map((v) => escapeVal(v)).join(", ");
await copyText(`INSERT INTO ${table} (${cols}) VALUES (${vals});`);
async function copyRowAsInsertWithoutPrimaryKeys() {
await copyRowAsInsertStatement(true);
}
async function copyRowAsUpdate() {
@@ -224,6 +217,20 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
);
});
const canCopyRowAsInsertWithoutPrimaryKeys = computed(() => {
if (!tableMeta.value?.primaryKeys.length) return false;
const rows = insertEligibleRows();
if (!rows.length) return false;
return !!buildDataGridCopyInsertStatement({
databaseType: databaseType.value,
tableMeta: tableMeta.value,
columns: columns.value,
sourceColumns: sourceColumns.value,
rows: [rows[0].data],
excludePrimaryKeys: true,
});
});
async function copyAll() {
const header = columns.value.join("\t");
const body = displayItems.value.map((item) => item.data.map((c) => displayCellValue(c)).join("\t")).join("\n");
@@ -370,7 +377,9 @@ export function useDataGridExport(options: UseDataGridExportOptions) {
copyCell,
copyRow,
copyRowAsInsert,
copyRowAsInsertWithoutPrimaryKeys,
copyRowAsUpdate,
canCopyRowAsInsertWithoutPrimaryKeys,
canCopyRowAsUpdate,
copyAll,
copySelectionTsv,
+2
View File
@@ -255,6 +255,7 @@ export default {
copyCell: "Copy Cell",
copyRow: "Copy Row (JSON)",
copyRowInsert: "Copy as INSERT",
copyRowInsertWithoutPrimaryKeys: "Copy as INSERT without Primary Keys",
copyRowUpdate: "Copy as UPDATE",
copyAll: "Copy All (TSV)",
selection: "Selection",
@@ -351,6 +352,7 @@ export default {
restoreRows: "Restore {count} Rows",
copyRows: "Copy {count} Rows (JSON)",
copyRowsInsert: "Copy {count} Rows as INSERT",
copyRowsInsertWithoutPrimaryKeys: "Copy {count} Rows as INSERT without Primary Keys",
copyRowsUpdate: "Copy {count} Rows as UPDATE",
selectedRows: "{count} rows selected",
restoreRow: "Restore Row",
+2
View File
@@ -250,6 +250,7 @@ export default {
copyCell: "Copiar celda",
copyRow: "Copiar fila (JSON)",
copyRowInsert: "Copiar como INSERT",
copyRowInsertWithoutPrimaryKeys: "Copiar como INSERT sin claves primarias",
copyRowUpdate: "Copiar como UPDATE",
copyAll: "Copiar todo (TSV)",
selection: "Selección",
@@ -319,6 +320,7 @@ export default {
restoreRows: "Restaurar {count} filas",
copyRows: "Copiar {count} filas (JSON)",
copyRowsInsert: "Copiar {count} filas como INSERT",
copyRowsInsertWithoutPrimaryKeys: "Copiar {count} filas como INSERT sin claves primarias",
copyRowsUpdate: "Copiar {count} filas como UPDATE",
selectedRows: "{count} filas seleccionadas",
restoreRow: "Restaurar fila",
+2
View File
@@ -253,6 +253,7 @@ export default {
copyCell: "复制单元格",
copyRow: "复制行 (JSON)",
copyRowInsert: "复制为 INSERT 语句",
copyRowInsertWithoutPrimaryKeys: "复制为 INSERT 语句(不含主键)",
copyRowUpdate: "复制为 UPDATE 语句",
copyAll: "复制全部 (TSV)",
selection: "选区",
@@ -348,6 +349,7 @@ export default {
restoreRows: "恢复 {count} 行",
copyRows: "复制 {count} 行 (JSON)",
copyRowsInsert: "复制 {count} 行为 INSERT",
copyRowsInsertWithoutPrimaryKeys: "复制 {count} 行为 INSERT(不含主键)",
copyRowsUpdate: "复制 {count} 行为 UPDATE",
selectedRows: "已选 {count} 行",
restoreRow: "恢复行",
+46
View File
@@ -45,6 +45,15 @@ export interface DataGridCopyUpdateStatementOptions {
rows: GridCellValue[][];
}
export interface DataGridCopyInsertStatementOptions {
databaseType?: DatabaseType;
tableMeta?: DataGridTableMeta;
columns: string[];
sourceColumns?: Array<string | undefined>;
rows: GridCellValue[][];
excludePrimaryKeys?: boolean;
}
export interface DataGridSaveValidationOptions {
databaseType?: DatabaseType;
tableMeta?: DataGridTableMeta;
@@ -249,6 +258,43 @@ export function buildDataGridCopyUpdateStatements(options: DataGridCopyUpdateSta
return statements;
}
export function buildDataGridCopyInsertStatement(options: DataGridCopyInsertStatementOptions): string | undefined {
const saveColumns = effectiveColumns(options.sourceColumns, options.columns);
const columnInfo = columnInfoByName(options.tableMeta?.columns);
const primaryKeySet = new Set(
(options.tableMeta?.primaryKeys ?? []).map((primaryKey) => normalizeColumnName(primaryKey)),
);
const insertableColumns = saveColumns
.map((column, index) => ({ column, index }))
.filter((entry): entry is { column: string; index: number } => !!entry.column)
.filter((entry) => !isOracleRowId(options.databaseType, entry.column));
const insertColumns = insertableColumns.filter(
(entry) => !options.excludePrimaryKeys || !primaryKeySet.has(normalizeColumnName(entry.column)),
);
if (options.excludePrimaryKeys && insertColumns.length === insertableColumns.length) return undefined;
if (insertColumns.length === 0 || options.rows.length === 0) return undefined;
const table = options.tableMeta
? qualifiedTableName({
databaseType: options.databaseType,
schema: options.tableMeta.schema,
tableName: options.tableMeta.tableName,
})
: "table_name";
const columns = insertColumns.map((entry) => quoteIdent(options.databaseType, entry.column)).join(", ");
const valueRows = options.rows.map(
(row) =>
`(${insertColumns
.map(({ column, index }) =>
formatGridSqlLiteral(row[index], options.databaseType, columnInfo.get(normalizeColumnName(column))),
)
.join(", ")})`,
);
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[] = [];