feat(structure): add configurable preset fields for new tables
This commit is contained in:
parent
cc2fa698d2
commit
529454e6d1
|
|
@ -111,6 +111,8 @@ const setupRequired = ref(false);
|
|||
const showConnectionDialog = ref(false);
|
||||
const connectionDialogPrefill = ref<ConnectionDeepLinkDraft | null>(null);
|
||||
const showSettingsDialog = ref(false);
|
||||
const settingsInitialTab = ref("editor");
|
||||
const settingsInitialSection = ref<string | undefined>(undefined);
|
||||
const showQueryEditorDdlDialog = ref(false);
|
||||
const showDriverStore = ref(false);
|
||||
const showQuickOpen = ref(false);
|
||||
|
|
@ -232,6 +234,12 @@ useVisibilityChange();
|
|||
const appVersion = ref("");
|
||||
const isClassicLayout = computed(() => settingsStore.editorSettings.appLayout === "classic");
|
||||
const updateNotificationsEnabled = computed(() => settingsStore.editorSettings.updateNotificationsEnabled);
|
||||
|
||||
function openSettings(initialTab = "editor", initialSection?: string) {
|
||||
settingsInitialTab.value = initialTab;
|
||||
settingsInitialSection.value = initialSection;
|
||||
showSettingsDialog.value = true;
|
||||
}
|
||||
const toolbarAgentDriverUpdateCount = computed(() => (updateNotificationsEnabled.value ? agentDriverUpdateCount.value : 0));
|
||||
const toolbarHasUpdateAvailable = computed(() => updateNotificationsEnabled.value && hasUpdateAvailable.value);
|
||||
const hasSqlFileConnections = computed(() => connectionStore.connections.some((c) => supportsSqlFileExecution(c.db_type)));
|
||||
|
|
@ -1140,7 +1148,7 @@ function handleKeydown(e: KeyboardEvent) {
|
|||
if (isOpenSettingsShortcut(e, shortcuts)) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
showSettingsDialog.value = true;
|
||||
openSettings();
|
||||
return;
|
||||
}
|
||||
if (isQuickOpenShortcut(e, shortcuts)) {
|
||||
|
|
@ -1427,7 +1435,7 @@ onUnmounted(() => {
|
|||
@toggle-history="showHistory = !showHistory"
|
||||
@toggle-sql-library="toggleSqlLibrary"
|
||||
@open-github="openGitHub"
|
||||
@open-settings="showSettingsDialog = true"
|
||||
@open-settings="openSettings()"
|
||||
@open-driver-store="showDriverStore = !showDriverStore"
|
||||
@check-updates="checkUpdates()"
|
||||
@open-transfer="dialogs.showTransferDialog.value = true"
|
||||
|
|
@ -1530,6 +1538,7 @@ onUnmounted(() => {
|
|||
)
|
||||
"
|
||||
@structure-editor-close="activeTab && queryStore.closeTab(activeTab.id)"
|
||||
@open-settings="openSettings"
|
||||
/>
|
||||
</KeepAlive>
|
||||
</div>
|
||||
|
|
@ -1576,6 +1585,8 @@ onUnmounted(() => {
|
|||
:show-connection-dialog="showConnectionDialog"
|
||||
:connection-prefill="connectionDialogPrefill"
|
||||
:show-settings-dialog="showSettingsDialog"
|
||||
:settings-initial-tab="settingsInitialTab"
|
||||
:settings-initial-section="settingsInitialSection"
|
||||
:app-version="appVersion"
|
||||
:show-danger-dialog="showDangerDialog"
|
||||
:danger-sql="dangerSql"
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { ref, watch, shallowRef, computed, onMounted, onUnmounted, nextTick } fr
|
|||
import type { Ref } from "vue";
|
||||
import type { EditorView as EditorViewType } from "@codemirror/view";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { AlertTriangle, CheckCircle2, CircleHelp, Cloud, Copy, Download, ExternalLink, Loader2, Moon, PackageSearch, Pencil, RefreshCw, RotateCcw, Settings, Sun, SunMoon, Terminal, Trash2, Upload, X } from "@lucide/vue";
|
||||
import { AlertTriangle, CheckCircle2, CircleHelp, Cloud, Copy, Download, ExternalLink, GripVertical, Loader2, Moon, PackageSearch, Pencil, RefreshCw, RotateCcw, Settings, Sun, SunMoon, Terminal, Trash2, Upload, X } from "@lucide/vue";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
|
|
@ -62,7 +62,9 @@ import { eventToShortcut } from "@/lib/keyboardShortcuts";
|
|||
import { SHORTCUT_DEFINITIONS, findShortcutConflict, normalizeShortcutSettings, type ShortcutActionId } from "@/lib/shortcutRegistry";
|
||||
import { normalizeSidebarHiddenTablePrefixes } from "@/lib/sidebarTableNameDisplay";
|
||||
import { normalizeSqlFormatterSettings, type SqlFormatterSettings } from "@/lib/sqlFormatterConfig";
|
||||
import type { SqlSnippet } from "@/types/database";
|
||||
import { EMPTY_TABLE_COLUMN_TEMPLATE_DATA_TYPE, parseTableColumnTemplateFields, TABLE_COLUMN_TEMPLATE_DATABASE_TYPES } from "@/lib/tableColumnTemplates";
|
||||
import { combineDataTypeForDatabase, getDataTypeOptions, getDefaultLengthForType, isDataTypeLengthDisabled, splitDataType } from "@/lib/tableStructureEditorState";
|
||||
import type { DatabaseType, SqlSnippet } from "@/types/database";
|
||||
import { uuid } from "@/lib/utils";
|
||||
import { DEFAULT_SQL_SNIPPETS } from "@/lib/sqlCompletion";
|
||||
import AiProviderLogo from "@/components/icons/AiProviderLogo.vue";
|
||||
|
|
@ -86,6 +88,7 @@ let pendingSystemFonts: Promise<string[]> | null = null;
|
|||
const props = defineProps<{
|
||||
open: boolean;
|
||||
initialTab?: string;
|
||||
initialSection?: string;
|
||||
appVersion?: string;
|
||||
}>();
|
||||
|
||||
|
|
@ -93,6 +96,76 @@ const emit = defineEmits<{
|
|||
"update:open": [value: boolean];
|
||||
}>();
|
||||
|
||||
interface TableColumnTemplateOverrideRow {
|
||||
id: string;
|
||||
databaseType: DatabaseType;
|
||||
dataType: string;
|
||||
}
|
||||
|
||||
interface TableColumnTemplateGridRow {
|
||||
id: string;
|
||||
name: string;
|
||||
defaultValue: string;
|
||||
required: boolean;
|
||||
comment: string;
|
||||
overrides: TableColumnTemplateOverrideRow[];
|
||||
}
|
||||
|
||||
function tableColumnTemplateRowsFromSettings(lines: readonly string[]): TableColumnTemplateGridRow[] {
|
||||
return parseTableColumnTemplateFields([...lines]).map((field) => ({
|
||||
id: uuid(),
|
||||
name: field.name,
|
||||
defaultValue: field.defaultValue ?? "",
|
||||
required: !(field.isNullable ?? false),
|
||||
comment: field.comment ?? "",
|
||||
overrides: Object.entries(field.dataTypesByDatabase).map(([databaseType, dataType]) => ({
|
||||
id: uuid(),
|
||||
databaseType: databaseType as DatabaseType,
|
||||
dataType: dataType === EMPTY_TABLE_COLUMN_TEMPLATE_DATA_TYPE ? "" : dataType,
|
||||
})),
|
||||
}));
|
||||
}
|
||||
|
||||
function tableColumnTemplateRowsToSettings(rows: readonly TableColumnTemplateGridRow[]): string[] {
|
||||
const seenNames = new Set<string>();
|
||||
const settings: string[] = [];
|
||||
for (const row of rows) {
|
||||
const name = row.name.trim();
|
||||
if (!name) continue;
|
||||
const key = name.toLowerCase();
|
||||
if (seenNames.has(key)) continue;
|
||||
seenNames.add(key);
|
||||
|
||||
const parts = [name];
|
||||
|
||||
const seenDatabaseTypes = new Set<DatabaseType>();
|
||||
for (const override of row.overrides) {
|
||||
const dataType = override.dataType.trim();
|
||||
if (seenDatabaseTypes.has(override.databaseType)) continue;
|
||||
seenDatabaseTypes.add(override.databaseType);
|
||||
parts.push(`${override.databaseType}:${dataType || EMPTY_TABLE_COLUMN_TEMPLATE_DATA_TYPE}`);
|
||||
}
|
||||
if (!row.required) parts.push("required:false");
|
||||
const defaultValue = row.defaultValue.trim();
|
||||
if (defaultValue) parts.push(`default:${defaultValue}`);
|
||||
const comment = row.comment.trim();
|
||||
if (comment) parts.push(`comment:${comment}`);
|
||||
settings.push(parts.join(" | "));
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
function createEmptyTableColumnTemplateRow(): TableColumnTemplateGridRow {
|
||||
return {
|
||||
id: uuid(),
|
||||
name: "",
|
||||
defaultValue: "",
|
||||
required: true,
|
||||
comment: "",
|
||||
overrides: [],
|
||||
};
|
||||
}
|
||||
|
||||
// Local edit state
|
||||
const editFontFamily = ref(settingsStore.editorSettings.fontFamily);
|
||||
const editFontSize = ref(settingsStore.editorSettings.fontSize);
|
||||
|
|
@ -120,6 +193,11 @@ const editShowColumnTypesInHeader = ref(settingsStore.editorSettings.showColumnT
|
|||
const editCompactColumnHeaderActions = ref(settingsStore.editorSettings.compactColumnHeaderActions);
|
||||
const editInfiniteScroll = ref(settingsStore.editorSettings.infiniteScroll);
|
||||
const editInfiniteScrollMaxRows = ref(settingsStore.editorSettings.infiniteScrollMaxRows);
|
||||
const editTableColumnTemplateRows = ref<TableColumnTemplateGridRow[]>(tableColumnTemplateRowsFromSettings(settingsStore.editorSettings.tableColumnTemplateFields));
|
||||
const editTableColumnTemplateDatabaseType = ref<DatabaseType>(TABLE_COLUMN_TEMPLATE_DATABASE_TYPES[0] ?? "mysql");
|
||||
const tableColumnTemplateSectionRef = ref<HTMLElement | null>(null);
|
||||
const draggedTableColumnTemplateRowId = ref<string | null>(null);
|
||||
let tableColumnTemplatePointerDragCleanup: (() => void) | null = null;
|
||||
const editRedisScanPageSize = ref(settingsStore.editorSettings.redisScanPageSize);
|
||||
const editShortcuts = ref(normalizeShortcutSettings(settingsStore.editorSettings.shortcuts));
|
||||
const editSqlFormatter = ref<SqlFormatterSettings>(normalizeSqlFormatterSettings(settingsStore.editorSettings.sqlFormatter));
|
||||
|
|
@ -157,6 +235,13 @@ const disconnectTabHandlingModeDescriptionKey = computed(() => {
|
|||
|
||||
return "disconnectTabHandlingModeCloseTabsDescription";
|
||||
});
|
||||
const normalizedEditTableColumnTemplateFields = computed(() => tableColumnTemplateRowsToSettings(editTableColumnTemplateRows.value));
|
||||
const visibleTableColumnTemplateRows = computed(() =>
|
||||
editTableColumnTemplateRows.value.filter((row) => {
|
||||
if (row.overrides.length === 0) return true;
|
||||
return row.overrides.some((override) => override.databaseType === editTableColumnTemplateDatabaseType.value);
|
||||
}),
|
||||
);
|
||||
|
||||
// --- Snippet state ---
|
||||
const editSnippets = ref<SqlSnippet[]>(settingsStore.editorSettings.snippets.map((s) => ({ ...s })));
|
||||
|
|
@ -383,6 +468,7 @@ watch(
|
|||
editCompactColumnHeaderActions.value = settingsStore.editorSettings.compactColumnHeaderActions;
|
||||
editInfiniteScroll.value = settingsStore.editorSettings.infiniteScroll;
|
||||
editInfiniteScrollMaxRows.value = settingsStore.editorSettings.infiniteScrollMaxRows;
|
||||
editTableColumnTemplateRows.value = tableColumnTemplateRowsFromSettings(settingsStore.editorSettings.tableColumnTemplateFields);
|
||||
editRedisScanPageSize.value = settingsStore.editorSettings.redisScanPageSize;
|
||||
editShortcuts.value = normalizeShortcutSettings(settingsStore.editorSettings.shortcuts);
|
||||
editSqlFormatter.value = normalizeSqlFormatterSettings(settingsStore.editorSettings.sqlFormatter);
|
||||
|
|
@ -445,6 +531,7 @@ function hasChanges(): boolean {
|
|||
editCompactColumnHeaderActions.value !== settingsStore.editorSettings.compactColumnHeaderActions ||
|
||||
editInfiniteScroll.value !== settingsStore.editorSettings.infiniteScroll ||
|
||||
editInfiniteScrollMaxRows.value !== settingsStore.editorSettings.infiniteScrollMaxRows ||
|
||||
JSON.stringify(normalizedEditTableColumnTemplateFields.value) !== JSON.stringify(settingsStore.editorSettings.tableColumnTemplateFields) ||
|
||||
editRedisScanPageSize.value !== settingsStore.editorSettings.redisScanPageSize ||
|
||||
JSON.stringify(editShortcuts.value) !== JSON.stringify(settingsStore.editorSettings.shortcuts) ||
|
||||
JSON.stringify(editSqlFormatter.value) !== JSON.stringify(normalizeSqlFormatterSettings(settingsStore.editorSettings.sqlFormatter)) ||
|
||||
|
|
@ -487,6 +574,7 @@ async function persistSettings() {
|
|||
compactColumnHeaderActions: editCompactColumnHeaderActions.value,
|
||||
infiniteScroll: editInfiniteScroll.value,
|
||||
infiniteScrollMaxRows: editInfiniteScrollMaxRows.value,
|
||||
tableColumnTemplateFields: normalizedEditTableColumnTemplateFields.value,
|
||||
redisScanPageSize: editRedisScanPageSize.value,
|
||||
shortcuts: editShortcuts.value,
|
||||
sqlFormatter: normalizeSqlFormatterSettings(editSqlFormatter.value),
|
||||
|
|
@ -570,6 +658,7 @@ function resetDefaultsForTab(tab: SettingsCategory) {
|
|||
editCompactColumnHeaderActions.value = DEFAULT_EDITOR_SETTINGS.compactColumnHeaderActions;
|
||||
editInfiniteScroll.value = DEFAULT_EDITOR_SETTINGS.infiniteScroll;
|
||||
editInfiniteScrollMaxRows.value = DEFAULT_EDITOR_SETTINGS.infiniteScrollMaxRows;
|
||||
editTableColumnTemplateRows.value = tableColumnTemplateRowsFromSettings(DEFAULT_EDITOR_SETTINGS.tableColumnTemplateFields);
|
||||
editExportBatchSize.value = DEFAULT_EDITOR_SETTINGS.exportBatchSize;
|
||||
editExportRowLimitEnabled.value = DEFAULT_EDITOR_SETTINGS.exportRowLimitEnabled;
|
||||
editExportRowLimit.value = DEFAULT_EDITOR_SETTINGS.exportRowLimit;
|
||||
|
|
@ -607,6 +696,7 @@ function resetAllDefaults() {
|
|||
editCompactColumnHeaderActions.value = DEFAULT_EDITOR_SETTINGS.compactColumnHeaderActions;
|
||||
editInfiniteScroll.value = DEFAULT_EDITOR_SETTINGS.infiniteScroll;
|
||||
editInfiniteScrollMaxRows.value = DEFAULT_EDITOR_SETTINGS.infiniteScrollMaxRows;
|
||||
editTableColumnTemplateRows.value = tableColumnTemplateRowsFromSettings(DEFAULT_EDITOR_SETTINGS.tableColumnTemplateFields);
|
||||
editRedisScanPageSize.value = DEFAULT_EDITOR_SETTINGS.redisScanPageSize;
|
||||
editShortcuts.value = normalizeShortcutSettings(DEFAULT_EDITOR_SETTINGS.shortcuts);
|
||||
editSqlFormatter.value = normalizeSqlFormatterSettings(DEFAULT_EDITOR_SETTINGS.sqlFormatter);
|
||||
|
|
@ -628,6 +718,130 @@ function resetAllDefaults() {
|
|||
editSnippets.value = DEFAULT_SQL_SNIPPETS.map((s) => ({ ...s }));
|
||||
}
|
||||
|
||||
function addTableColumnTemplateRow() {
|
||||
const row = createEmptyTableColumnTemplateRow();
|
||||
row.overrides.push({
|
||||
id: uuid(),
|
||||
databaseType: editTableColumnTemplateDatabaseType.value,
|
||||
dataType: "",
|
||||
});
|
||||
editTableColumnTemplateRows.value.push(row);
|
||||
}
|
||||
|
||||
function removeTableColumnTemplateRow(id: string) {
|
||||
const row = editTableColumnTemplateRows.value.find((item) => item.id === id);
|
||||
if (!row) return;
|
||||
if (row.overrides.some((override) => override.databaseType === editTableColumnTemplateDatabaseType.value)) {
|
||||
row.overrides = row.overrides.filter((override) => override.databaseType !== editTableColumnTemplateDatabaseType.value);
|
||||
if (row.overrides.length > 0) return;
|
||||
}
|
||||
editTableColumnTemplateRows.value = editTableColumnTemplateRows.value.filter((item) => item.id !== id);
|
||||
}
|
||||
|
||||
function moveTableColumnTemplateRow(sourceId: string, targetId: string, placement: "before" | "after") {
|
||||
if (!sourceId || sourceId === targetId) return;
|
||||
const rows = [...editTableColumnTemplateRows.value];
|
||||
const sourceIndex = rows.findIndex((row) => row.id === sourceId);
|
||||
const targetIndex = rows.findIndex((row) => row.id === targetId);
|
||||
if (sourceIndex === -1 || targetIndex === -1) return;
|
||||
const [source] = rows.splice(sourceIndex, 1);
|
||||
if (!source) return;
|
||||
const nextTargetIndex = rows.findIndex((row) => row.id === targetId);
|
||||
const insertIndex = placement === "after" ? nextTargetIndex + 1 : nextTargetIndex;
|
||||
rows.splice(nextTargetIndex === -1 ? rows.length : insertIndex, 0, source);
|
||||
editTableColumnTemplateRows.value = rows;
|
||||
}
|
||||
|
||||
function cleanupTableColumnTemplatePointerDrag() {
|
||||
tableColumnTemplatePointerDragCleanup?.();
|
||||
tableColumnTemplatePointerDragCleanup = null;
|
||||
draggedTableColumnTemplateRowId.value = null;
|
||||
document.body.style.cursor = "";
|
||||
document.body.style.userSelect = "";
|
||||
}
|
||||
|
||||
function startTableColumnTemplateRowDrag(id: string, event: PointerEvent) {
|
||||
if (event.button !== 0) return;
|
||||
event.preventDefault();
|
||||
cleanupTableColumnTemplatePointerDrag();
|
||||
draggedTableColumnTemplateRowId.value = id;
|
||||
document.body.style.cursor = "grabbing";
|
||||
document.body.style.userSelect = "none";
|
||||
|
||||
const onPointerMove = (moveEvent: PointerEvent) => {
|
||||
const sourceId = draggedTableColumnTemplateRowId.value;
|
||||
if (!sourceId) return;
|
||||
const targetRow = document.elementFromPoint(moveEvent.clientX, moveEvent.clientY)?.closest<HTMLElement>("[data-table-column-template-row-id]");
|
||||
const targetId = targetRow?.dataset.tableColumnTemplateRowId;
|
||||
if (!targetRow || !targetId || targetId === sourceId) return;
|
||||
const rect = targetRow.getBoundingClientRect();
|
||||
moveTableColumnTemplateRow(sourceId, targetId, moveEvent.clientY > rect.top + rect.height / 2 ? "after" : "before");
|
||||
};
|
||||
const onPointerUp = () => cleanupTableColumnTemplatePointerDrag();
|
||||
|
||||
window.addEventListener("pointermove", onPointerMove);
|
||||
window.addEventListener("pointerup", onPointerUp, { once: true });
|
||||
window.addEventListener("pointercancel", onPointerUp, { once: true });
|
||||
tableColumnTemplatePointerDragCleanup = () => {
|
||||
window.removeEventListener("pointermove", onPointerMove);
|
||||
window.removeEventListener("pointerup", onPointerUp);
|
||||
window.removeEventListener("pointercancel", onPointerUp);
|
||||
};
|
||||
}
|
||||
|
||||
function tableColumnTemplateTypeOptions(databaseType: DatabaseType): string[] {
|
||||
return getDataTypeOptions(databaseType);
|
||||
}
|
||||
|
||||
function tableColumnTemplateDataTypeForSelectedDatabase(row: TableColumnTemplateGridRow): string {
|
||||
return row.overrides.find((override) => override.databaseType === editTableColumnTemplateDatabaseType.value)?.dataType ?? "";
|
||||
}
|
||||
|
||||
function tableColumnTemplateBaseTypeForSelectedDatabase(row: TableColumnTemplateGridRow): string {
|
||||
return splitDataType(tableColumnTemplateDataTypeForSelectedDatabase(row)).baseType;
|
||||
}
|
||||
|
||||
function tableColumnTemplateLengthForSelectedDatabase(row: TableColumnTemplateGridRow): string {
|
||||
return splitDataType(tableColumnTemplateDataTypeForSelectedDatabase(row)).params;
|
||||
}
|
||||
|
||||
function setTableColumnTemplateDataTypeForSelectedDatabase(row: TableColumnTemplateGridRow, value: string) {
|
||||
const dataType = value.trim();
|
||||
const databaseType = editTableColumnTemplateDatabaseType.value;
|
||||
const existing = row.overrides.find((override) => override.databaseType === databaseType);
|
||||
if (!dataType) {
|
||||
row.overrides = row.overrides.filter((override) => override.databaseType !== databaseType);
|
||||
return;
|
||||
}
|
||||
if (existing) {
|
||||
existing.dataType = dataType;
|
||||
} else {
|
||||
row.overrides.push({ id: uuid(), databaseType, dataType });
|
||||
}
|
||||
}
|
||||
|
||||
function setTableColumnTemplateBaseTypeForSelectedDatabase(row: TableColumnTemplateGridRow, value: string) {
|
||||
const baseType = value.trim();
|
||||
if (!baseType) {
|
||||
setTableColumnTemplateDataTypeForSelectedDatabase(row, "");
|
||||
return;
|
||||
}
|
||||
const databaseType = editTableColumnTemplateDatabaseType.value;
|
||||
setTableColumnTemplateDataTypeForSelectedDatabase(row, combineDataTypeForDatabase(databaseType, baseType, getDefaultLengthForType(databaseType, baseType)));
|
||||
}
|
||||
|
||||
function setTableColumnTemplateLengthForSelectedDatabase(row: TableColumnTemplateGridRow, value: string) {
|
||||
const databaseType = editTableColumnTemplateDatabaseType.value;
|
||||
const baseType = tableColumnTemplateBaseTypeForSelectedDatabase(row);
|
||||
if (!baseType || isDataTypeLengthDisabled(databaseType, baseType)) return;
|
||||
setTableColumnTemplateDataTypeForSelectedDatabase(row, combineDataTypeForDatabase(databaseType, baseType, value));
|
||||
}
|
||||
|
||||
function isTableColumnTemplateLengthDisabled(row: TableColumnTemplateGridRow): boolean {
|
||||
const baseType = tableColumnTemplateBaseTypeForSelectedDatabase(row);
|
||||
return !baseType || isDataTypeLengthDisabled(editTableColumnTemplateDatabaseType.value, baseType);
|
||||
}
|
||||
|
||||
function onExecuteModeChange(v: any) {
|
||||
if (v === "all" || v === "current") editExecuteMode.value = v;
|
||||
}
|
||||
|
|
@ -1133,6 +1347,13 @@ const passwordMessage = ref("");
|
|||
const passwordError = ref(false);
|
||||
const changingPassword = ref(false);
|
||||
|
||||
async function scrollToInitialSettingsSection() {
|
||||
await nextTick();
|
||||
if (props.initialSection === "tableColumnTemplates") {
|
||||
tableColumnTemplateSectionRef.value?.scrollIntoView({ block: "center", behavior: "smooth" });
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.open,
|
||||
async (open) => {
|
||||
|
|
@ -1154,11 +1375,19 @@ watch(
|
|||
syncAiEditState();
|
||||
if (!isWeb && activeSettingsTab.value === "mcp") void refreshMcpStatus();
|
||||
if (!isWeb && activeSettingsTab.value === "ai" && aiIsCodexCli.value) void ensureCodexMcpStatus();
|
||||
await scrollToInitialSettingsSection();
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.initialSection,
|
||||
() => {
|
||||
if (props.open) void scrollToInitialSettingsSection();
|
||||
},
|
||||
);
|
||||
|
||||
watch([webdavEndpoint, webdavUsername], () => {
|
||||
void refreshWebDavPasswordStatus();
|
||||
});
|
||||
|
|
@ -1192,6 +1421,7 @@ onUnmounted(() => {
|
|||
window.clearInterval(webdavAutoUploadTimer);
|
||||
webdavAutoUploadTimer = undefined;
|
||||
}
|
||||
cleanupTableColumnTemplatePointerDrag();
|
||||
cleanupTruncationObservers();
|
||||
});
|
||||
|
||||
|
|
@ -2324,6 +2554,98 @@ watch(
|
|||
<Switch id="query-export-keyset-enabled" v-model="editQueryExportKeysetOptimizationEnabled" class="mt-0.5" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div class="space-y-3">
|
||||
<div class="text-sm font-medium text-muted-foreground">{{ t("settings.tableStructureSection") }}</div>
|
||||
<div ref="tableColumnTemplateSectionRef" class="space-y-2 rounded-md border bg-muted/20 px-3 py-2">
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="space-y-1">
|
||||
<Label>{{ t("settings.tableColumnTemplateFields") }}</Label>
|
||||
<p class="text-xs text-muted-foreground">{{ t("settings.tableColumnTemplateFieldsDescription") }}</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Select v-model="editTableColumnTemplateDatabaseType">
|
||||
<SelectTrigger class="h-8 w-44 px-2 text-xs">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent class="max-h-72">
|
||||
<SelectItem v-for="dbType in TABLE_COLUMN_TEMPLATE_DATABASE_TYPES" :key="dbType" :value="dbType">
|
||||
{{ dbType }}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Button type="button" size="sm" variant="outline" @click="addTableColumnTemplateRow">
|
||||
{{ t("settings.tableColumnTemplateAdd") }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overflow-x-auto rounded-md border bg-background">
|
||||
<table class="w-full min-w-[900px] border-separate border-spacing-0 text-xs">
|
||||
<thead class="bg-muted/50 text-muted-foreground">
|
||||
<tr>
|
||||
<th class="w-8 border-b px-2 py-1.5" />
|
||||
<th class="border-b px-2 py-1.5 text-left font-medium">{{ t("settings.tableColumnTemplateColumn") }}</th>
|
||||
<th class="border-b px-2 py-1.5 text-left font-medium">{{ t("settings.tableColumnTemplateType") }}</th>
|
||||
<th class="border-b px-2 py-1.5 text-left font-medium">{{ t("settings.tableColumnTemplateLength") }}</th>
|
||||
<th class="border-b px-2 py-1.5 text-left font-medium">{{ t("settings.tableColumnTemplateDefault") }}</th>
|
||||
<th class="border-b px-2 py-1.5 text-left font-medium">{{ t("settings.tableColumnTemplateRequired") }}</th>
|
||||
<th class="border-b px-2 py-1.5 text-left font-medium">{{ t("settings.tableColumnTemplateComment") }}</th>
|
||||
<th class="w-10 border-b px-2 py-1.5" />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="row in visibleTableColumnTemplateRows" :key="row.id" :data-table-column-template-row-id="row.id" :class="draggedTableColumnTemplateRowId === row.id ? 'opacity-60' : ''">
|
||||
<td class="border-b px-2 py-1.5 align-middle">
|
||||
<button
|
||||
type="button"
|
||||
class="flex h-7 w-6 cursor-grab touch-none items-center justify-center rounded text-muted-foreground hover:bg-muted hover:text-foreground active:cursor-grabbing"
|
||||
:aria-label="t('settings.tableColumnTemplateDragHandle')"
|
||||
@pointerdown="startTableColumnTemplateRowDrag(row.id, $event)"
|
||||
>
|
||||
<GripVertical class="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</td>
|
||||
<td class="border-b px-2 py-1.5">
|
||||
<Input v-model="row.name" class="h-7 px-2 text-xs" />
|
||||
</td>
|
||||
<td class="border-b px-2 py-1.5">
|
||||
<SearchableSelect
|
||||
:model-value="tableColumnTemplateBaseTypeForSelectedDatabase(row)"
|
||||
:options="tableColumnTemplateTypeOptions(editTableColumnTemplateDatabaseType)"
|
||||
:placeholder="t('settings.tableColumnTemplateNoPresetType')"
|
||||
:search-placeholder="t('structureEditor.typePlaceholder')"
|
||||
:empty-text="t('structureEditor.noMatchingType')"
|
||||
:loading-text="t('common.loading')"
|
||||
:allow-custom="true"
|
||||
:trigger-class="['h-7 w-full px-2 font-mono text-xs']"
|
||||
@update:model-value="setTableColumnTemplateBaseTypeForSelectedDatabase(row, $event)"
|
||||
/>
|
||||
</td>
|
||||
<td class="border-b px-2 py-1.5">
|
||||
<Input :model-value="tableColumnTemplateLengthForSelectedDatabase(row)" class="h-7 w-28 px-2 font-mono text-xs" :disabled="isTableColumnTemplateLengthDisabled(row)" @update:model-value="setTableColumnTemplateLengthForSelectedDatabase(row, String($event))" />
|
||||
</td>
|
||||
<td class="border-b px-2 py-1.5">
|
||||
<Input v-model="row.defaultValue" class="h-7 px-2 font-mono text-xs" />
|
||||
</td>
|
||||
<td class="border-b px-2 py-1.5">
|
||||
<Switch v-model="row.required" />
|
||||
</td>
|
||||
<td class="border-b px-2 py-1.5">
|
||||
<Input v-model="row.comment" class="h-7 px-2 text-xs" />
|
||||
</td>
|
||||
<td class="border-b px-2 py-1.5 text-right">
|
||||
<Button type="button" variant="ghost" size="icon" class="h-7 w-7" @click="removeTableColumnTemplateRow(row.id)">
|
||||
<X class="h-3.5 w-3.5" />
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section v-else-if="activeSettingsTab === 'redis'" class="flex flex-col gap-5 py-2">
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ const props = defineProps<{
|
|||
connectionPrefill?: ConnectionDeepLinkDraft | null;
|
||||
showSettingsDialog: boolean;
|
||||
settingsInitialTab?: string;
|
||||
settingsInitialSection?: string;
|
||||
appVersion?: string;
|
||||
showDangerDialog: boolean;
|
||||
dangerSql: string;
|
||||
|
|
@ -107,7 +108,7 @@ watch(
|
|||
@connect-failed="emit('connectFailed', $event)"
|
||||
@open-driver-store="emit('openDriverStore')"
|
||||
/>
|
||||
<EditorSettingsDialog v-if="showSettingsDialog" :open="showSettingsDialog" :initial-tab="settingsInitialTab || 'editor'" :app-version="appVersion" @update:open="emit('update:showSettingsDialog', $event)" />
|
||||
<EditorSettingsDialog v-if="showSettingsDialog" :open="showSettingsDialog" :initial-tab="settingsInitialTab || 'editor'" :initial-section="settingsInitialSection" :app-version="appVersion" @update:open="emit('update:showSettingsDialog', $event)" />
|
||||
<DangerConfirmDialog
|
||||
v-if="showDangerDialog"
|
||||
:open="showDangerDialog"
|
||||
|
|
|
|||
|
|
@ -132,6 +132,7 @@ const emit = defineEmits<{
|
|||
objectSchemaChange: [schema: string | undefined];
|
||||
structureEditorSaved: [commentChanged: boolean];
|
||||
structureEditorClose: [];
|
||||
openSettings: [initialTab?: string, initialSection?: string];
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
|
@ -1106,6 +1107,7 @@ defineExpose({ focusSearch, refreshData, handleModRTarget, requestQueryEditorExe
|
|||
:table-name="activeTab.structureTableName || ''"
|
||||
@saved="(commentChanged) => emit('structureEditorSaved', commentChanged)"
|
||||
@close="emit('structureEditorClose')"
|
||||
@open-settings="(initialTab, initialSection) => emit('openSettings', initialTab, initialSection)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { Button } from "@/components/ui/button";
|
|||
import { Input } from "@/components/ui/input";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { AlertTriangle, Check, ChevronDown, ChevronUp, Copy, Database, Info, KeyRound, Loader2, Maximize2, Plus, RefreshCw, Save, SlidersHorizontal, Trash2, X } from "@lucide/vue";
|
||||
import { AlertTriangle, Check, ChevronDown, ChevronUp, Copy, Database, Info, KeyRound, Loader2, Maximize2, Plus, RefreshCw, Save, Settings, SlidersHorizontal, Trash2, X } from "@lucide/vue";
|
||||
import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
|
|
@ -22,6 +22,7 @@ import { type SqlHighlighter, createShikiSqlHighlighter } from "@/lib/sqlHighlig
|
|||
import { copyToClipboard } from "@/lib/clipboard";
|
||||
import { queryTimeoutSecsForConnection } from "@/lib/queryTimeout";
|
||||
import { type EditableStructureColumn, type EditableStructureForeignKey, type EditableStructureIndex, type EditableStructureTrigger } from "@/lib/tableStructureEditorSql";
|
||||
import { PRESET_FIELDS_TEMPLATE_ID, createTableColumnTemplateDrafts } from "@/lib/tableColumnTemplates";
|
||||
import { getTableMetadataCapabilities } from "@/lib/tableMetadataCapabilities";
|
||||
import { canAddTableStructureColumn, getTableStructureCapabilities } from "@/lib/tableStructureCapabilities";
|
||||
import { connectionObjectTreeQuerySchema, tableStructureDatabaseTypeForConnection } from "@/lib/jdbcDialect";
|
||||
|
|
@ -80,6 +81,7 @@ const props = defineProps<{
|
|||
const emit = defineEmits<{
|
||||
saved: [commentChanged: boolean];
|
||||
close: [];
|
||||
openSettings: [initialTab?: string, initialSection?: string];
|
||||
}>();
|
||||
|
||||
const activeTab = ref("columns");
|
||||
|
|
@ -822,6 +824,20 @@ async function addColumn() {
|
|||
input?.select();
|
||||
}
|
||||
|
||||
function applyColumnTemplate(templateId: string) {
|
||||
if (!canAddColumn.value) return;
|
||||
activeTab.value = "columns";
|
||||
const templateColumns = createTableColumnTemplateDrafts({
|
||||
templateId,
|
||||
databaseType: databaseType.value,
|
||||
columnNames: settingsStore.editorSettings.tableColumnTemplateFields,
|
||||
existingColumnNames: columns.value.map((column) => column.name),
|
||||
createId: uuid,
|
||||
});
|
||||
if (!templateColumns.length) return;
|
||||
columns.value.push(...templateColumns);
|
||||
}
|
||||
|
||||
function removeNewColumn(column: EditableStructureColumn) {
|
||||
columns.value = columns.value.filter((item) => item.id !== column.id);
|
||||
}
|
||||
|
|
@ -1354,6 +1370,18 @@ watch(activeTab, (tab) => {
|
|||
<Plus :class="structureIconClass" />
|
||||
{{ t("structureEditor.addColumn") }}
|
||||
</Button>
|
||||
<Button v-if="isCreateMode && activeTab === 'columns'" size="sm" variant="outline" :class="structureToolbarButtonClass" :disabled="!canAddColumn" @click="applyColumnTemplate(PRESET_FIELDS_TEMPLATE_ID)">
|
||||
<Copy :class="structureIconClass" />
|
||||
{{ t("structureEditor.columnTemplates") }}
|
||||
</Button>
|
||||
<Tooltip v-if="isCreateMode && activeTab === 'columns'">
|
||||
<TooltipTrigger as-child>
|
||||
<Button size="sm" variant="ghost" :class="structureToolbarButtonClass" :disabled="!canAddColumn" :aria-label="t('structureEditor.configureColumnTemplates')" @click="emit('openSettings', 'data', 'tableColumnTemplates')">
|
||||
<Settings :class="structureIconClass" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{{ t("structureEditor.configureColumnTemplates") }}</TooltipContent>
|
||||
</Tooltip>
|
||||
<Button v-if="activeTab === 'indexes'" size="sm" :class="structureToolbarButtonClass" :disabled="!structureCapabilities.createIndex || indexesLoading" @click="addIndex">
|
||||
<Plus :class="structureIconClass" />
|
||||
{{ t("structureEditor.addIndex") }}
|
||||
|
|
|
|||
|
|
@ -1458,6 +1458,9 @@ export default {
|
|||
triggers: "Triggers",
|
||||
ddl: "DDL",
|
||||
addColumn: "Add Column",
|
||||
columnTemplates: "Insert Preset Fields",
|
||||
configureColumnTemplates: "Configure Preset Fields",
|
||||
presetFieldsTemplate: "Preset Field Set",
|
||||
addIndex: "Add Index",
|
||||
addForeignKey: "Add Foreign Key",
|
||||
addTrigger: "Add Trigger",
|
||||
|
|
@ -2407,6 +2410,7 @@ export default {
|
|||
debugLogsDownload: "Download logs",
|
||||
debugLogsDownloaded: "Downloaded",
|
||||
debugLogsClear: "Clear logs",
|
||||
tableStructureSection: "Table Structure",
|
||||
dataGridDisplay: "Data grid display",
|
||||
showColumnCommentsInHeader: "Show column comments under names",
|
||||
showColumnCommentsInHeaderDescription: "Display table column comments directly below grid column names.",
|
||||
|
|
@ -2418,6 +2422,18 @@ export default {
|
|||
infiniteScrollDescription: "Automatically load the next page of data when scrolling to the bottom of the table.",
|
||||
infiniteScrollMaxRows: "Infinite scroll max rows",
|
||||
infiniteScrollMaxRowsDescription: "Maximum number of rows to load in infinite scroll mode (1000–50000).",
|
||||
tableColumnTemplateFields: "New Table Preset Fields",
|
||||
tableColumnTemplateFieldsDescription: "Choose a database type, then configure the preset field types used when creating new tables.",
|
||||
tableColumnTemplateAdd: "Add Field",
|
||||
tableColumnTemplateColumn: "Field",
|
||||
tableColumnTemplateDatabase: "Database",
|
||||
tableColumnTemplateType: "Type",
|
||||
tableColumnTemplateLength: "Length",
|
||||
tableColumnTemplateDefault: "Default",
|
||||
tableColumnTemplateNoPresetType: "No preset type",
|
||||
tableColumnTemplateRequired: "Required",
|
||||
tableColumnTemplateComment: "Description",
|
||||
tableColumnTemplateDragHandle: "Drag to reorder",
|
||||
sidebarActivation: "Sidebar activation",
|
||||
sidebarActivationSingle: "Single click",
|
||||
sidebarActivationSingleDescription: "Open actionable sidebar items with one click.",
|
||||
|
|
|
|||
|
|
@ -1214,6 +1214,9 @@ export default {
|
|||
triggers: "Disparadores",
|
||||
ddl: "DDL",
|
||||
addColumn: "Agregar columna",
|
||||
columnTemplates: "Insertar campos predefinidos",
|
||||
configureColumnTemplates: "Configurar campos predefinidos",
|
||||
presetFieldsTemplate: "Conjunto de campos predefinidos",
|
||||
addIndex: "Agregar índice",
|
||||
columnName: "Columna",
|
||||
dataType: "Tipo",
|
||||
|
|
@ -2020,6 +2023,7 @@ export default {
|
|||
debugLogsDownload: "Descargar logs",
|
||||
debugLogsDownloaded: "Descargado",
|
||||
debugLogsClear: "Borrar logs",
|
||||
tableStructureSection: "Estructura de tabla",
|
||||
dataGridDisplay: "Visualización de la tabla",
|
||||
showColumnCommentsInHeader: "Mostrar comentarios bajo los nombres",
|
||||
showColumnCommentsInHeaderDescription: "Muestra los comentarios de columnas directamente debajo del nombre de la columna.",
|
||||
|
|
@ -2031,6 +2035,18 @@ export default {
|
|||
infiniteScrollDescription: "Carga automáticamente la siguiente página de datos al desplazarse hasta el final de la tabla.",
|
||||
infiniteScrollMaxRows: "Máximo de filas en desplazamiento infinito",
|
||||
infiniteScrollMaxRowsDescription: "Número máximo de filas a cargar en modo desplazamiento infinito (1000–50000).",
|
||||
tableColumnTemplateFields: "Campos predefinidos para tablas nuevas",
|
||||
tableColumnTemplateFieldsDescription: "Elige un tipo de base de datos y configura los tipos de campo predefinidos usados al crear tablas nuevas.",
|
||||
tableColumnTemplateAdd: "Agregar campo",
|
||||
tableColumnTemplateColumn: "Campo",
|
||||
tableColumnTemplateDatabase: "Base de datos",
|
||||
tableColumnTemplateType: "Tipo",
|
||||
tableColumnTemplateLength: "Longitud",
|
||||
tableColumnTemplateDefault: "Valor por defecto",
|
||||
tableColumnTemplateNoPresetType: "Sin tipo predefinido",
|
||||
tableColumnTemplateRequired: "Obligatorio",
|
||||
tableColumnTemplateComment: "Descripción",
|
||||
tableColumnTemplateDragHandle: "Arrastrar para reordenar",
|
||||
sidebarActivation: "Activación de la barra lateral",
|
||||
sidebarActivationSingle: "Un clic",
|
||||
sidebarActivationSingleDescription: "Abrir elementos accionables de la barra lateral con un clic.",
|
||||
|
|
|
|||
|
|
@ -1338,6 +1338,9 @@ export default {
|
|||
triggers: "Trigger",
|
||||
ddl: "DDL",
|
||||
addColumn: "Aggiungi Colonna",
|
||||
columnTemplates: "Inserisci campi predefiniti",
|
||||
configureColumnTemplates: "Configura campi predefiniti",
|
||||
presetFieldsTemplate: "Set di campi predefiniti",
|
||||
addIndex: "Aggiungi Indice",
|
||||
columnName: "Colonna",
|
||||
dataType: "Tipo",
|
||||
|
|
@ -2086,6 +2089,7 @@ export default {
|
|||
debugLogsDownload: "Scarica log",
|
||||
debugLogsDownloaded: "Scaricati",
|
||||
debugLogsClear: "Cancella log",
|
||||
tableStructureSection: "Struttura tabella",
|
||||
dataGridDisplay: "Visualizzazione griglia dati",
|
||||
showColumnCommentsInHeader: "Mostra i commenti delle colonne sotto i nomi",
|
||||
showColumnCommentsInHeaderDescription: "Visualizza i commenti delle colonne della tabella direttamente sotto i nomi delle colonne nella griglia.",
|
||||
|
|
@ -2097,6 +2101,18 @@ export default {
|
|||
infiniteScrollDescription: "Carica automaticamente la pagina successiva dei dati quando scorri fino in fondo alla tabella.",
|
||||
infiniteScrollMaxRows: "Righe max a scorrimento infinito",
|
||||
infiniteScrollMaxRowsDescription: "Numero massimo di righe da caricare in modalità scorrimento infinito (1000–50000).",
|
||||
tableColumnTemplateFields: "Campi predefiniti per nuove tabelle",
|
||||
tableColumnTemplateFieldsDescription: "Scegli un tipo di database e configura i tipi dei campi predefiniti usati durante la creazione di nuove tabelle.",
|
||||
tableColumnTemplateAdd: "Aggiungi campo",
|
||||
tableColumnTemplateColumn: "Campo",
|
||||
tableColumnTemplateDatabase: "Database",
|
||||
tableColumnTemplateType: "Tipo",
|
||||
tableColumnTemplateLength: "Lunghezza",
|
||||
tableColumnTemplateDefault: "Valore predefinito",
|
||||
tableColumnTemplateNoPresetType: "Nessun tipo preimpostato",
|
||||
tableColumnTemplateRequired: "Obbligatorio",
|
||||
tableColumnTemplateComment: "Descrizione",
|
||||
tableColumnTemplateDragHandle: "Trascina per riordinare",
|
||||
sidebarActivation: "Attivazione barra laterale",
|
||||
sidebarActivationSingle: "Singolo clic",
|
||||
sidebarActivationSingleDescription: "Apri gli elementi della barra laterale con un solo clic.",
|
||||
|
|
|
|||
|
|
@ -1437,6 +1437,9 @@ export default {
|
|||
triggers: "トリガー",
|
||||
ddl: "DDL",
|
||||
addColumn: "列を追加",
|
||||
columnTemplates: "プリセット列を挿入",
|
||||
configureColumnTemplates: "プリセット列を設定",
|
||||
presetFieldsTemplate: "プリセット列セット",
|
||||
addIndex: "インデックスを追加",
|
||||
addForeignKey: "外部キーを追加",
|
||||
addTrigger: "トリガーを追加",
|
||||
|
|
@ -2324,6 +2327,7 @@ export default {
|
|||
debugLogsDownload: "ログをダウンロード",
|
||||
debugLogsDownloaded: "ダウンロードしました",
|
||||
debugLogsClear: "ログをクリア",
|
||||
tableStructureSection: "テーブル構造",
|
||||
dataGridDisplay: "データグリッド表示",
|
||||
showColumnCommentsInHeader: "列名の下にコメントを表示",
|
||||
showColumnCommentsInHeaderDescription: "グリッド列名の直下にテーブル列コメントを表示します。",
|
||||
|
|
@ -2331,6 +2335,18 @@ export default {
|
|||
showColumnTypesInHeaderDescription: "グリッド列名の直下に各列のデータ型を表示します。",
|
||||
compactColumnHeaderActions: "列ヘッダーツールをコンパクトに",
|
||||
compactColumnHeaderActionsDescription: "フォーマッターとローカルフィルターツールをもっと見るメニューに移動し、列名を優先表示します。",
|
||||
tableColumnTemplateFields: "新規テーブルのプリセット列",
|
||||
tableColumnTemplateFieldsDescription: "データベース種別を選択し、新規テーブル作成時に使うプリセット列の型を設定します。",
|
||||
tableColumnTemplateAdd: "列を追加",
|
||||
tableColumnTemplateColumn: "列",
|
||||
tableColumnTemplateDatabase: "データベース",
|
||||
tableColumnTemplateType: "型",
|
||||
tableColumnTemplateLength: "長さ",
|
||||
tableColumnTemplateDefault: "デフォルト",
|
||||
tableColumnTemplateNoPresetType: "プリセット型なし",
|
||||
tableColumnTemplateRequired: "必須",
|
||||
tableColumnTemplateComment: "説明",
|
||||
tableColumnTemplateDragHandle: "ドラッグして並べ替え",
|
||||
sidebarActivation: "サイドバーのアクティベーション",
|
||||
sidebarActivationSingle: "シングルクリック",
|
||||
sidebarActivationSingleDescription: "1クリックでサイドバー項目を開きます。",
|
||||
|
|
|
|||
|
|
@ -1338,6 +1338,9 @@ export default {
|
|||
triggers: "Triggers",
|
||||
ddl: "DDL",
|
||||
addColumn: "Adicionar coluna",
|
||||
columnTemplates: "Inserir campos predefinidos",
|
||||
configureColumnTemplates: "Configurar campos predefinidos",
|
||||
presetFieldsTemplate: "Conjunto de campos predefinidos",
|
||||
addIndex: "Adicionar índice",
|
||||
addForeignKey: "Adicionar chave estrangeira",
|
||||
addTrigger: "Adicionar trigger",
|
||||
|
|
@ -2097,6 +2100,7 @@ export default {
|
|||
debugLogsDownload: "Baixar logs",
|
||||
debugLogsDownloaded: "Baixado",
|
||||
debugLogsClear: "Limpar logs",
|
||||
tableStructureSection: "Estrutura da tabela",
|
||||
dataGridDisplay: "Exibição da grade de dados",
|
||||
showColumnCommentsInHeader: "Mostrar comentários de coluna sob os nomes",
|
||||
showColumnCommentsInHeaderDescription: "Exibir comentários de colunas da tabela diretamente abaixo dos nomes das colunas da grade.",
|
||||
|
|
@ -2108,6 +2112,18 @@ export default {
|
|||
infiniteScrollDescription: "Carregar automaticamente a próxima página de dados ao rolar até o final da tabela.",
|
||||
infiniteScrollMaxRows: "Máximo de linhas em rolagem infinita",
|
||||
infiniteScrollMaxRowsDescription: "Número máximo de linhas a carregar no modo de rolagem infinita (1000–50000).",
|
||||
tableColumnTemplateFields: "Campos predefinidos para novas tabelas",
|
||||
tableColumnTemplateFieldsDescription: "Escolha um tipo de banco de dados e configure os tipos dos campos predefinidos usados ao criar novas tabelas.",
|
||||
tableColumnTemplateAdd: "Adicionar campo",
|
||||
tableColumnTemplateColumn: "Campo",
|
||||
tableColumnTemplateDatabase: "Banco de dados",
|
||||
tableColumnTemplateType: "Tipo",
|
||||
tableColumnTemplateLength: "Comprimento",
|
||||
tableColumnTemplateDefault: "Padrão",
|
||||
tableColumnTemplateNoPresetType: "Sem tipo predefinido",
|
||||
tableColumnTemplateRequired: "Obrigatório",
|
||||
tableColumnTemplateComment: "Descrição",
|
||||
tableColumnTemplateDragHandle: "Arraste para reordenar",
|
||||
sidebarActivation: "Ativação da barra lateral",
|
||||
sidebarActivationSingle: "Clique único",
|
||||
sidebarActivationSingleDescription: "Abrir itens acionáveis da barra lateral com um clique.",
|
||||
|
|
|
|||
|
|
@ -1457,6 +1457,9 @@ export default {
|
|||
triggers: "触发器",
|
||||
ddl: "DDL",
|
||||
addColumn: "新增字段",
|
||||
columnTemplates: "插入预设字段",
|
||||
configureColumnTemplates: "配置预设字段",
|
||||
presetFieldsTemplate: "预设字段组合",
|
||||
addIndex: "新增索引",
|
||||
addForeignKey: "新增外键",
|
||||
addTrigger: "新增触发器",
|
||||
|
|
@ -2431,6 +2434,7 @@ export default {
|
|||
debugLogsDownload: "下载日志",
|
||||
debugLogsDownloaded: "已下载",
|
||||
debugLogsClear: "清空日志",
|
||||
tableStructureSection: "表结构",
|
||||
dataGridDisplay: "数据表格显示",
|
||||
showColumnCommentsInHeader: "在字段名下方显示注释",
|
||||
showColumnCommentsInHeaderDescription: "把表字段注释直接显示在结果表头字段名下方。",
|
||||
|
|
@ -2442,6 +2446,18 @@ export default {
|
|||
infiniteScrollDescription: "滚动到表格底部时自动加载下一页数据,无需手动翻页。",
|
||||
infiniteScrollMaxRows: "无限滚动最大行数",
|
||||
infiniteScrollMaxRowsDescription: "无限滚动模式下最多加载的行数(1000–50000)。",
|
||||
tableColumnTemplateFields: "新建表预设字段",
|
||||
tableColumnTemplateFieldsDescription: "先选择数据库类型,再配置新建表时使用的预设字段类型。",
|
||||
tableColumnTemplateAdd: "新增字段",
|
||||
tableColumnTemplateColumn: "字段",
|
||||
tableColumnTemplateDatabase: "数据库",
|
||||
tableColumnTemplateType: "类型",
|
||||
tableColumnTemplateLength: "长度",
|
||||
tableColumnTemplateDefault: "默认值",
|
||||
tableColumnTemplateNoPresetType: "不预设类型",
|
||||
tableColumnTemplateRequired: "必填",
|
||||
tableColumnTemplateComment: "描述",
|
||||
tableColumnTemplateDragHandle: "拖动调整顺序",
|
||||
sidebarActivation: "侧边栏打开方式",
|
||||
sidebarActivationSingle: "单击打开",
|
||||
sidebarActivationSingleDescription: "单击即可打开侧边栏中的可操作项目。",
|
||||
|
|
|
|||
|
|
@ -1318,6 +1318,9 @@ export default {
|
|||
triggers: "觸發器",
|
||||
ddl: "DDL",
|
||||
addColumn: "新增欄位",
|
||||
columnTemplates: "插入預設欄位",
|
||||
configureColumnTemplates: "設定預設欄位",
|
||||
presetFieldsTemplate: "預設欄位組合",
|
||||
addIndex: "新增索引",
|
||||
addForeignKey: "新增外鍵",
|
||||
addTrigger: "新增觸發器",
|
||||
|
|
@ -2148,6 +2151,7 @@ export default {
|
|||
debugLogsDownload: "下載日誌",
|
||||
debugLogsDownloaded: "已下載",
|
||||
debugLogsClear: "清空日誌",
|
||||
tableStructureSection: "表結構",
|
||||
dataGridDisplay: "資料表格顯示",
|
||||
showColumnCommentsInHeader: "在欄位名稱下方顯示註解",
|
||||
showColumnCommentsInHeaderDescription: "直接在資料表格欄位名稱下方顯示資料表欄位註解。",
|
||||
|
|
@ -2159,6 +2163,18 @@ export default {
|
|||
infiniteScrollDescription: "滾動到表格底部時自動載入下一頁資料,無需手動翻頁。",
|
||||
infiniteScrollMaxRows: "無限滾動最大筆數",
|
||||
infiniteScrollMaxRowsDescription: "無限滾動模式下最多載入的筆數(1000–50000)。",
|
||||
tableColumnTemplateFields: "新建表預設欄位",
|
||||
tableColumnTemplateFieldsDescription: "先選擇資料庫類型,再設定新建表時使用的預設欄位類型。",
|
||||
tableColumnTemplateAdd: "新增欄位",
|
||||
tableColumnTemplateColumn: "欄位",
|
||||
tableColumnTemplateDatabase: "資料庫",
|
||||
tableColumnTemplateType: "類型",
|
||||
tableColumnTemplateLength: "長度",
|
||||
tableColumnTemplateDefault: "預設值",
|
||||
tableColumnTemplateNoPresetType: "不預設類型",
|
||||
tableColumnTemplateRequired: "必填",
|
||||
tableColumnTemplateComment: "描述",
|
||||
tableColumnTemplateDragHandle: "拖曳調整順序",
|
||||
sidebarActivation: "側邊欄開啟方式",
|
||||
sidebarActivationSingle: "單擊開啟",
|
||||
sidebarActivationSingleDescription: "單擊即可開啟側邊欄中的可操作項目。",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,130 @@
|
|||
import type { DatabaseType } from "@/types/database";
|
||||
import type { EditableStructureColumn } from "@/lib/tableStructureEditorSql";
|
||||
import { manifestDatabaseTypes } from "@/lib/databaseDriverManifest";
|
||||
import { getTableStructureCapabilities } from "@/lib/tableStructureCapabilities";
|
||||
|
||||
export interface TableColumnTemplate {
|
||||
id: string;
|
||||
labelKey: string;
|
||||
columnNames: string[];
|
||||
}
|
||||
|
||||
export interface TableColumnTemplateField {
|
||||
name: string;
|
||||
dataTypesByDatabase: Partial<Record<DatabaseType, string>>;
|
||||
defaultValue?: string;
|
||||
isNullable?: boolean;
|
||||
comment?: string;
|
||||
}
|
||||
|
||||
export const PRESET_FIELDS_TEMPLATE_ID = "preset-fields";
|
||||
export const EMPTY_TABLE_COLUMN_TEMPLATE_DATA_TYPE = "<empty>";
|
||||
export const TABLE_COLUMN_TEMPLATE_DATABASE_TYPES: DatabaseType[] = manifestDatabaseTypes().filter(isTableColumnTemplateDatabaseType);
|
||||
export const DEFAULT_TABLE_COLUMN_TEMPLATE_FIELDS: string[] = [];
|
||||
|
||||
export function normalizeTableColumnTemplateFields(value: unknown): string[] {
|
||||
if (!Array.isArray(value)) return [...DEFAULT_TABLE_COLUMN_TEMPLATE_FIELDS];
|
||||
const fields: string[] = [];
|
||||
const seen = new Set<string>();
|
||||
for (const item of value) {
|
||||
if (typeof item !== "string") continue;
|
||||
const field = item.trim();
|
||||
const name = field.split("|")[0]?.trim();
|
||||
if (!field || !name || seen.has(name.toLowerCase())) continue;
|
||||
seen.add(name.toLowerCase());
|
||||
fields.push(field);
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
|
||||
export function parseTableColumnTemplateFields(value: unknown): TableColumnTemplateField[] {
|
||||
return normalizeTableColumnTemplateFields(value).map(parseTableColumnTemplateField);
|
||||
}
|
||||
|
||||
export function tableColumnTemplates(columnNames: readonly string[] = DEFAULT_TABLE_COLUMN_TEMPLATE_FIELDS): TableColumnTemplate[] {
|
||||
const fields = parseTableColumnTemplateFields([...columnNames]);
|
||||
return [
|
||||
{
|
||||
id: PRESET_FIELDS_TEMPLATE_ID,
|
||||
labelKey: "structureEditor.presetFieldsTemplate",
|
||||
columnNames: fields.map((field) => field.name),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function createTableColumnTemplateDrafts(options: { templateId: string; databaseType?: DatabaseType; columnNames?: readonly string[]; existingColumnNames?: Iterable<string>; createId: () => string }): EditableStructureColumn[] {
|
||||
if (options.templateId !== PRESET_FIELDS_TEMPLATE_ID) return [];
|
||||
const existingNames = new Set([...(options.existingColumnNames ?? [])].map((name) => name.toLowerCase()));
|
||||
return presetFieldColumns(options.databaseType, parseTableColumnTemplateFields([...(options.columnNames ?? DEFAULT_TABLE_COLUMN_TEMPLATE_FIELDS)]), options.createId).filter((column) => !existingNames.has(column.name.toLowerCase()));
|
||||
}
|
||||
|
||||
function presetFieldColumns(databaseType: DatabaseType | undefined, fields: readonly TableColumnTemplateField[], createId: () => string): EditableStructureColumn[] {
|
||||
return fields
|
||||
.filter((field) => isTableColumnTemplateFieldApplicable(field, databaseType))
|
||||
.map((field) => {
|
||||
return templateColumn(createId, field.name, configuredFieldDataType(field, databaseType) ?? "", field.isNullable ?? false, field.defaultValue ?? "", field.comment ?? "");
|
||||
});
|
||||
}
|
||||
|
||||
function templateColumn(createId: () => string, name: string, dataType: string, isNullable: boolean, defaultValue = "", comment = ""): EditableStructureColumn {
|
||||
return {
|
||||
id: `new:${createId()}`,
|
||||
name,
|
||||
dataType,
|
||||
isNullable,
|
||||
defaultValue,
|
||||
comment,
|
||||
isPrimaryKey: false,
|
||||
extra: {},
|
||||
markedForDrop: false,
|
||||
};
|
||||
}
|
||||
|
||||
function parseTableColumnTemplateField(value: string): TableColumnTemplateField {
|
||||
const [rawName = "", ...rawParts] = value.split("|").map((part) => part.trim());
|
||||
const field: TableColumnTemplateField = { name: rawName, dataTypesByDatabase: {} };
|
||||
for (const part of rawParts) {
|
||||
const separator = part.indexOf(":");
|
||||
if (separator <= 0) continue;
|
||||
const key = part.slice(0, separator).trim().toLowerCase();
|
||||
const dataType = part.slice(separator + 1).trim();
|
||||
if (!dataType) continue;
|
||||
if (key === "nullable") {
|
||||
field.isNullable = parseBooleanConfigValue(dataType);
|
||||
} else if (key === "required") {
|
||||
const required = parseBooleanConfigValue(dataType);
|
||||
field.isNullable = required === undefined ? undefined : !required;
|
||||
} else if (key === "default" || key === "defaultvalue" || key === "default_value") {
|
||||
field.defaultValue = dataType;
|
||||
} else if (key === "comment" || key === "description") {
|
||||
field.comment = dataType;
|
||||
} else if (isDatabaseTypeKey(key)) {
|
||||
field.dataTypesByDatabase[key] = dataType;
|
||||
}
|
||||
}
|
||||
return field;
|
||||
}
|
||||
|
||||
function parseBooleanConfigValue(value: string): boolean | undefined {
|
||||
const normalized = value.trim().toLowerCase();
|
||||
if (normalized === "true" || normalized === "yes" || normalized === "1") return true;
|
||||
if (normalized === "false" || normalized === "no" || normalized === "0") return false;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function configuredFieldDataType(field: TableColumnTemplateField, databaseType: DatabaseType | undefined): string | undefined {
|
||||
const configuredDataType = databaseType ? field.dataTypesByDatabase[databaseType] : undefined;
|
||||
return configuredDataType && configuredDataType !== EMPTY_TABLE_COLUMN_TEMPLATE_DATA_TYPE ? configuredDataType : undefined;
|
||||
}
|
||||
|
||||
function isTableColumnTemplateFieldApplicable(field: TableColumnTemplateField, databaseType: DatabaseType | undefined): boolean {
|
||||
return !!databaseType && Object.prototype.hasOwnProperty.call(field.dataTypesByDatabase, databaseType);
|
||||
}
|
||||
|
||||
function isTableColumnTemplateDatabaseType(databaseType: DatabaseType): boolean {
|
||||
return databaseType !== "manticoresearch" && getTableStructureCapabilities(databaseType).createTable;
|
||||
}
|
||||
|
||||
function isDatabaseTypeKey(value: string): value is DatabaseType {
|
||||
return TABLE_COLUMN_TEMPLATE_DATABASE_TYPES.includes(value as DatabaseType);
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ import type { SidebarActivation } from "@/lib/treeNodeClick";
|
|||
import type { SqlSnippet } from "@/types/database";
|
||||
import { DEFAULT_SQL_SNIPPETS } from "@/lib/sqlCompletion";
|
||||
import { setDebugLoggingEnabled } from "@/lib/debugLog";
|
||||
import { DEFAULT_TABLE_COLUMN_TEMPLATE_FIELDS, normalizeTableColumnTemplateFields } from "@/lib/tableColumnTemplates";
|
||||
|
||||
export type AiProvider = "claude" | "openai" | "gemini" | "deepseek" | "qwen" | "ollama" | "openai-compatible" | "codex-cli" | "custom";
|
||||
export type AiApiStyle = "completions" | "responses";
|
||||
|
|
@ -340,6 +341,7 @@ export interface EditorSettings {
|
|||
columnFormatters: Record<string, ColumnFormatterConfig>;
|
||||
customColumnFormatters: Record<string, CustomColumnFormatterConfig>;
|
||||
snippets: SqlSnippet[];
|
||||
tableColumnTemplateFields: string[];
|
||||
exportBatchSize: number;
|
||||
exportRowLimitEnabled: boolean;
|
||||
exportRowLimit: number;
|
||||
|
|
@ -443,6 +445,7 @@ export const DEFAULT_EDITOR_SETTINGS: EditorSettings = {
|
|||
columnFormatters: {},
|
||||
customColumnFormatters: {},
|
||||
snippets: DEFAULT_SQL_SNIPPETS,
|
||||
tableColumnTemplateFields: [...DEFAULT_TABLE_COLUMN_TEMPLATE_FIELDS],
|
||||
exportBatchSize: 2000,
|
||||
exportRowLimitEnabled: true,
|
||||
exportRowLimit: 100000,
|
||||
|
|
@ -613,6 +616,7 @@ export function normalizeEditorSettings(settings: Partial<EditorSettings>, exist
|
|||
columnFormatters: normalizeColumnFormatters(settings.columnFormatters),
|
||||
customColumnFormatters: normalizeCustomColumnFormatters(settings.customColumnFormatters),
|
||||
snippets: normalizeSqlSnippets(settings.snippets, existing?.snippets),
|
||||
tableColumnTemplateFields: normalizeTableColumnTemplateFields(settings.tableColumnTemplateFields),
|
||||
exportBatchSize: typeof settings.exportBatchSize === "number" && settings.exportBatchSize >= 100 && settings.exportBatchSize <= 100000 ? Math.round(settings.exportBatchSize) : DEFAULT_EDITOR_SETTINGS.exportBatchSize,
|
||||
exportRowLimitEnabled: typeof settings.exportRowLimitEnabled === "boolean" ? settings.exportRowLimitEnabled : DEFAULT_EDITOR_SETTINGS.exportRowLimitEnabled,
|
||||
exportRowLimit: typeof settings.exportRowLimit === "number" && settings.exportRowLimit >= 100 && settings.exportRowLimit <= 2147483647 ? Math.round(settings.exportRowLimit) : DEFAULT_EDITOR_SETTINGS.exportRowLimit,
|
||||
|
|
@ -784,6 +788,7 @@ export const useSettingsStore = defineStore("settings", () => {
|
|||
if (partial.columnFormatters !== undefined) editorSettings.value.columnFormatters = partial.columnFormatters;
|
||||
if (partial.customColumnFormatters !== undefined) editorSettings.value.customColumnFormatters = partial.customColumnFormatters;
|
||||
if (partial.snippets !== undefined) editorSettings.value.snippets = normalizeSqlSnippets(partial.snippets);
|
||||
if (partial.tableColumnTemplateFields !== undefined) editorSettings.value.tableColumnTemplateFields = normalizeTableColumnTemplateFields(partial.tableColumnTemplateFields);
|
||||
if (partial.exportBatchSize !== undefined) editorSettings.value.exportBatchSize = Math.min(100000, Math.max(100, Math.round(partial.exportBatchSize)));
|
||||
if (partial.exportRowLimitEnabled !== undefined) editorSettings.value.exportRowLimitEnabled = partial.exportRowLimitEnabled;
|
||||
if (partial.exportRowLimit !== undefined) editorSettings.value.exportRowLimit = Math.min(2147483647, Math.max(100, Math.round(partial.exportRowLimit)));
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { test } from "vitest";
|
|||
import assert from "node:assert/strict";
|
||||
import { createPinia, setActivePinia } from "pinia";
|
||||
import { DEFAULT_SQL_FORMATTER_SETTINGS } from "../../apps/desktop/src/lib/sqlFormatterConfig.ts";
|
||||
import { DEFAULT_TABLE_COLUMN_TEMPLATE_FIELDS } from "../../apps/desktop/src/lib/tableColumnTemplates.ts";
|
||||
import { AI_PROVIDER_PRESETS, DEFAULT_EDITOR_SETTINGS, normalizeAiConfig, normalizeEditorSettings, useSettingsStore } from "../../apps/desktop/src/stores/settingsStore.ts";
|
||||
|
||||
const OLD_FONT_SIZE_KEY = "dbx-query-editor-font-size";
|
||||
|
|
@ -206,6 +207,22 @@ test("normalizes table structure editor density", () => {
|
|||
assert.equal(normalizeEditorSettings({ structureEditorDensity: "invalid" as any }).structureEditorDensity, "compact");
|
||||
});
|
||||
|
||||
test("normalizes table column template fields", () => {
|
||||
assert.deepEqual(DEFAULT_EDITOR_SETTINGS.tableColumnTemplateFields, DEFAULT_TABLE_COLUMN_TEMPLATE_FIELDS);
|
||||
assert.deepEqual(DEFAULT_EDITOR_SETTINGS.tableColumnTemplateFields, []);
|
||||
assert.deepEqual(normalizeEditorSettings({}).tableColumnTemplateFields, DEFAULT_TABLE_COLUMN_TEMPLATE_FIELDS);
|
||||
const normalizedTemplateFields = normalizeEditorSettings({ tableColumnTemplateFields: [" tenant_id | mysql:bigint ", "request_id | mysql:varchar(64)", "TENANT_ID | mysql:bigint", ""] } as any).tableColumnTemplateFields;
|
||||
assert.equal(
|
||||
normalizedTemplateFields.find((field) => field.startsWith("tenant_id")),
|
||||
"tenant_id | mysql:bigint",
|
||||
);
|
||||
assert.equal(
|
||||
normalizedTemplateFields.find((field) => field.startsWith("request_id")),
|
||||
"request_id | mysql:varchar(64)",
|
||||
);
|
||||
assert.deepEqual(normalizeEditorSettings({ tableColumnTemplateFields: [] } as any).tableColumnTemplateFields, DEFAULT_TABLE_COLUMN_TEMPLATE_FIELDS);
|
||||
});
|
||||
|
||||
test("normalizes grid drawer widths", () => {
|
||||
assert.equal(DEFAULT_EDITOR_SETTINGS.tableInfoDrawerWidth, 320);
|
||||
assert.equal(DEFAULT_EDITOR_SETTINGS.cellDetailDrawerWidth, 380);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,116 @@
|
|||
import { strict as assert } from "node:assert";
|
||||
import { test } from "vitest";
|
||||
import { createTableColumnTemplateDrafts, DEFAULT_TABLE_COLUMN_TEMPLATE_FIELDS, normalizeTableColumnTemplateFields, parseTableColumnTemplateFields, PRESET_FIELDS_TEMPLATE_ID, tableColumnTemplates, TABLE_COLUMN_TEMPLATE_DATABASE_TYPES } from "../../apps/desktop/src/lib/tableColumnTemplates.ts";
|
||||
|
||||
const sixCustomFields = [
|
||||
"tenant_id | mysql:bigint | postgres:uuid | default:0 | comment:Tenant",
|
||||
"request_id | mysql:varchar(64) | postgres:varchar(64)",
|
||||
"created_time | mysql:datetime | postgres:timestamp | default:CURRENT_TIMESTAMP",
|
||||
"modified_time | mysql:datetime | postgres:timestamp",
|
||||
"creator_id | mysql:bigint | postgres:bigint",
|
||||
"modifier_id | mysql:bigint | postgres:bigint | required:false",
|
||||
];
|
||||
|
||||
test("has no built-in preset field names by default", () => {
|
||||
assert.deepEqual(DEFAULT_TABLE_COLUMN_TEMPLATE_FIELDS, []);
|
||||
assert.deepEqual(normalizeTableColumnTemplateFields(undefined), []);
|
||||
assert.deepEqual(normalizeTableColumnTemplateFields([]), []);
|
||||
assert.deepEqual(tableColumnTemplates(), [
|
||||
{
|
||||
id: PRESET_FIELDS_TEMPLATE_ID,
|
||||
labelKey: "structureEditor.presetFieldsTemplate",
|
||||
columnNames: [],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("builds no preset field drafts until users configure fields", () => {
|
||||
const columns = createTableColumnTemplateDrafts({
|
||||
templateId: PRESET_FIELDS_TEMPLATE_ID,
|
||||
databaseType: "postgres",
|
||||
createId: () => "id",
|
||||
});
|
||||
|
||||
assert.deepEqual(columns, []);
|
||||
});
|
||||
|
||||
test("builds configured preset field drafts", () => {
|
||||
let id = 0;
|
||||
const columns = createTableColumnTemplateDrafts({
|
||||
templateId: PRESET_FIELDS_TEMPLATE_ID,
|
||||
databaseType: "mysql",
|
||||
columnNames: sixCustomFields,
|
||||
createId: () => String(++id),
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
columns.map((column) => ({
|
||||
id: column.id,
|
||||
name: column.name,
|
||||
dataType: column.dataType,
|
||||
isNullable: column.isNullable,
|
||||
defaultValue: column.defaultValue,
|
||||
comment: column.comment,
|
||||
})),
|
||||
[
|
||||
{ id: "new:1", name: "tenant_id", dataType: "bigint", isNullable: false, defaultValue: "0", comment: "Tenant" },
|
||||
{ id: "new:2", name: "request_id", dataType: "varchar(64)", isNullable: false, defaultValue: "", comment: "" },
|
||||
{ id: "new:3", name: "created_time", dataType: "datetime", isNullable: false, defaultValue: "CURRENT_TIMESTAMP", comment: "" },
|
||||
{ id: "new:4", name: "modified_time", dataType: "datetime", isNullable: false, defaultValue: "", comment: "" },
|
||||
{ id: "new:5", name: "creator_id", dataType: "bigint", isNullable: false, defaultValue: "", comment: "" },
|
||||
{ id: "new:6", name: "modifier_id", dataType: "bigint", isNullable: true, defaultValue: "", comment: "" },
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
test("filters configured fields by current database type", () => {
|
||||
const columns = createTableColumnTemplateDrafts({
|
||||
templateId: PRESET_FIELDS_TEMPLATE_ID,
|
||||
databaseType: "postgres",
|
||||
columnNames: ["mysql_only | mysql:bigint", "postgres_only | postgres:uuid", "common_name | mysql:<empty> | postgres:<empty>", "common_code | mysql:varchar(64) | postgres:varchar(32)"],
|
||||
createId: () => "id",
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
columns.map((column) => ({ name: column.name, dataType: column.dataType })),
|
||||
[
|
||||
{ name: "postgres_only", dataType: "uuid" },
|
||||
{ name: "common_name", dataType: "" },
|
||||
{ name: "common_code", dataType: "varchar(32)" },
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
test("normalizes configured preset fields without adding built-in fields", () => {
|
||||
const [tenantId, requestId] = normalizeTableColumnTemplateFields([" tenant_id | mysql:bigint | postgres:uuid | default:0 ", "tenant_id | mysql:int", "", "request_id | mysql:varchar(64)"]);
|
||||
assert.equal(tenantId, "tenant_id | mysql:bigint | postgres:uuid | default:0");
|
||||
assert.equal(requestId, "request_id | mysql:varchar(64)");
|
||||
|
||||
const parsed = parseTableColumnTemplateFields(["tenant_id | mysql:bigint | postgres:uuid | nullable:true | default:0 | comment:Tenant"]);
|
||||
assert.deepEqual(parsed[0], { name: "tenant_id", dataTypesByDatabase: { mysql: "bigint", postgres: "uuid" }, defaultValue: "0", isNullable: true, comment: "Tenant" });
|
||||
});
|
||||
|
||||
test("limits preset field database types to create-table capable SQL structures", () => {
|
||||
assert.ok(TABLE_COLUMN_TEMPLATE_DATABASE_TYPES.includes("mysql"));
|
||||
assert.ok(TABLE_COLUMN_TEMPLATE_DATABASE_TYPES.includes("postgres"));
|
||||
assert.ok(TABLE_COLUMN_TEMPLATE_DATABASE_TYPES.includes("clickhouse"));
|
||||
assert.ok(!TABLE_COLUMN_TEMPLATE_DATABASE_TYPES.includes("mongodb"));
|
||||
assert.ok(!TABLE_COLUMN_TEMPLATE_DATABASE_TYPES.includes("redis"));
|
||||
assert.ok(!TABLE_COLUMN_TEMPLATE_DATABASE_TYPES.includes("elasticsearch"));
|
||||
assert.ok(!TABLE_COLUMN_TEMPLATE_DATABASE_TYPES.includes("manticoresearch"));
|
||||
});
|
||||
|
||||
test("skips configured preset fields that already exist", () => {
|
||||
const columns = createTableColumnTemplateDrafts({
|
||||
templateId: PRESET_FIELDS_TEMPLATE_ID,
|
||||
databaseType: "mysql",
|
||||
columnNames: sixCustomFields,
|
||||
existingColumnNames: ["tenant_id", "MODIFIER_ID"],
|
||||
createId: () => "x",
|
||||
});
|
||||
|
||||
assert.deepEqual(
|
||||
columns.map((column) => column.name),
|
||||
["request_id", "created_time", "modified_time", "creator_id"],
|
||||
);
|
||||
});
|
||||
Loading…
Reference in New Issue