From 52bc25d404416d7c297684b17c73091463f91e70 Mon Sep 17 00:00:00 2001 From: Rohan Juneja Date: Fri, 22 Mar 2024 18:53:33 -0700 Subject: [PATCH] identify pathfinder queries. identify templates --- src/index.ts | 61 +++++++++++++++++++++++++++- src/inferred_mode/inferred_mode.ts | 7 +++- src/inferred_mode/template_lookup.ts | 4 +- src/query_graph.ts | 8 ++-- 4 files changed, 72 insertions(+), 8 deletions(-) diff --git a/src/index.ts b/src/index.ts index e0a029ad..7e16b003 100644 --- a/src/index.ts +++ b/src/index.ts @@ -413,7 +413,7 @@ export default class TRAPIQueryHandler { async _processQueryGraph(queryGraph: TrapiQueryGraph): Promise { try { - const queryGraphHandler = new QueryGraph(queryGraph, this.options.schema); + const queryGraphHandler = new QueryGraph(queryGraph, this.options.schema, this._queryIsPathfinder()); const queryEdges = await queryGraphHandler.calculateEdges(); this.logs = [...this.logs, ...queryGraphHandler.logs]; return queryEdges; @@ -509,6 +509,12 @@ export default class TRAPIQueryHandler { } } + _queryIsPathfinder(): boolean { + const inferredEdgeCount = Object.values(this.queryGraph.edges).reduce((i, edge) => i + (edge.knowledge_type === 'inferred' ? 1 : 0), 0); + const pinnedNodes = Object.values(this.queryGraph.nodes).reduce((i, node) => i + (node.ids != null ? 1 : 0), 0); + return inferredEdgeCount === 3 && pinnedNodes == 2 && Object.keys(this.queryGraph.edges).length === 3 && Object.keys(this.queryGraph.nodes).length === 3; + } + _queryUsesInferredMode(): boolean { const inferredEdge = Object.values(this.queryGraph.edges).some((edge) => edge.knowledge_type === 'inferred'); return inferredEdge; @@ -519,7 +525,50 @@ export default class TRAPIQueryHandler { return oneHop; } - async _handleInferredEdges(): Promise { + async _handlePathfinder(): Promise { + const [unpinnedNodeId, unpinnedNode] = Object.entries(this.queryGraph.nodes).find(([_, node]) => !node.ids); + // remove unpinned node & all edges involving unpinned node for now + delete this.queryGraph.nodes[unpinnedNodeId]; + const intermediateEdges = Object.entries(this.queryGraph.edges).filter(([_, edge]) => edge.subject === unpinnedNodeId || edge.object === unpinnedNodeId); + const mainEdge = Object.entries(this.queryGraph.edges).find(([_, edge]) => edge.subject !== unpinnedNodeId && edge.object !== unpinnedNodeId); + + // intermediateEdges should be in order of n0 -> un & un -> n1 + if (intermediateEdges[0][1].subject === unpinnedNodeId) { + let temp = intermediateEdges[0]; + intermediateEdges[0] = intermediateEdges[1]; + intermediateEdges[1] = temp; + } + + // remove intermediates for creative execution + intermediateEdges.forEach(([edgeId, _]) => delete this.queryGraph.edges[edgeId]); + + if (Object.keys(this.queryGraph.edges).length !== 1) { + const message = 'Pathfinder Mode needs exactly one edge between nodes with IDs. Your query terminates.'; + debug(message); + this.logs.push(new LogEntry('WARNING', null, message).getLog()); + return; + } + + if (intermediateEdges[0][1].subject !== mainEdge[1].subject || intermediateEdges[1][1].object !== mainEdge[1].object || intermediateEdges[0][1].object !== unpinnedNodeId || intermediateEdges[1][1].subject !== unpinnedNodeId) { + const message = 'Intermediate edges for Pathfinder are incorrect. Your query terminates.'; + debug(message); + this.logs.push(new LogEntry('WARNING', null, message).getLog()); + return; + } + + // test + console.log("recognized pathfinder"); + + // run creative mode + await this._handleInferredEdges(true); + const creativeResponse = this.getResponse(); + + // restore query graph + this.queryGraph.nodes[unpinnedNodeId] = unpinnedNode; + intermediateEdges.forEach(([edgeId, edge]) => this.queryGraph.edges[edgeId] = edge); + } + + async _handleInferredEdges(pathfinder = false): Promise { if (!this._queryIsOneHop()) { const message = 'Inferred Mode edges are only supported in single-edge queries. Your query terminates.'; debug(message); @@ -534,6 +583,7 @@ export default class TRAPIQueryHandler { this.path, this.predicatePath, this.includeReasoner, + pathfinder ); const inferredQueryResponse = await inferredQueryHandler.query(); if (inferredQueryResponse) { @@ -668,6 +718,13 @@ export default class TRAPIQueryHandler { } debug(`(3) All edges created ${JSON.stringify(queryEdges)} `); + if (this._queryIsPathfinder()) { + const span2 = Telemetry.startSpan({ description: 'pathfinderExecution' }); + await this._handlePathfinder(); + span2?.finish(); + return; + } + if (this._queryUsesInferredMode()) { const span2 = Telemetry.startSpan({ description: 'creativeExecution' }); await this._handleInferredEdges(); diff --git a/src/inferred_mode/inferred_mode.ts b/src/inferred_mode/inferred_mode.ts index 31b2169f..0a5e41fd 100644 --- a/src/inferred_mode/inferred_mode.ts +++ b/src/inferred_mode/inferred_mode.ts @@ -51,6 +51,7 @@ export default class InferredQueryHandler { path: string; predicatePath: string; includeReasoner: boolean; + pathfinder: boolean; CREATIVE_LIMIT: number; constructor( parent: TRAPIQueryHandler, @@ -60,6 +61,7 @@ export default class InferredQueryHandler { path: string, predicatePath: string, includeReasoner: boolean, + pathfinder: boolean ) { this.parent = parent; this.queryGraph = queryGraph; @@ -68,6 +70,7 @@ export default class InferredQueryHandler { this.path = path; this.predicatePath = predicatePath; this.includeReasoner = includeReasoner; + this.pathfinder = pathfinder; this.CREATIVE_LIMIT = process.env.CREATIVE_LIMIT ? parseInt(process.env.CREATIVE_LIMIT) : 500; } @@ -107,7 +110,7 @@ export default class InferredQueryHandler { Object.values(this.queryGraph.nodes).reduce((sum, node) => { return typeof node.ids !== 'undefined' ? sum + node.ids.length : sum; }, 0); - if (tooManyIDs) { + if (tooManyIDs && !this.pathfinder) { const message = 'Inferred Mode queries with multiple IDs are not supported. Your query terminates.'; this.logs.push(new LogEntry('WARNING', null, message).getLog()); debug(message); @@ -195,7 +198,7 @@ export default class InferredQueryHandler { }, []); return [...arr, ...objectCombos]; }, []); - const templates = await getTemplates(lookupObjects); + const templates = await getTemplates(lookupObjects, this.pathfinder); const logMessage = `Got ${templates.length} inferred query templates.`; debug(logMessage); diff --git a/src/inferred_mode/template_lookup.ts b/src/inferred_mode/template_lookup.ts index 100dc5cc..c525f6bb 100644 --- a/src/inferred_mode/template_lookup.ts +++ b/src/inferred_mode/template_lookup.ts @@ -25,6 +25,7 @@ export interface TemplateGroup { object: string[]; qualifiers?: CompactQualifiers; templates: string[]; + pathfinder: boolean; } export interface CompactEdge { @@ -34,7 +35,7 @@ export interface CompactEdge { qualifiers: CompactQualifiers; } -export async function getTemplates(lookups: TemplateLookup[]): Promise { +export async function getTemplates(lookups: TemplateLookup[], pathfinder = false): Promise { async function getFiles(dir: string): Promise { const rootFiles = await fs.readdir(path.resolve(dir)); return await async.reduce(rootFiles, [] as string[], async (arr, fname: string) => { @@ -57,6 +58,7 @@ export async function getTemplates(lookups: TemplateLookup[]): Promise { const lookupMatch = lookups.some((lookup) => { return ( + group.pathfinder === pathfinder && group.subject.includes(lookup.subject) && group.object.includes(lookup.object) && group.predicate.includes(lookup.predicate) && diff --git a/src/query_graph.ts b/src/query_graph.ts index 7a6f9d80..cb1a0c2b 100644 --- a/src/query_graph.ts +++ b/src/query_graph.ts @@ -15,11 +15,13 @@ export default class QueryGraph { queryGraph: TrapiQueryGraph; schema: any; logs: StampedLog[]; + skipCycleDetection: boolean; nodes: { [QNodeID: string]: QNode }; edges: { [QEdgeID: string]: QEdge }; - constructor(queryGraph: TrapiQueryGraph, schema: any) { + constructor(queryGraph: TrapiQueryGraph, schema: any, skipCycleDetection = false) { this.queryGraph = queryGraph; this.schema = schema; + this.skipCycleDetection = skipCycleDetection; this.logs = []; } @@ -93,7 +95,7 @@ export default class QueryGraph { } for (const firstNode in nodes) { - if (nodes[firstNode].visited === true) continue; + if (nodes[firstNode].visited == true) continue; const stack: { curNode: string; parent: string | number }[] = [{ curNode: firstNode, parent: -1 }]; nodes[firstNode].visited = true; while (stack.length !== 0) { @@ -191,7 +193,7 @@ export default class QueryGraph { this._validateNodeProperties(queryGraph); this._validateEdgeProperties(queryGraph); this._validateBatchSize(queryGraph); - this._validateCycles(queryGraph); + !this.skipCycleDetection && this._validateCycles(queryGraph); this._validateNoDuplicateQualifierTypes(queryGraph); }