mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-08 20:51:55 +08:00
chore: apply OB Oracle performance navigation count patches
This commit is contained in:
459
.github/workflows/apply-ob-oracle-performance-navigation-count.yml
vendored
Normal file
459
.github/workflows/apply-ob-oracle-performance-navigation-count.yml
vendored
Normal 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
|
||||
Reference in New Issue
Block a user