feat(oracle): show table constraints in structure editor
This commit is contained in:
1 parent
76b8f2f54b
commit
f9c4f290f5
19 files changed
+413
-20
No files matched your search
@@ -40,7 +40,7 @@ import { canAddTableStructureColumn, getTableStructureCapabilities, hasLocalTabl
|
||||
import { orderedColumnIndexes, uniqueDataGridColumnOrderKeys } from "@/lib/dataGrid/dataGridColumnOrder";
|
||||
import { loadTableDataGridColumnOrder, notifyTableDataGridColumnOrderChanged, removeTableDataGridColumnOrder, saveTableDataGridColumnOrder, tableDataGridColumnOrderScopeKey } from "@/lib/dataGrid/dataGridColumnLayoutStorage";
|
||||
import { connectionObjectTreeQuerySchema, tableStructureDatabaseTypeForConnection } from "@/lib/database/jdbcDialect";
|
||||
import type { TableInfoTab, TableStructureEditorDraft, TableStructureEditorTarget, TableStructureEditorViewport } from "@/types/database";
|
||||
import type { ConstraintInfo, TableInfoTab, TableStructureEditorDraft, TableStructureEditorTarget, TableStructureEditorViewport } from "@/types/database";
|
||||
import {
|
||||
applyManticoreDdlColumnExtras,
|
||||
buildStructureTargetLabel,
|
||||
@@ -95,6 +95,7 @@ type StructureScrollerRef = HTMLElement | { $el?: HTMLElement };
|
||||
const columnsScrollerRef = ref<StructureScrollerRef>();
|
||||
const indexesScrollerRef = ref<StructureScrollerRef>();
|
||||
const foreignKeysScrollerRef = ref<StructureScrollerRef>();
|
||||
const constraintsScrollerRef = ref<StructureScrollerRef>();
|
||||
const triggersScrollerRef = ref<StructureScrollerRef>();
|
||||
const ddlScrollerRef = ref<StructureScrollerRef>();
|
||||
const dynamicDataTypeOptionsCache = new Map<string, string[]>();
|
||||
@@ -141,6 +142,7 @@ const sqlPreviewLoading = ref(false);
|
||||
const sqlPreviewPending = ref(false);
|
||||
const indexesLoading = ref(false);
|
||||
const foreignKeysLoading = ref(false);
|
||||
const constraintsLoading = ref(false);
|
||||
const triggersLoading = ref(false);
|
||||
const ddlContent = ref("");
|
||||
const ddlLoading = ref(false);
|
||||
@@ -192,9 +194,11 @@ const pendingStatements = ref<string[]>([]);
|
||||
const warnings = ref<string[]>([]);
|
||||
const sqliteSchemaRevision = ref<string>();
|
||||
const foreignKeys = ref<EditableStructureForeignKey[]>([]);
|
||||
const constraints = ref<ConstraintInfo[]>([]);
|
||||
const constraintsLoaded = ref(false);
|
||||
const triggers = ref<EditableStructureTrigger[]>([]);
|
||||
const triggersLoaded = ref(false);
|
||||
const secondaryMetadataLoading = computed(() => indexesLoading.value || foreignKeysLoading.value || triggersLoading.value);
|
||||
const secondaryMetadataLoading = computed(() => indexesLoading.value || foreignKeysLoading.value || constraintsLoading.value || triggersLoading.value);
|
||||
|
||||
function sameList(left: string[] | null | undefined, right: string[] | null | undefined): boolean {
|
||||
const a = left ?? [];
|
||||
@@ -262,6 +266,10 @@ function captureStructureRefreshScope(): TableStructureRefreshScope {
|
||||
columns: columns.value.some(columnChanged),
|
||||
indexes: indexes.value.some(indexChanged),
|
||||
foreignKeys: foreignKeys.value.some(foreignKeyChanged),
|
||||
// Constraints have no editable draft of their own, but column/index/FK
|
||||
// saves can change what constraints exist (e.g. toggling a primary key),
|
||||
// so refresh the tab if it was ever loaded rather than leaving it stale.
|
||||
constraints: constraintsLoaded.value,
|
||||
triggers: triggers.value.some(triggerChanged),
|
||||
tableComment: tableComment.value !== originalTableComment.value,
|
||||
};
|
||||
@@ -881,6 +889,7 @@ function structureScrollerForTab(tab: TableInfoTab): HTMLElement | undefined {
|
||||
if (tab === "columns") return structureScrollerElement(columnsScrollerRef.value);
|
||||
if (tab === "indexes") return structureScrollerElement(indexesScrollerRef.value);
|
||||
if (tab === "foreignKeys") return structureScrollerElement(foreignKeysScrollerRef.value);
|
||||
if (tab === "constraints") return structureScrollerElement(constraintsScrollerRef.value);
|
||||
if (tab === "triggers") return structureScrollerElement(triggersScrollerRef.value);
|
||||
if (tab === "ddl") return structureScrollerElement(ddlScrollerRef.value);
|
||||
return undefined;
|
||||
@@ -927,6 +936,8 @@ function createCurrentDraft(initialized = true): TableStructureEditorDraft {
|
||||
columns: cloneDraftValue(columns.value),
|
||||
indexes: cloneDraftValue(indexes.value),
|
||||
foreignKeys: cloneDraftValue(foreignKeys.value),
|
||||
constraints: cloneDraftValue(constraints.value),
|
||||
constraintsLoaded: constraintsLoaded.value,
|
||||
triggers: cloneDraftValue(triggers.value),
|
||||
triggersLoaded: triggersLoaded.value,
|
||||
loadedMetadataFacets: [...loadedMetadataFacets],
|
||||
@@ -953,6 +964,9 @@ function restoreDraft(draft: TableStructureEditorDraft) {
|
||||
columns.value = cloneDraftValue(draft.columns || []);
|
||||
indexes.value = cloneDraftValue(draft.indexes || []);
|
||||
foreignKeys.value = cloneDraftValue(draft.foreignKeys || []);
|
||||
constraints.value = cloneDraftValue(draft.constraints || []);
|
||||
// Drafts created before constraint loading existed have no saved facet.
|
||||
constraintsLoaded.value = draft.constraintsLoaded ?? false;
|
||||
triggers.value = cloneDraftValue(draft.triggers || []);
|
||||
// Drafts created before lazy trigger loading always contained live trigger metadata.
|
||||
triggersLoaded.value = draft.triggersLoaded ?? true;
|
||||
@@ -964,6 +978,7 @@ function restoreDraft(draft: TableStructureEditorDraft) {
|
||||
if (activeScope.columns) loadedMetadataFacets.add("columns");
|
||||
if (activeScope.indexes || draft.indexes?.length) loadedMetadataFacets.add("indexes");
|
||||
if (activeScope.foreignKeys || draft.foreignKeys?.length) loadedMetadataFacets.add("foreign-keys");
|
||||
if (activeScope.constraints || constraintsLoaded.value) loadedMetadataFacets.add("constraints");
|
||||
if (activeScope.triggers || triggersLoaded.value) loadedMetadataFacets.add("triggers");
|
||||
if (activeScope.tableComment) loadedMetadataFacets.add("comment");
|
||||
}
|
||||
@@ -1226,6 +1241,7 @@ function resetState() {
|
||||
sqlPreviewPending.value = false;
|
||||
indexesLoading.value = false;
|
||||
foreignKeysLoading.value = false;
|
||||
constraintsLoading.value = false;
|
||||
triggersLoading.value = false;
|
||||
errorMessage.value = "";
|
||||
columns.value = [];
|
||||
@@ -1234,6 +1250,8 @@ function resetState() {
|
||||
warnings.value = [];
|
||||
sqliteSchemaRevision.value = undefined;
|
||||
foreignKeys.value = [];
|
||||
constraints.value = [];
|
||||
constraintsLoaded.value = false;
|
||||
triggers.value = [];
|
||||
triggersLoaded.value = false;
|
||||
selectedColumnId.value = null;
|
||||
@@ -1259,6 +1277,10 @@ async function reloadStructureFromDatabase() {
|
||||
triggers.value = [];
|
||||
triggersLoaded.value = false;
|
||||
}
|
||||
if (activeTab.value !== "constraints") {
|
||||
constraints.value = [];
|
||||
constraintsLoaded.value = false;
|
||||
}
|
||||
const refreshDdl = activeTab.value === "ddl";
|
||||
const metadataMatch = { connectionId: props.connectionId, database: props.database, schema: metadataSchema.value, tableName: props.tableName };
|
||||
invalidateTableMetadataCache(metadataMatch);
|
||||
@@ -1275,6 +1297,7 @@ async function reloadStructureFromDatabase() {
|
||||
function setSecondaryMetadataLoading(scope: TableStructureRefreshScope, value: boolean) {
|
||||
if (scope.indexes && tableMetadataCapabilities.value.indexes) indexesLoading.value = value;
|
||||
if (scope.foreignKeys && tableMetadataCapabilities.value.foreignKeys) foreignKeysLoading.value = value;
|
||||
if (scope.constraints && tableMetadataCapabilities.value.constraints) constraintsLoading.value = value;
|
||||
if (scope.triggers && tableMetadataCapabilities.value.triggers) triggersLoading.value = value;
|
||||
}
|
||||
|
||||
@@ -1330,6 +1353,11 @@ async function loadStructure(
|
||||
? loadObjectMetadataFacet(metadataRequest, "foreign-keys", () => api.listForeignKeys(connectionId, database, schema, tableName, catalog).catch(() => []), { force: forceMetadata }).then((result) => result.value)
|
||||
: Promise.resolve([])
|
||||
: Promise.resolve(undefined);
|
||||
const constraintsPromise = scope.constraints
|
||||
? tableMetadataCapabilities.value.constraints
|
||||
? loadObjectMetadataFacet(metadataRequest, "constraints", () => api.listConstraints(connectionId, database, schema, tableName, catalog).catch(() => []), { force: forceMetadata }).then((result) => result.value)
|
||||
: Promise.resolve([])
|
||||
: Promise.resolve(undefined);
|
||||
const triggersPromise = scope.triggers
|
||||
? tableMetadataCapabilities.value.triggers
|
||||
? loadObjectMetadataFacet(metadataRequest, "triggers", () => api.listTriggers(connectionId, database, schema, tableName, catalog).catch(() => []), { force: forceMetadata }).then((result) => result.value)
|
||||
@@ -1366,7 +1394,7 @@ async function loadStructure(
|
||||
loadedMetadataFacets.add("comment");
|
||||
}
|
||||
const applySecondaryMetadata = async () => {
|
||||
const [nextIndexes, nextForeignKeys, nextTriggers] = await Promise.all([indexesPromise, foreignKeysPromise, triggersPromise]);
|
||||
const [nextIndexes, nextForeignKeys, nextConstraints, nextTriggers] = await Promise.all([indexesPromise, foreignKeysPromise, constraintsPromise, triggersPromise]);
|
||||
if (requestId !== structureLoadRequestId) return;
|
||||
if (nextIndexes) {
|
||||
indexes.value = createIndexDrafts(nextIndexes);
|
||||
@@ -1376,6 +1404,11 @@ async function loadStructure(
|
||||
foreignKeys.value = createForeignKeyDrafts(nextForeignKeys);
|
||||
loadedMetadataFacets.add("foreign-keys");
|
||||
}
|
||||
if (nextConstraints) {
|
||||
constraints.value = nextConstraints;
|
||||
constraintsLoaded.value = true;
|
||||
loadedMetadataFacets.add("constraints");
|
||||
}
|
||||
if (nextTriggers) {
|
||||
triggers.value = createTriggerDrafts(nextTriggers);
|
||||
triggersLoaded.value = true;
|
||||
@@ -2605,6 +2638,10 @@ watch(refreshVersion, (version, previous) => {
|
||||
triggers.value = [];
|
||||
triggersLoaded.value = false;
|
||||
}
|
||||
if (activeTab.value !== "constraints") {
|
||||
constraints.value = [];
|
||||
constraintsLoaded.value = false;
|
||||
}
|
||||
void loadStructure(true, visibleTableStructureRefreshScope(activeTab.value));
|
||||
});
|
||||
|
||||
@@ -2674,6 +2711,7 @@ watch([activeTab, ddlLoading], ([tab, loading]) => {
|
||||
<TabsTrigger v-if="tableMetadataCapabilities.columns" value="columns">{{ t("structureEditor.columns") }}</TabsTrigger>
|
||||
<TabsTrigger v-if="tableMetadataCapabilities.indexes" value="indexes">{{ t("structureEditor.indexes") }}</TabsTrigger>
|
||||
<TabsTrigger v-if="tableMetadataCapabilities.foreignKeys" value="foreignKeys">{{ t("structureEditor.foreignKeys") }}</TabsTrigger>
|
||||
<TabsTrigger v-if="tableMetadataCapabilities.constraints" value="constraints">{{ t("structureEditor.constraints") }}</TabsTrigger>
|
||||
<TabsTrigger v-if="tableMetadataCapabilities.triggers" value="triggers">{{ t("structureEditor.triggers") }}</TabsTrigger>
|
||||
</TabsList>
|
||||
<div class="flex shrink-0 items-center gap-1.5">
|
||||
@@ -3302,6 +3340,29 @@ watch([activeTab, ddlLoading], ([tab, loading]) => {
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent ref="constraintsScrollerRef" v-if="tableMetadataCapabilities.constraints" value="constraints" class="m-0 min-h-0 flex-1 overflow-auto p-[var(--structure-cell-px)]" @scroll.passive="onStructureContentScroll('constraints', $event)">
|
||||
<div v-if="constraintsLoading" class="flex items-center justify-center gap-2 py-10 text-muted-foreground">
|
||||
<Loader2 class="h-4 w-4 animate-spin" />
|
||||
{{ t("common.loading") }}
|
||||
</div>
|
||||
<div v-else-if="constraints.length === 0" class="py-10 text-center text-muted-foreground">
|
||||
{{ t("structureEditor.emptyReadonly") }}
|
||||
</div>
|
||||
<div v-else class="space-y-1.5">
|
||||
<div v-for="constraint in constraints" :key="constraint.name" class="rounded-md border px-[var(--structure-cell-px)] py-[var(--structure-header-py)] text-[length:var(--structure-font-size)]" :class="constraint.enabled ? '' : 'opacity-60'">
|
||||
<div class="flex flex-wrap items-center gap-1.5">
|
||||
<span class="font-mono font-medium">{{ constraint.name }}</span>
|
||||
<Badge variant="outline" class="shrink-0">{{ constraint.constraint_type }}</Badge>
|
||||
<Badge v-if="!constraint.enabled" variant="outline" class="shrink-0 text-muted-foreground">{{ t("structureEditor.constraintDisabled") }}</Badge>
|
||||
<Badge v-else-if="!constraint.valid" variant="outline" class="shrink-0 text-muted-foreground">{{ t("structureEditor.constraintNotValidated") }}</Badge>
|
||||
</div>
|
||||
<div v-if="constraint.columns.length" class="mt-1 truncate font-mono text-muted-foreground">{{ constraint.columns.join(", ") }}</div>
|
||||
<div v-if="constraint.ref_table" class="mt-1 truncate font-mono text-muted-foreground">-> {{ constraint.ref_schema ? `${constraint.ref_schema}.` : "" }}{{ constraint.ref_table }}{{ constraint.ref_columns.length ? `(${constraint.ref_columns.join(", ")})` : "" }}</div>
|
||||
<div v-if="constraint.definition" class="mt-1 whitespace-pre-wrap break-words font-mono text-muted-foreground">{{ constraint.definition }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent ref="triggersScrollerRef" v-if="tableMetadataCapabilities.triggers" value="triggers" class="m-0 min-h-0 flex-1 overflow-auto p-[var(--structure-cell-px)]" @scroll.passive="onStructureContentScroll('triggers', $event)">
|
||||
<div v-if="triggersLoading" class="flex items-center justify-center gap-2 py-10 text-muted-foreground">
|
||||
<Loader2 class="h-4 w-4 animate-spin" />
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const source = readFileSync(new URL("../TableStructureEditor.vue", import.meta.url), "utf8");
|
||||
|
||||
describe("TableStructureEditor constraints", () => {
|
||||
it("renders complete check expressions instead of truncating them", () => {
|
||||
expect(source).toContain('class="mt-1 whitespace-pre-wrap break-words font-mono text-muted-foreground">{{ constraint.definition }}');
|
||||
expect(source).not.toContain('class="mt-1 truncate font-mono text-muted-foreground">{{ constraint.definition }}');
|
||||
});
|
||||
});
|
||||
@@ -3430,6 +3430,9 @@ export default {
|
||||
columns: "Columns",
|
||||
indexes: "Indexes",
|
||||
foreignKeys: "Foreign Keys",
|
||||
constraints: "Constraints",
|
||||
constraintDisabled: "Disabled",
|
||||
constraintNotValidated: "Not validated",
|
||||
triggers: "Triggers",
|
||||
ddl: "DDL",
|
||||
addColumn: "Add Column",
|
||||
|
||||
@@ -3371,6 +3371,9 @@ export default withEnglishFallback({
|
||||
columns: "Columnas",
|
||||
indexes: "Índices",
|
||||
foreignKeys: "Claves foráneas",
|
||||
constraints: "Restricciones",
|
||||
constraintDisabled: "Deshabilitada",
|
||||
constraintNotValidated: "No validada",
|
||||
triggers: "Disparadores",
|
||||
ddl: "DDL",
|
||||
addColumn: "Agregar columna",
|
||||
|
||||
@@ -3369,6 +3369,9 @@ export default withEnglishFallback({
|
||||
columns: "Colonne",
|
||||
indexes: "Indici",
|
||||
foreignKeys: "Chiavi Esterne",
|
||||
constraints: "Vincoli",
|
||||
constraintDisabled: "Disabilitato",
|
||||
constraintNotValidated: "Non convalidato",
|
||||
triggers: "Trigger",
|
||||
ddl: "DDL",
|
||||
addColumn: "Aggiungi Colonna",
|
||||
|
||||
@@ -3430,6 +3430,9 @@ export default withEnglishFallback({
|
||||
columns: "列",
|
||||
indexes: "インデックス",
|
||||
foreignKeys: "外部キー",
|
||||
constraints: "制約",
|
||||
constraintDisabled: "無効",
|
||||
constraintNotValidated: "未検証",
|
||||
triggers: "トリガー",
|
||||
ddl: "DDL",
|
||||
addColumn: "列を追加",
|
||||
|
||||
@@ -3256,6 +3256,9 @@ export default withEnglishFallback({
|
||||
columns: "컬럼",
|
||||
indexes: "인덱스",
|
||||
foreignKeys: "외래 키",
|
||||
constraints: "제약 조건",
|
||||
constraintDisabled: "비활성화됨",
|
||||
constraintNotValidated: "검증되지 않음",
|
||||
triggers: "트리거",
|
||||
ddl: "DDL",
|
||||
addColumn: "컬럼 추가",
|
||||
|
||||
@@ -3371,6 +3371,9 @@ export default withEnglishFallback({
|
||||
columns: "Colunas",
|
||||
indexes: "Índices",
|
||||
foreignKeys: "Chaves estrangeiras",
|
||||
constraints: "Restrições",
|
||||
constraintDisabled: "Desabilitada",
|
||||
constraintNotValidated: "Não validada",
|
||||
triggers: "Triggers",
|
||||
ddl: "DDL",
|
||||
addColumn: "Adicionar coluna",
|
||||
|
||||
@@ -3418,6 +3418,9 @@ export default withEnglishFallback({
|
||||
columns: "字段",
|
||||
indexes: "索引",
|
||||
foreignKeys: "外键",
|
||||
constraints: "约束",
|
||||
constraintDisabled: "已禁用",
|
||||
constraintNotValidated: "未验证",
|
||||
triggers: "触发器",
|
||||
ddl: "DDL",
|
||||
addColumn: "新增字段",
|
||||
|
||||
@@ -3131,6 +3131,9 @@ export default withEnglishFallback({
|
||||
columns: "欄位",
|
||||
indexes: "索引",
|
||||
foreignKeys: "外鍵",
|
||||
constraints: "約束",
|
||||
constraintDisabled: "已停用",
|
||||
constraintNotValidated: "未驗證",
|
||||
triggers: "觸發器",
|
||||
ddl: "DDL",
|
||||
addColumn: "新增欄位",
|
||||
|
||||
+13
-7
@@ -3,11 +3,12 @@ import { hasTableStructureRefreshWork, unloadedTableStructureRefreshScope, visib
|
||||
|
||||
describe("table structure metadata loading", () => {
|
||||
it.each([
|
||||
["columns", { columns: true, indexes: false, foreignKeys: false, triggers: false, tableComment: true }],
|
||||
["indexes", { columns: true, indexes: true, foreignKeys: false, triggers: false, tableComment: true }],
|
||||
["foreignKeys", { columns: true, indexes: false, foreignKeys: true, triggers: false, tableComment: true }],
|
||||
["triggers", { columns: false, indexes: false, foreignKeys: false, triggers: true, tableComment: true }],
|
||||
["ddl", { columns: false, indexes: false, foreignKeys: false, triggers: false, tableComment: false }],
|
||||
["columns", { columns: true, indexes: false, foreignKeys: false, constraints: false, triggers: false, tableComment: true }],
|
||||
["indexes", { columns: true, indexes: true, foreignKeys: false, constraints: false, triggers: false, tableComment: true }],
|
||||
["foreignKeys", { columns: true, indexes: false, foreignKeys: true, constraints: false, triggers: false, tableComment: true }],
|
||||
["constraints", { columns: false, indexes: false, foreignKeys: false, constraints: true, triggers: false, tableComment: true }],
|
||||
["triggers", { columns: false, indexes: false, foreignKeys: false, constraints: false, triggers: true, tableComment: true }],
|
||||
["ddl", { columns: false, indexes: false, foreignKeys: false, constraints: false, triggers: false, tableComment: false }],
|
||||
] as const)("requests only the metadata required by the %s tab", (tab, expected) => {
|
||||
expect(visibleTableStructureRefreshScope(tab)).toEqual(expected);
|
||||
});
|
||||
@@ -15,13 +16,18 @@ describe("table structure metadata loading", () => {
|
||||
it("requests only index metadata after columns and comments are already loaded", () => {
|
||||
const scope = unloadedTableStructureRefreshScope("indexes", new Set(["columns", "comment"]));
|
||||
|
||||
expect(scope).toEqual({ columns: false, indexes: true, foreignKeys: false, triggers: false, tableComment: false });
|
||||
expect(scope).toEqual({ columns: false, indexes: true, foreignKeys: false, constraints: false, triggers: false, tableComment: false });
|
||||
expect(hasTableStructureRefreshWork(scope)).toBe(true);
|
||||
expect(hasTableStructureRefreshWork(unloadedTableStructureRefreshScope("indexes", new Set(["columns", "indexes", "comment"])))).toBe(false);
|
||||
});
|
||||
|
||||
it("requests trigger metadata only until that facet is loaded", () => {
|
||||
expect(unloadedTableStructureRefreshScope("triggers", new Set(["comment"]))).toEqual({ columns: false, indexes: false, foreignKeys: false, triggers: true, tableComment: false });
|
||||
expect(unloadedTableStructureRefreshScope("triggers", new Set(["comment"]))).toEqual({ columns: false, indexes: false, foreignKeys: false, constraints: false, triggers: true, tableComment: false });
|
||||
expect(hasTableStructureRefreshWork(unloadedTableStructureRefreshScope("triggers", new Set(["triggers", "comment"])))).toBe(false);
|
||||
});
|
||||
|
||||
it("requests constraint metadata only until that facet is loaded", () => {
|
||||
expect(unloadedTableStructureRefreshScope("constraints", new Set(["comment"]))).toEqual({ columns: false, indexes: false, foreignKeys: false, constraints: true, triggers: false, tableComment: false });
|
||||
expect(hasTableStructureRefreshWork(unloadedTableStructureRefreshScope("constraints", new Set(["constraints", "comment"])))).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -44,7 +44,7 @@ async function deleteSchemaCachePrefixSafe(prefix: string): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
export type ObjectMetadataFacet = "columns" | "indexes" | "foreign-keys" | "triggers" | "comment";
|
||||
export type ObjectMetadataFacet = "columns" | "indexes" | "foreign-keys" | "constraints" | "triggers" | "comment";
|
||||
|
||||
function cacheSegment(value: string | undefined): string {
|
||||
return encodeURIComponent(value ?? "");
|
||||
|
||||
@@ -7,8 +7,15 @@ describe("tableMetadataCapabilities", () => {
|
||||
columns: false,
|
||||
indexes: true,
|
||||
foreignKeys: false,
|
||||
constraints: false,
|
||||
triggers: false,
|
||||
ddl: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("exposes structured constraints only for dialects that implement list_constraints", () => {
|
||||
expect(getTableMetadataCapabilities("oracle").constraints).toBe(true);
|
||||
expect(getTableMetadataCapabilities("mysql").constraints).toBe(false);
|
||||
expect(getTableMetadataCapabilities(undefined).constraints).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -4,6 +4,7 @@ export interface TableMetadataCapabilities {
|
||||
columns: boolean;
|
||||
indexes: boolean;
|
||||
foreignKeys: boolean;
|
||||
constraints: boolean;
|
||||
triggers: boolean;
|
||||
ddl: boolean;
|
||||
}
|
||||
@@ -12,11 +13,18 @@ const defaultCapabilities: TableMetadataCapabilities = {
|
||||
columns: true,
|
||||
indexes: true,
|
||||
foreignKeys: true,
|
||||
// Structured PK/UNIQUE/CHECK constraint metadata (list_constraints) is only
|
||||
// implemented by a handful of agent-backed drivers today; leave it off by
|
||||
// default so every other dialect doesn't grow a permanently empty tab.
|
||||
constraints: false,
|
||||
triggers: true,
|
||||
ddl: true,
|
||||
};
|
||||
|
||||
const capabilityByType: Partial<Record<DatabaseType, Partial<TableMetadataCapabilities>>> = {
|
||||
oracle: {
|
||||
constraints: true,
|
||||
},
|
||||
mongodb: {
|
||||
columns: false,
|
||||
foreignKeys: false,
|
||||
@@ -108,11 +116,19 @@ export function firstStructureMetadataTab(capabilities: TableMetadataCapabilitie
|
||||
if (capabilities.columns) return "columns";
|
||||
if (capabilities.indexes) return "indexes";
|
||||
if (capabilities.foreignKeys) return "foreignKeys";
|
||||
if (capabilities.constraints) return "constraints";
|
||||
if (capabilities.triggers) return "triggers";
|
||||
if (!isCreateMode && capabilities.ddl) return "ddl";
|
||||
return "columns";
|
||||
}
|
||||
|
||||
export function isStructureMetadataTabSupported(tab: TableInfoTab, capabilities: TableMetadataCapabilities, isCreateMode: boolean): boolean {
|
||||
return (tab === "columns" && capabilities.columns) || (tab === "indexes" && capabilities.indexes) || (tab === "foreignKeys" && capabilities.foreignKeys) || (tab === "triggers" && capabilities.triggers) || (tab === "ddl" && capabilities.ddl && !isCreateMode);
|
||||
return (
|
||||
(tab === "columns" && capabilities.columns) ||
|
||||
(tab === "indexes" && capabilities.indexes) ||
|
||||
(tab === "foreignKeys" && capabilities.foreignKeys) ||
|
||||
(tab === "constraints" && capabilities.constraints) ||
|
||||
(tab === "triggers" && capabilities.triggers) ||
|
||||
(tab === "ddl" && capabilities.ddl && !isCreateMode)
|
||||
);
|
||||
}
|
||||
@@ -5,6 +5,7 @@ export interface TableStructureRefreshScope {
|
||||
columns: boolean;
|
||||
indexes: boolean;
|
||||
foreignKeys: boolean;
|
||||
constraints: boolean;
|
||||
triggers: boolean;
|
||||
tableComment: boolean;
|
||||
}
|
||||
@@ -12,15 +13,17 @@ export interface TableStructureRefreshScope {
|
||||
export function visibleTableStructureRefreshScope(activeTab: TableInfoTab): TableStructureRefreshScope {
|
||||
switch (activeTab) {
|
||||
case "columns":
|
||||
return { columns: true, indexes: false, foreignKeys: false, triggers: false, tableComment: true };
|
||||
return { columns: true, indexes: false, foreignKeys: false, constraints: false, triggers: false, tableComment: true };
|
||||
case "indexes":
|
||||
return { columns: true, indexes: true, foreignKeys: false, triggers: false, tableComment: true };
|
||||
return { columns: true, indexes: true, foreignKeys: false, constraints: false, triggers: false, tableComment: true };
|
||||
case "foreignKeys":
|
||||
return { columns: true, indexes: false, foreignKeys: true, triggers: false, tableComment: true };
|
||||
return { columns: true, indexes: false, foreignKeys: true, constraints: false, triggers: false, tableComment: true };
|
||||
case "constraints":
|
||||
return { columns: false, indexes: false, foreignKeys: false, constraints: true, triggers: false, tableComment: true };
|
||||
case "triggers":
|
||||
return { columns: false, indexes: false, foreignKeys: false, triggers: true, tableComment: true };
|
||||
return { columns: false, indexes: false, foreignKeys: false, constraints: false, triggers: true, tableComment: true };
|
||||
case "ddl":
|
||||
return { columns: false, indexes: false, foreignKeys: false, triggers: false, tableComment: false };
|
||||
return { columns: false, indexes: false, foreignKeys: false, constraints: false, triggers: false, tableComment: false };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,11 +33,12 @@ export function unloadedTableStructureRefreshScope(activeTab: TableInfoTab, load
|
||||
columns: visibleScope.columns && !loadedFacets.has("columns"),
|
||||
indexes: visibleScope.indexes && !loadedFacets.has("indexes"),
|
||||
foreignKeys: visibleScope.foreignKeys && !loadedFacets.has("foreign-keys"),
|
||||
constraints: visibleScope.constraints && !loadedFacets.has("constraints"),
|
||||
triggers: visibleScope.triggers && !loadedFacets.has("triggers"),
|
||||
tableComment: visibleScope.tableComment && !loadedFacets.has("comment"),
|
||||
};
|
||||
}
|
||||
|
||||
export function hasTableStructureRefreshWork(scope: TableStructureRefreshScope): boolean {
|
||||
return scope.columns || scope.indexes || scope.foreignKeys || scope.triggers || scope.tableComment;
|
||||
return scope.columns || scope.indexes || scope.foreignKeys || scope.constraints || scope.triggers || scope.tableComment;
|
||||
}
|
||||
@@ -1019,7 +1019,7 @@ function normalizeToolbarItems(items: Partial<ToolbarItems> | undefined): Toolba
|
||||
};
|
||||
}
|
||||
|
||||
const TABLE_INFO_TABS = new Set<TableInfoTab>(["ddl", "columns", "indexes", "foreignKeys", "triggers"]);
|
||||
const TABLE_INFO_TABS = new Set<TableInfoTab>(["ddl", "columns", "indexes", "foreignKeys", "constraints", "triggers"]);
|
||||
|
||||
function normalizeTableInfoTab(value: unknown): TableInfoTab {
|
||||
return typeof value === "string" && TABLE_INFO_TABS.has(value as TableInfoTab) ? (value as TableInfoTab) : DEFAULT_EDITOR_SETTINGS.tableInfoActiveTab;
|
||||
|
||||
@@ -1037,7 +1037,7 @@ export interface TableNameFilter {
|
||||
excludePatterns: string[];
|
||||
}
|
||||
|
||||
export type TableInfoTab = "columns" | "indexes" | "foreignKeys" | "triggers" | "ddl";
|
||||
export type TableInfoTab = "columns" | "indexes" | "foreignKeys" | "constraints" | "triggers" | "ddl";
|
||||
|
||||
export interface TableStructureEditorTarget {
|
||||
kind: "column" | "index";
|
||||
@@ -1053,6 +1053,8 @@ export interface TableStructureEditorDraft {
|
||||
columns: import("@/lib/table/tableStructureEditorSql").EditableStructureColumn[];
|
||||
indexes: import("@/lib/table/tableStructureEditorSql").EditableStructureIndex[];
|
||||
foreignKeys: import("@/lib/table/tableStructureEditorSql").EditableStructureForeignKey[];
|
||||
constraints?: ConstraintInfo[];
|
||||
constraintsLoaded?: boolean;
|
||||
triggers: import("@/lib/table/tableStructureEditorSql").EditableStructureTrigger[];
|
||||
triggersLoaded?: boolean;
|
||||
loadedMetadataFacets?: import("@/lib/metadata/objectMetadataCache").ObjectMetadataFacet[];
|
||||
|
||||
Reference in new issue
Block a user