mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-06-19 04:59:43 +08:00
- 结果区状态按 SQL Tab 独立保存,快捷键可恢复手动隐藏面板 - 对象设计保留完整字段类型和可空信息,完善兼容驱动 DDL 元数据 - 数据编辑新增手动/自动提交设置和自动提交倒计时 - 修复 schema 视图定位时找不到左侧树节点的问题
555 lines
21 KiB
TypeScript
555 lines
21 KiB
TypeScript
import { splitQualifiedNameLast } from './qualifiedName';
|
|
|
|
export type SidebarLocateObjectGroup = 'tables' | 'views' | 'materializedViews' | 'triggers' | 'routines' | 'externalSqlFiles';
|
|
export type SidebarLocateDatabaseObjectGroup = Exclude<SidebarLocateObjectGroup, 'externalSqlFiles'>;
|
|
|
|
export interface SidebarLocateDatabaseObjectRequest {
|
|
tabId?: string;
|
|
connectionId: string;
|
|
dbName: string;
|
|
tableName: string;
|
|
schemaName?: string;
|
|
objectGroup: SidebarLocateDatabaseObjectGroup;
|
|
}
|
|
|
|
export interface SidebarLocateExternalSQLFileRequest {
|
|
tabId?: string;
|
|
connectionId?: string;
|
|
dbName?: string;
|
|
filePath: string;
|
|
fileName?: string;
|
|
objectGroup: 'externalSqlFiles';
|
|
}
|
|
|
|
export type SidebarLocateObjectRequest = SidebarLocateDatabaseObjectRequest | SidebarLocateExternalSQLFileRequest;
|
|
|
|
export interface SidebarLocateTarget {
|
|
connectionKey: string;
|
|
databaseKey: string;
|
|
targetKey: string;
|
|
objectGroup: SidebarLocateObjectGroup;
|
|
objectGroupKey: string;
|
|
schemaKey?: string;
|
|
expectedAncestorKeys: string[];
|
|
connectionId: string;
|
|
dbName: string;
|
|
tableName: string;
|
|
schemaName: string;
|
|
filePath?: string;
|
|
}
|
|
|
|
export interface SidebarLocateTreeNodeLike {
|
|
key: string | number;
|
|
title?: unknown;
|
|
type?: string;
|
|
dataRef?: Record<string, any>;
|
|
children?: SidebarLocateTreeNodeLike[];
|
|
}
|
|
|
|
export interface SidebarLocateTabLike {
|
|
id?: string;
|
|
type?: string;
|
|
connectionId?: string;
|
|
dbName?: string;
|
|
tableName?: string;
|
|
viewName?: string;
|
|
viewKind?: string;
|
|
triggerName?: string;
|
|
triggerTableName?: string;
|
|
routineName?: string;
|
|
schemaName?: string;
|
|
sidebarLocateKey?: string;
|
|
filePath?: string;
|
|
objectType?: string;
|
|
}
|
|
|
|
const toTrimmedString = (value: unknown): string => String(value ?? '').trim();
|
|
const normalizeLocateName = (value: string): string => toTrimmedString(value).toLowerCase();
|
|
|
|
const normalizeExternalSQLLocatePath = (value: unknown): string => toTrimmedString(value).replace(/\\/g, '/');
|
|
|
|
export const splitSidebarQualifiedName = (qualifiedName: string): { schemaName: string; objectName: string } => {
|
|
const raw = toTrimmedString(qualifiedName);
|
|
if (!raw) return { schemaName: '', objectName: '' };
|
|
const parsed = splitQualifiedNameLast(raw);
|
|
return {
|
|
schemaName: parsed.parentPath,
|
|
objectName: parsed.objectName,
|
|
};
|
|
};
|
|
|
|
const inferObjectGroup = (detail: Record<string, unknown>, connectionId: string, dbName: string): SidebarLocateDatabaseObjectGroup => {
|
|
const explicitGroup = toTrimmedString(detail.objectGroup);
|
|
if (explicitGroup === 'views' || explicitGroup === 'view') return 'views';
|
|
if (explicitGroup === 'materializedViews' || explicitGroup === 'materialized-view') return 'materializedViews';
|
|
if (explicitGroup === 'triggers' || explicitGroup === 'trigger') return 'triggers';
|
|
if (explicitGroup === 'routines' || explicitGroup === 'routine') return 'routines';
|
|
|
|
const explicitType = toTrimmedString(detail.objectType);
|
|
if (explicitType === 'view' || explicitType === 'views') return 'views';
|
|
if (explicitType === 'materialized' || explicitType === 'materialized-view') return 'materializedViews';
|
|
if (explicitType === 'trigger' || explicitType === 'triggers') return 'triggers';
|
|
if (explicitType === 'routine' || explicitType === 'routines') return 'routines';
|
|
|
|
const tabId = toTrimmedString(detail.tabId);
|
|
const dbNodeKey = `${connectionId}-${dbName}`;
|
|
if (tabId.startsWith(`${dbNodeKey}-materialized-view-`)) return 'materializedViews';
|
|
if (tabId.startsWith(`${dbNodeKey}-view-`)) return 'views';
|
|
if (tabId.startsWith(`${dbNodeKey}-trigger-`)) return 'triggers';
|
|
if (tabId.startsWith(`${dbNodeKey}-routine-`) || tabId.startsWith(`routine-def-${connectionId}-${dbName}-`)) return 'routines';
|
|
|
|
return 'tables';
|
|
};
|
|
|
|
export const normalizeSidebarLocateObjectRequest = (detail: unknown): SidebarLocateObjectRequest | null => {
|
|
const raw = (detail || {}) as Record<string, unknown>;
|
|
const filePath = normalizeExternalSQLLocatePath(raw.filePath);
|
|
if (filePath) {
|
|
return {
|
|
tabId: toTrimmedString(raw.tabId) || undefined,
|
|
connectionId: toTrimmedString(raw.connectionId) || undefined,
|
|
dbName: toTrimmedString(raw.dbName) || undefined,
|
|
filePath,
|
|
fileName: toTrimmedString(raw.fileName || raw.title) || undefined,
|
|
objectGroup: 'externalSqlFiles',
|
|
};
|
|
}
|
|
|
|
const connectionId = toTrimmedString(raw.connectionId);
|
|
const dbName = toTrimmedString(raw.dbName);
|
|
const tableName = toTrimmedString(raw.tableName || raw.objectName || raw.viewName || raw.triggerName || raw.routineName);
|
|
|
|
if (!connectionId || !dbName || !tableName) {
|
|
return null;
|
|
}
|
|
|
|
const parsed = splitSidebarQualifiedName(tableName);
|
|
const schemaName = toTrimmedString(raw.schemaName) || parsed.schemaName;
|
|
|
|
return {
|
|
tabId: toTrimmedString(raw.tabId) || undefined,
|
|
connectionId,
|
|
dbName,
|
|
tableName,
|
|
schemaName,
|
|
objectGroup: inferObjectGroup(raw, connectionId, dbName),
|
|
};
|
|
};
|
|
|
|
export const normalizeSidebarLocateObjectRequestFromTab = (tab: SidebarLocateTabLike | null | undefined): SidebarLocateObjectRequest | null => {
|
|
if (!tab) return null;
|
|
const filePath = normalizeExternalSQLLocatePath(tab.filePath);
|
|
if (tab.type === 'query' && filePath) {
|
|
return normalizeSidebarLocateObjectRequest({
|
|
tabId: tab.id,
|
|
connectionId: tab.connectionId,
|
|
dbName: tab.dbName,
|
|
filePath,
|
|
fileName: tab.id,
|
|
});
|
|
}
|
|
|
|
const objectName = tab.type === 'view-def'
|
|
? toTrimmedString(tab.viewName || tab.tableName)
|
|
: tab.type === 'trigger'
|
|
? toTrimmedString(tab.triggerName || tab.tableName)
|
|
: tab.type === 'routine-def'
|
|
? toTrimmedString(tab.routineName || tab.tableName)
|
|
: toTrimmedString(tab.tableName || tab.viewName);
|
|
if (tab.type !== 'table' && tab.type !== 'view-def' && tab.type !== 'trigger' && tab.type !== 'routine-def') {
|
|
return null;
|
|
}
|
|
|
|
return normalizeSidebarLocateObjectRequest({
|
|
tabId: toTrimmedString(tab.sidebarLocateKey || tab.id) || undefined,
|
|
connectionId: tab.connectionId,
|
|
dbName: tab.dbName,
|
|
tableName: objectName,
|
|
schemaName: tab.schemaName,
|
|
objectGroup: tab.type === 'view-def'
|
|
? (tab.viewKind === 'materialized' ? 'materializedViews' : 'views')
|
|
: (tab.type === 'trigger'
|
|
? 'triggers'
|
|
: (tab.type === 'routine-def'
|
|
? 'routines'
|
|
: (tab.objectType === 'materialized-view' ? 'materializedViews' : (tab.objectType === 'view' ? 'views' : undefined)))),
|
|
});
|
|
};
|
|
|
|
export const resolveSidebarLocateTarget = (
|
|
request: SidebarLocateObjectRequest,
|
|
options: { groupBySchema: boolean },
|
|
): SidebarLocateTarget => {
|
|
if (request.objectGroup === 'externalSqlFiles') {
|
|
const filePath = normalizeExternalSQLLocatePath(request.filePath);
|
|
return {
|
|
connectionKey: toTrimmedString(request.connectionId),
|
|
databaseKey: request.connectionId && request.dbName ? `${request.connectionId}-${request.dbName}` : '',
|
|
targetKey: request.tabId || filePath,
|
|
objectGroup: 'externalSqlFiles',
|
|
objectGroupKey: 'external-sql-root',
|
|
expectedAncestorKeys: ['external-sql-root'],
|
|
connectionId: toTrimmedString(request.connectionId),
|
|
dbName: toTrimmedString(request.dbName),
|
|
tableName: request.fileName || filePath.split('/').filter(Boolean).pop() || filePath,
|
|
schemaName: '',
|
|
filePath,
|
|
};
|
|
}
|
|
|
|
const connectionKey = request.connectionId;
|
|
const databaseKey = `${request.connectionId}-${request.dbName}`;
|
|
const fallbackTargetKey = request.objectGroup === 'materializedViews'
|
|
? `${databaseKey}-materialized-view-${request.tableName}`
|
|
: request.objectGroup === 'views'
|
|
? `${databaseKey}-view-${request.tableName}`
|
|
: request.objectGroup === 'triggers'
|
|
? `${databaseKey}-trigger-${request.tableName}`
|
|
: request.objectGroup === 'routines'
|
|
? `${databaseKey}-routine-${request.tableName}`
|
|
: `${databaseKey}-${request.tableName}`;
|
|
const targetKey = request.tabId || fallbackTargetKey;
|
|
const schemaSegment = request.schemaName || 'default';
|
|
const schemaKey = options.groupBySchema ? `${databaseKey}-schema-${schemaSegment}` : undefined;
|
|
const objectGroupKey = options.groupBySchema
|
|
? `${schemaKey}-${request.objectGroup}`
|
|
: `${databaseKey}-${request.objectGroup}`;
|
|
const expectedAncestorKeys = [
|
|
connectionKey,
|
|
databaseKey,
|
|
...(schemaKey ? [schemaKey] : []),
|
|
objectGroupKey,
|
|
];
|
|
|
|
return {
|
|
connectionKey,
|
|
databaseKey,
|
|
targetKey,
|
|
objectGroup: request.objectGroup,
|
|
objectGroupKey,
|
|
schemaKey,
|
|
expectedAncestorKeys,
|
|
connectionId: request.connectionId,
|
|
dbName: request.dbName,
|
|
tableName: request.tableName,
|
|
schemaName: request.schemaName || '',
|
|
};
|
|
};
|
|
|
|
export const findSidebarNodePathByKey = (
|
|
nodes: SidebarLocateTreeNodeLike[],
|
|
targetKey: string,
|
|
): string[] | null => {
|
|
for (const node of nodes) {
|
|
const nodeKey = String(node.key);
|
|
if (nodeKey === targetKey) {
|
|
return [nodeKey];
|
|
}
|
|
|
|
if (node.children) {
|
|
const childPath = findSidebarNodePathByKey(node.children, targetKey);
|
|
if (childPath) {
|
|
return [nodeKey, ...childPath];
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
};
|
|
|
|
const matchesLocateObjectName = (
|
|
target: SidebarLocateTarget,
|
|
nodeObjectName: string,
|
|
nodeSchemaName: string,
|
|
options: { allowUnqualifiedSchemaMatch?: boolean } = {},
|
|
): boolean => {
|
|
const normalizedNodeName = toTrimmedString(nodeObjectName);
|
|
if (!normalizedNodeName) return false;
|
|
|
|
const nodeParsed = splitSidebarQualifiedName(normalizedNodeName);
|
|
const targetParsed = splitSidebarQualifiedName(target.tableName);
|
|
const nodeObject = nodeParsed.objectName || normalizedNodeName;
|
|
const targetObject = targetParsed.objectName || target.tableName;
|
|
const resolvedNodeSchema = toTrimmedString(nodeSchemaName) || nodeParsed.schemaName;
|
|
const resolvedTargetSchema = toTrimmedString(target.schemaName) || targetParsed.schemaName;
|
|
|
|
if (
|
|
resolvedTargetSchema
|
|
&& !resolvedNodeSchema
|
|
&& normalizeLocateName(resolvedTargetSchema) === normalizeLocateName(target.dbName)
|
|
&& normalizeLocateName(nodeObject) === normalizeLocateName(targetObject)
|
|
) {
|
|
return true;
|
|
}
|
|
|
|
if (
|
|
options.allowUnqualifiedSchemaMatch
|
|
&& !resolvedNodeSchema
|
|
&& normalizeLocateName(nodeObject) === normalizeLocateName(targetObject)
|
|
) {
|
|
return true;
|
|
}
|
|
|
|
if (!resolvedTargetSchema) {
|
|
if (options.allowUnqualifiedSchemaMatch) {
|
|
return normalizeLocateName(nodeObject) === normalizeLocateName(targetObject);
|
|
}
|
|
return !resolvedNodeSchema && normalizeLocateName(nodeObject) === normalizeLocateName(targetObject);
|
|
}
|
|
|
|
return normalizeLocateName(resolvedNodeSchema) === normalizeLocateName(resolvedTargetSchema)
|
|
&& normalizeLocateName(nodeObject) === normalizeLocateName(targetObject);
|
|
};
|
|
|
|
const matchesLocateObjectNode = (
|
|
node: SidebarLocateTreeNodeLike,
|
|
target: SidebarLocateTarget,
|
|
options: { allowUnqualifiedSchemaMatch?: boolean } = {},
|
|
): boolean => {
|
|
const dataRef = node.dataRef || {};
|
|
const nodeObjectType = normalizeLocateName(toTrimmedString(dataRef.objectType || dataRef.objectKind));
|
|
|
|
if (target.objectGroup === 'externalSqlFiles') {
|
|
return node.type === 'external-sql-file'
|
|
&& normalizeExternalSQLLocatePath(dataRef.path) === normalizeExternalSQLLocatePath(target.filePath);
|
|
}
|
|
|
|
const nodeConnectionId = toTrimmedString(dataRef.id || dataRef.connectionId);
|
|
const nodeDbName = toTrimmedString(dataRef.dbName);
|
|
|
|
if (nodeConnectionId !== target.connectionId || nodeDbName !== target.dbName) {
|
|
return false;
|
|
}
|
|
|
|
if (target.objectGroup === 'views') {
|
|
if (node.type !== 'view' && nodeObjectType !== 'view' && nodeObjectType !== 'views') return false;
|
|
return matchesLocateObjectName(target, toTrimmedString(dataRef.viewName || dataRef.tableName), toTrimmedString(dataRef.schemaName), options);
|
|
}
|
|
|
|
if (target.objectGroup === 'materializedViews') {
|
|
if (node.type !== 'materialized-view' && nodeObjectType !== 'materialized-view' && nodeObjectType !== 'materializedviews') return false;
|
|
return matchesLocateObjectName(target, toTrimmedString(dataRef.viewName || dataRef.tableName), toTrimmedString(dataRef.schemaName), options);
|
|
}
|
|
|
|
if (target.objectGroup === 'triggers') {
|
|
if (node.type !== 'db-trigger') return false;
|
|
return matchesLocateObjectName(target, toTrimmedString(dataRef.triggerName || dataRef.tableName), toTrimmedString(dataRef.schemaName), options);
|
|
}
|
|
|
|
if (target.objectGroup === 'routines') {
|
|
if (node.type !== 'routine') return false;
|
|
return matchesLocateObjectName(target, toTrimmedString(dataRef.routineName || dataRef.tableName), toTrimmedString(dataRef.schemaName), options);
|
|
}
|
|
|
|
if (node.type !== 'table') return false;
|
|
return matchesLocateObjectName(target, toTrimmedString(dataRef.tableName), toTrimmedString(dataRef.schemaName), options);
|
|
};
|
|
|
|
const findSidebarNodePathForLocateByObject = (
|
|
nodes: SidebarLocateTreeNodeLike[],
|
|
target: SidebarLocateTarget,
|
|
options: { allowUnqualifiedSchemaMatch?: boolean } = {},
|
|
): string[] | null => {
|
|
for (const node of nodes) {
|
|
const nodeKey = String(node.key);
|
|
if (matchesLocateObjectNode(node, target, options)) {
|
|
return [nodeKey];
|
|
}
|
|
|
|
if (node.children) {
|
|
const childPath = findSidebarNodePathForLocateByObject(node.children, target, options);
|
|
if (childPath) {
|
|
return [nodeKey, ...childPath];
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
};
|
|
|
|
const collectSidebarNodePathsForLocateByObject = (
|
|
nodes: SidebarLocateTreeNodeLike[],
|
|
target: SidebarLocateTarget,
|
|
options: { allowUnqualifiedSchemaMatch?: boolean } = {},
|
|
ancestorPath: string[] = [],
|
|
): string[][] => {
|
|
const paths: string[][] = [];
|
|
for (const node of nodes) {
|
|
const nodeKey = String(node.key);
|
|
const path = [...ancestorPath, nodeKey];
|
|
if (matchesLocateObjectNode(node, target, options)) {
|
|
paths.push(path);
|
|
}
|
|
if (node.children) {
|
|
paths.push(...collectSidebarNodePathsForLocateByObject(node.children, target, options, path));
|
|
}
|
|
}
|
|
return paths;
|
|
};
|
|
|
|
const getVisualNodeObjectName = (
|
|
node: SidebarLocateTreeNodeLike,
|
|
target: SidebarLocateTarget,
|
|
): string => {
|
|
const title = toTrimmedString(node.title);
|
|
if (title && title !== '[object Object]') return title;
|
|
|
|
const nodeKey = toTrimmedString(node.key);
|
|
const keyPrefixes = target.objectGroup === 'materializedViews'
|
|
? [`${target.databaseKey}-materialized-view-`]
|
|
: target.objectGroup === 'views'
|
|
? [`${target.databaseKey}-view-`]
|
|
: target.objectGroup === 'triggers'
|
|
? [`${target.databaseKey}-trigger-`]
|
|
: target.objectGroup === 'routines'
|
|
? [`${target.databaseKey}-routine-`]
|
|
: [`${target.databaseKey}-table-`, `${target.databaseKey}-`];
|
|
|
|
const matchedPrefix = keyPrefixes.find((prefix) => nodeKey.startsWith(prefix));
|
|
return matchedPrefix ? nodeKey.slice(matchedPrefix.length) : '';
|
|
};
|
|
|
|
const getLocateObjectGroupPathSuffix = (objectGroup: SidebarLocateObjectGroup): string => {
|
|
if (objectGroup === 'externalSqlFiles') return 'external-sql-root';
|
|
return objectGroup.toLowerCase();
|
|
};
|
|
|
|
const isPathInsideLocateObjectGroup = (
|
|
path: string[],
|
|
target: SidebarLocateTarget,
|
|
): boolean => {
|
|
if (target.objectGroup === 'externalSqlFiles') return false;
|
|
const normalizedObjectGroupKey = normalizeLocateName(target.objectGroupKey);
|
|
const groupSuffix = getLocateObjectGroupPathSuffix(target.objectGroup);
|
|
return path.some((key) => {
|
|
const normalizedKey = normalizeLocateName(key);
|
|
return normalizedKey === normalizedObjectGroupKey || normalizedKey.endsWith(`-${groupSuffix}`);
|
|
});
|
|
};
|
|
|
|
const matchesLocateObjectNodeByVisualIdentity = (
|
|
node: SidebarLocateTreeNodeLike,
|
|
target: SidebarLocateTarget,
|
|
path: string[],
|
|
): boolean => {
|
|
if (!path.includes(target.databaseKey)) return false;
|
|
const nodeObjectType = normalizeLocateName(toTrimmedString(node.dataRef?.objectType || node.dataRef?.objectKind));
|
|
const insideExpectedGroup = isPathInsideLocateObjectGroup(path, target);
|
|
|
|
if (target.objectGroup === 'views' && node.type !== 'view' && nodeObjectType !== 'view' && nodeObjectType !== 'views' && !insideExpectedGroup) return false;
|
|
if (target.objectGroup === 'materializedViews' && node.type !== 'materialized-view' && nodeObjectType !== 'materialized-view' && nodeObjectType !== 'materializedviews' && !insideExpectedGroup) return false;
|
|
if (target.objectGroup === 'triggers' && node.type !== 'db-trigger' && !insideExpectedGroup) return false;
|
|
if (target.objectGroup === 'routines' && node.type !== 'routine' && !insideExpectedGroup) return false;
|
|
if (target.objectGroup === 'tables' && node.type !== 'table' && !insideExpectedGroup) return false;
|
|
if (target.objectGroup === 'externalSqlFiles') return false;
|
|
|
|
const schemaName = toTrimmedString(node.dataRef?.schemaName);
|
|
return matchesLocateObjectName(target, getVisualNodeObjectName(node, target), schemaName, { allowUnqualifiedSchemaMatch: true });
|
|
};
|
|
|
|
const collectSidebarNodePathsForLocateByVisualIdentity = (
|
|
nodes: SidebarLocateTreeNodeLike[],
|
|
target: SidebarLocateTarget,
|
|
ancestorPath: string[] = [],
|
|
): string[][] => {
|
|
const paths: string[][] = [];
|
|
for (const node of nodes) {
|
|
const nodeKey = String(node.key);
|
|
const path = [...ancestorPath, nodeKey];
|
|
if (matchesLocateObjectNodeByVisualIdentity(node, target, path)) {
|
|
paths.push(path);
|
|
}
|
|
if (node.children) {
|
|
paths.push(...collectSidebarNodePathsForLocateByVisualIdentity(node.children, target, path));
|
|
}
|
|
}
|
|
return paths;
|
|
};
|
|
|
|
const hasLocateTargetSchema = (target: SidebarLocateTarget): boolean => {
|
|
if (target.objectGroup === 'externalSqlFiles') return true;
|
|
return Boolean(toTrimmedString(target.schemaName) || splitSidebarQualifiedName(target.tableName).schemaName);
|
|
};
|
|
|
|
const shouldFallbackViewLocateToTableNode = (target: SidebarLocateTarget): boolean => (
|
|
target.objectGroup === 'views' || target.objectGroup === 'materializedViews'
|
|
);
|
|
|
|
const selectPreferredSidebarLocatePath = (
|
|
paths: string[][],
|
|
target: SidebarLocateTarget,
|
|
): string[] | null => {
|
|
if (paths.length === 1) return paths[0];
|
|
if (paths.length === 0 || target.objectGroup === 'externalSqlFiles') return null;
|
|
|
|
const targetParsed = splitSidebarQualifiedName(target.tableName);
|
|
const targetObjectName = normalizeLocateName(targetParsed.objectName || target.tableName);
|
|
const schemaCandidates = [
|
|
toTrimmedString(target.schemaName),
|
|
targetParsed.schemaName,
|
|
target.dbName,
|
|
].filter(Boolean);
|
|
const normalizedSchemas = Array.from(new Set(schemaCandidates.map(normalizeLocateName)));
|
|
|
|
for (const normalizedSchema of normalizedSchemas) {
|
|
const preferredSchemaKey = `${normalizeLocateName(target.databaseKey)}-schema-${normalizedSchema}`;
|
|
const bySchemaGroup = paths.filter((path) =>
|
|
path.some((key) => normalizeLocateName(key) === preferredSchemaKey),
|
|
);
|
|
if (bySchemaGroup.length === 1) return bySchemaGroup[0];
|
|
|
|
const qualifiedSuffix = `${normalizedSchema}.${targetObjectName}`;
|
|
const byQualifiedLeafKey = paths.filter((path) => {
|
|
const leafKey = normalizeLocateName(path[path.length - 1] || '');
|
|
return leafKey.endsWith(qualifiedSuffix);
|
|
});
|
|
if (byQualifiedLeafKey.length === 1) return byQualifiedLeafKey[0];
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
export const findSidebarNodePathForLocate = (
|
|
nodes: SidebarLocateTreeNodeLike[],
|
|
target: SidebarLocateTarget,
|
|
): string[] | null => {
|
|
const exactPath = findSidebarNodePathByKey(nodes, target.targetKey);
|
|
if (exactPath) return exactPath;
|
|
|
|
const strictPath = findSidebarNodePathForLocateByObject(nodes, target);
|
|
if (strictPath) return strictPath;
|
|
|
|
const visualIdentityPaths = collectSidebarNodePathsForLocateByVisualIdentity(nodes, target);
|
|
const visualIdentityPath = selectPreferredSidebarLocatePath(visualIdentityPaths, target);
|
|
if (visualIdentityPath) return visualIdentityPath;
|
|
|
|
if (shouldFallbackViewLocateToTableNode(target)) {
|
|
const tableLikeTarget = { ...target, objectGroup: 'tables' as const };
|
|
const tableLikePaths = collectSidebarNodePathsForLocateByObject(nodes, tableLikeTarget);
|
|
const tableLikePath = selectPreferredSidebarLocatePath(tableLikePaths, target);
|
|
if (tableLikePath) return tableLikePath;
|
|
const visualTableLikePaths = collectSidebarNodePathsForLocateByVisualIdentity(nodes, tableLikeTarget);
|
|
const visualTableLikePath = selectPreferredSidebarLocatePath(visualTableLikePaths, target);
|
|
if (visualTableLikePath) return visualTableLikePath;
|
|
if (!hasLocateTargetSchema(target)) {
|
|
const relaxedTableLikePaths = collectSidebarNodePathsForLocateByObject(
|
|
nodes,
|
|
tableLikeTarget,
|
|
{ allowUnqualifiedSchemaMatch: true },
|
|
);
|
|
const relaxedTableLikePath = selectPreferredSidebarLocatePath(relaxedTableLikePaths, target);
|
|
if (relaxedTableLikePath) return relaxedTableLikePath;
|
|
}
|
|
}
|
|
|
|
const relaxedPaths = collectSidebarNodePathsForLocateByObject(
|
|
nodes,
|
|
target,
|
|
{ allowUnqualifiedSchemaMatch: true },
|
|
);
|
|
const relaxedPath = selectPreferredSidebarLocatePath(relaxedPaths, target);
|
|
if (relaxedPath) return relaxedPath;
|
|
|
|
if (hasLocateTargetSchema(target)) return null;
|
|
|
|
return null;
|
|
};
|