chore: apply OB Oracle performance navigation count patches

This commit is contained in:
Syngnat
2026-07-07 17:33:59 +08:00
parent 8c968c1a87
commit 401c33c505

View File

@@ -0,0 +1,459 @@
name: Apply OB Oracle performance/navigation/count fixes
on:
push:
branches:
- fix/ob-oracle-performance-navigation-count
paths:
- .github/workflows/apply-ob-oracle-performance-navigation-count.yml
permissions:
contents: write
jobs:
apply-fix:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: fix/ob-oracle-performance-navigation-count
persist-credentials: true
- name: Apply focused patches
run: |
python3 - <<'PY'
from pathlib import Path
def replace_once(text: str, old: str, new: str, label: str) -> str:
if old not in text:
if new in text:
return text
raise SystemExit(f'{label} not found')
return text.replace(old, new, 1)
# 1) Avoid retrying the same query after its execution context is already done.
methods_db = Path('internal/app/methods_db.go')
text = methods_db.read_text()
helper_anchor = """func newQueryExecutionContext(config connection.ConnectionConfig) (context.Context, context.CancelFunc) {
if strings.EqualFold(strings.TrimSpace(config.Type), "duckdb") {
return context.WithCancel(context.Background())
}
timeoutSeconds := config.Timeout
if timeoutSeconds <= 0 {
timeoutSeconds = 30
}
return utils.ContextWithTimeout(time.Duration(timeoutSeconds) * time.Second)
}
"""
helper_block = helper_anchor + """
func shouldRetryConnectionAfterQueryError(ctx context.Context, err error) bool {
if err == nil {
return false
}
if ctx != nil && ctx.Err() != nil {
return false
}
return shouldRefreshCachedConnection(err)
}
"""
if 'func shouldRetryConnectionAfterQueryError(ctx context.Context, err error) bool' not in text:
text = replace_once(text, helper_anchor, helper_block, 'query retry helper anchor')
text = text.replace('if err != nil && shouldRefreshCachedConnection(err) {', 'if err != nil && shouldRetryConnectionAfterQueryError(ctx, err) {')
text = text.replace('if batchErr != nil && shouldRefreshCachedConnection(batchErr) {', 'if batchErr != nil && shouldRetryConnectionAfterQueryError(ctx, batchErr) {')
methods_db.write_text(text)
# 2) Lazy-load schema table metadata for Ctrl/Cmd navigation in the SQL editor.
query_editor = Path('frontend/src/components/QueryEditor.tsx')
text = query_editor.read_text()
if ' findIdentifierWindowAtOffset,\n' not in text:
text = replace_once(
text,
' collectQueryEditorReferencedDatabaseNames,\n',
' collectQueryEditorReferencedDatabaseNames,\n findIdentifierWindowAtOffset,\n',
'QueryEditor import findIdentifierWindowAtOffset',
)
if ' normalizeNavigationIdentifierParts,\n' not in text:
text = replace_once(
text,
' normalizeMetadataDialect,\n',
' normalizeMetadataDialect,\n normalizeNavigationIdentifierParts,\n',
'QueryEditor import normalizeNavigationIdentifierParts',
)
if 'const navigationMetadataWarmupRef = useRef<Record<string, Promise<boolean> | undefined>>({});' not in text:
text = replace_once(
text,
' const aiContextMetadataWarmupRef = useRef<Record<string, Promise<boolean> | undefined>>({});\n',
' const aiContextMetadataWarmupRef = useRef<Record<string, Promise<boolean> | undefined>>({});\n const navigationMetadataWarmupRef = useRef<Record<string, Promise<boolean> | undefined>>({});\n',
'QueryEditor navigation metadata ref',
)
navigation_helpers = """ const getNavigationIdentifierPartsAtPosition = (
lineContent: string,
column: number,
): string[] => {
const offset = Math.max(0, Number(column || 1) - 2);
const windowRange = findIdentifierWindowAtOffset(lineContent, offset);
if (!windowRange) {
return [];
}
return normalizeNavigationIdentifierParts(lineContent.slice(windowRange.start, windowRange.end).trim());
};
const isVisibleNavigationSchema = (schemaName: string): boolean => {
const normalized = String(schemaName || '').trim().toLowerCase();
return !!normalized && visibleDbsRef.current.some((db) => String(db || '').trim().toLowerCase() === normalized);
};
const ensureQueryEditorNavigationSchemaTables = async (schemaName: string): Promise<boolean> => {
const requestedSchema = String(schemaName || '').trim();
if (!requestedSchema) {
return false;
}
const connectionId = String(currentConnectionIdRef.current || currentConnectionId || tab.connectionId || '').trim();
if (!connectionId || !isVisibleNavigationSchema(requestedSchema)) {
return false;
}
const normalizedSchema = requestedSchema.toLowerCase();
if (tablesRef.current.some((table) => String(table.dbName || '').trim().toLowerCase() === normalizedSchema)) {
return true;
}
const warmupKey = `${connectionId}\u0000${normalizedSchema}\u0000navigation-tables`;
const existingWarmup = navigationMetadataWarmupRef.current[warmupKey];
if (existingWarmup) {
return existingWarmup;
}
const warmupPromise = (async (): Promise<boolean> => {
const conn = connectionsRef.current.find((item) => item.id === connectionId);
if (!conn) {
return false;
}
const schemaFromTree = visibleDbsRef.current.find((db) => String(db || '').trim().toLowerCase() === normalizedSchema) || requestedSchema;
const config = buildQueryEditorObjectDefinitionConnectionConfig(conn);
try {
const resTables = await DBGetTables(buildRpcConnectionConfig(config) as any, schemaFromTree);
if (!resTables?.success || !Array.isArray(resTables.data)) {
return false;
}
const fetchedTables = resTables.data
.map((row: any) => buildCompletionTableMeta(schemaFromTree, row, new Map<string, string>()))
.filter((table): table is CompletionTableMeta => !!table);
if (fetchedTables.length === 0) {
return false;
}
const nextTableByKey = new Map(
tablesRef.current.map((table) => [
`${String(table.dbName || '').trim().toLowerCase()}\u0000${String(table.tableName || '').trim().toLowerCase()}`,
table,
]),
);
fetchedTables.forEach((table) => {
nextTableByKey.set(
`${String(table.dbName || '').trim().toLowerCase()}\u0000${String(table.tableName || '').trim().toLowerCase()}`,
table,
);
});
tablesRef.current = [...nextTableByKey.values()];
if (!visibleDbsRef.current.some((db) => String(db || '').trim().toLowerCase() === normalizedSchema)) {
visibleDbsRef.current = [...visibleDbsRef.current, schemaFromTree];
}
sharedTablesData = tablesRef.current;
sharedVisibleDbs = visibleDbsRef.current;
sharedLazyTablesCache[`${connectionId}|${schemaFromTree}`] = fetchedTables;
refreshObjectDecorations(QUERY_EDITOR_LIVE_DECORATION_MAX_TEXT_LENGTH);
return true;
} catch (error) {
console.warn('GoNavi query editor navigation metadata warmup failed', error);
return false;
}
})();
navigationMetadataWarmupRef.current[warmupKey] = warmupPromise;
const succeeded = await warmupPromise;
if (!succeeded) {
delete navigationMetadataWarmupRef.current[warmupKey];
}
return succeeded;
};
"""
if 'const ensureQueryEditorNavigationSchemaTables = async (schemaName: string): Promise<boolean>' not in text:
text = replace_once(
text,
' editor.onMouseDown?.((event: any) => {\n',
navigation_helpers + ' editor.onMouseDown?.((event: any) => {\n',
'QueryEditor navigation helper insertion',
)
old_mouse = """ editor.onMouseDown?.((event: any) => {
const browserEvent = event?.event;
const targetPosition = normalizeEditorPosition(event?.target?.position);
if (!browserEvent || !targetPosition) {
return;
}
if (!isQueryEditorPrimaryMouseButton(browserEvent)) {
return;
}
if (!hasQueryEditorCtrlMetaModifier(browserEvent) && !ctrlMetaPressedRef.current) {
return;
}
const model = editor.getModel?.();
const lineContent = String(model?.getLineContent?.(targetPosition.lineNumber) || '');
const navigationTarget = resolveQueryEditorNavigationTarget(
lineContent,
targetPosition.column,
currentDbRef.current,
visibleDbsRef.current,
tablesRef.current,
viewsRef.current,
materializedViewsRef.current,
triggersRef.current,
routinesRef.current,
sequencesRef.current,
packagesRef.current,
);
if (!navigationTarget) {
return;
}
browserEvent.preventDefault?.();
browserEvent.stopPropagation?.();
const connectionId = String(currentConnectionIdRef.current || '').trim();
if (!connectionId) {
return;
}
if (navigationTarget.type === 'database') {
const nextDbName = String(navigationTarget.dbName || '').trim();
if (!nextDbName) {
return;
}
setCurrentDb(nextDbName);
currentDbRef.current = nextDbName;
setActiveContext({ connectionId, dbName: nextDbName });
return;
}
const targetDbName = String(navigationTarget.dbName || '').trim();
if (!targetDbName) {
return;
}
if (navigationTarget.type === 'table') {
const targetTableName = String(navigationTarget.tableName || '').trim();
if (!targetTableName) return;
addTab({
id: `${connectionId}-${targetDbName}-table-${targetTableName}`,
title: targetTableName,
type: 'table',
connectionId,
dbName: targetDbName,
tableName: targetTableName,
initialViewMode: 'fields',
initialViewModeRequestId: String(Date.now()),
objectType: 'table',
returnToTabId: activeTabId || undefined,
});
return;
}
if (navigationTarget.type === 'view' || navigationTarget.type === 'materialized-view') {
void openDefinitionObjectEditTab(navigationTarget, connectionId, targetDbName);
return;
}
if (navigationTarget.type === 'trigger') {
void openTriggerObjectEditTab(navigationTarget, connectionId, targetDbName);
return;
}
if (navigationTarget.type === 'sequence') {
void openDefinitionObjectEditTab(navigationTarget, connectionId, targetDbName);
return;
}
if (navigationTarget.type === 'package') {
void openDefinitionObjectEditTab(navigationTarget, connectionId, targetDbName);
return;
}
void openRoutineObjectEditTab(navigationTarget, connectionId, targetDbName);
});
"""
new_mouse = """ editor.onMouseDown?.((event: any) => {
const browserEvent = event?.event;
const targetPosition = normalizeEditorPosition(event?.target?.position);
if (!browserEvent || !targetPosition) {
return;
}
if (!isQueryEditorPrimaryMouseButton(browserEvent)) {
return;
}
if (!hasQueryEditorCtrlMetaModifier(browserEvent) && !ctrlMetaPressedRef.current) {
return;
}
const model = editor.getModel?.();
const lineContent = String(model?.getLineContent?.(targetPosition.lineNumber) || '');
const resolveNavigationTargetAtPosition = () => resolveQueryEditorNavigationTarget(
lineContent,
targetPosition.column,
currentDbRef.current,
visibleDbsRef.current,
tablesRef.current,
viewsRef.current,
materializedViewsRef.current,
triggersRef.current,
routinesRef.current,
sequencesRef.current,
packagesRef.current,
);
const initialNavigationTarget = resolveNavigationTargetAtPosition();
const identifierParts = getNavigationIdentifierPartsAtPosition(lineContent, targetPosition.column);
const lazySchemaName = !initialNavigationTarget && identifierParts.length >= 2 && isVisibleNavigationSchema(identifierParts[0])
? identifierParts[0]
: '';
if (!initialNavigationTarget && !lazySchemaName) {
return;
}
browserEvent.preventDefault?.();
browserEvent.stopPropagation?.();
void (async () => {
let navigationTarget = initialNavigationTarget;
if (!navigationTarget && lazySchemaName) {
const loaded = await ensureQueryEditorNavigationSchemaTables(lazySchemaName);
if (loaded) {
navigationTarget = resolveNavigationTargetAtPosition();
if (navigationTarget) {
applyNavigationHoverStateAtPosition(targetPosition);
}
}
}
if (!navigationTarget) {
return;
}
const connectionId = String(currentConnectionIdRef.current || '').trim();
if (!connectionId) {
return;
}
if (navigationTarget.type === 'database') {
const nextDbName = String(navigationTarget.dbName || '').trim();
if (!nextDbName) {
return;
}
setCurrentDb(nextDbName);
currentDbRef.current = nextDbName;
setActiveContext({ connectionId, dbName: nextDbName });
return;
}
const targetDbName = String(navigationTarget.dbName || '').trim();
if (!targetDbName) {
return;
}
if (navigationTarget.type === 'table') {
const targetTableName = String(navigationTarget.tableName || '').trim();
if (!targetTableName) return;
addTab({
id: `${connectionId}-${targetDbName}-table-${targetTableName}`,
title: targetTableName,
type: 'table',
connectionId,
dbName: targetDbName,
tableName: targetTableName,
initialViewMode: 'fields',
initialViewModeRequestId: String(Date.now()),
objectType: 'table',
returnToTabId: activeTabId || undefined,
});
return;
}
if (navigationTarget.type === 'view' || navigationTarget.type === 'materialized-view') {
void openDefinitionObjectEditTab(navigationTarget, connectionId, targetDbName);
return;
}
if (navigationTarget.type === 'trigger') {
void openTriggerObjectEditTab(navigationTarget, connectionId, targetDbName);
return;
}
if (navigationTarget.type === 'sequence') {
void openDefinitionObjectEditTab(navigationTarget, connectionId, targetDbName);
return;
}
if (navigationTarget.type === 'package') {
void openDefinitionObjectEditTab(navigationTarget, connectionId, targetDbName);
return;
}
void openRoutineObjectEditTab(navigationTarget, connectionId, targetDbName);
})();
});
"""
text = replace_once(text, old_mouse, new_mouse, 'QueryEditor mouse navigation block')
query_editor.write_text(text)
# 3) Put manual total-count action in the pagination bar where the not-counted state is shown.
shell = Path('frontend/src/components/DataGridShell.tsx')
text = shell.read_text()
old = """ <DataGridPaginationBar
isV2Ui={isV2Ui}
pagination={pagination}
paginationV2SummaryText={paginationV2SummaryText}
paginationSummaryText={paginationSummaryText}
paginationControlTotal={paginationControlTotal}
paginationTotalPages={paginationTotalPages}
paginationPageText={paginationPageText}
paginationPageSizeOptions={paginationPageSizeOptions}
showKnownPageCount={paginationHasKnownTotalPages}
onPageChange={onPageChange}
onPageSizeChange={handlePageSizeChange}
onV2PageStep={handleV2PageStep}
translate={translateDataGrid}
/>
"""
new = """ <DataGridPaginationBar
isV2Ui={isV2Ui}
pagination={pagination}
paginationV2SummaryText={paginationV2SummaryText}
paginationSummaryText={paginationSummaryText}
paginationControlTotal={paginationControlTotal}
paginationTotalPages={paginationTotalPages}
paginationPageText={paginationPageText}
paginationPageSizeOptions={paginationPageSizeOptions}
showKnownPageCount={paginationHasKnownTotalPages}
manualTotalCountAvailable={prefersManualTotalCount && !!onRequestTotalCount}
totalCountLoading={pagination?.totalCountLoading}
onPageChange={onPageChange}
onPageSizeChange={handlePageSizeChange}
onV2PageStep={handleV2PageStep}
onToggleTotalCount={handleToggleTotalCount}
translate={translateDataGrid}
/>
"""
text = replace_once(text, old, new, 'DataGridShell pagination props')
shell.write_text(text)
workflow = Path('.github/workflows/apply-ob-oracle-performance-navigation-count.yml')
if workflow.exists():
workflow.unlink()
PY
git diff --check
- name: Commit patches
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add internal/app/methods_db.go frontend/src/components/QueryEditor.tsx frontend/src/components/DataGridShell.tsx .github/workflows/apply-ob-oracle-performance-navigation-count.yml
git commit -m "fix(ob-oracle): avoid timeout retries and lazy-load navigation metadata"
git push origin HEAD:fix/ob-oracle-performance-navigation-count