Skip to content

Commit e7d209f

Browse files
authored
Schema based completions for nodes in paths (#595)
1 parent d7e90a9 commit e7d209f

File tree

3 files changed

+372
-52
lines changed

3 files changed

+372
-52
lines changed

packages/language-support/src/autocompletion/completionCoreCompletions.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import {
4444
completeRelationshipType,
4545
allLabelCompletions,
4646
allReltypeCompletions,
47+
completeNodeLabel,
4748
} from './schemaBasedCompletions';
4849
import { backtickIfNeeded, uniq } from './autocompletionHelpers';
4950

@@ -702,7 +703,7 @@ export function completionCoreCompletion(
702703
}
703704

704705
if (topExprParent === CypherParser.RULE_nodePattern) {
705-
return allLabelCompletions(dbSchema);
706+
return completeNodeLabel(dbSchema, parsingResult, symbolsInfo);
706707
}
707708

708709
if (topExprParent === CypherParser.RULE_relationshipPattern) {

packages/language-support/src/autocompletion/schemaBasedCompletions.ts

Lines changed: 102 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import {
1010
NodePatternContext,
1111
PatternElementContext,
1212
QuantifierContext,
13+
RelationshipPatternContext,
1314
} from '../generated-parser/CypherCmdParser';
14-
import { ParserRuleContext } from 'antlr4';
1515
import { backtickIfNeeded } from './autocompletionHelpers';
1616
import { _internalFeatureFlags } from '../featureFlags';
1717

@@ -52,45 +52,45 @@ export const allReltypeCompletions = (dbSchema: DbSchema) =>
5252
reltypesToCompletions(dbSchema.relationshipTypes);
5353

5454
function intersectChildren(
55-
relsFromLabels: Map<string, Set<string>>,
55+
connectedLabels: Map<string, Set<string>>,
5656
children: LabelOrCondition[],
5757
): Set<string> {
5858
let intersection: Set<string> = undefined;
5959
children.forEach((c) => {
6060
intersection = intersection
6161
? (intersection = intersection.intersection(
62-
walkLabelTree(relsFromLabels, c),
62+
walkLabelTree(connectedLabels, c),
6363
))
64-
: walkLabelTree(relsFromLabels, c);
64+
: walkLabelTree(connectedLabels, c);
6565
});
6666
return intersection ?? new Set();
6767
}
6868

6969
function uniteChildren(
70-
relsFromLabels: Map<string, Set<string>>,
70+
connectedLabels: Map<string, Set<string>>,
7171
children: LabelOrCondition[],
7272
): Set<string> {
7373
let union: Set<string> = new Set();
7474
children.forEach(
75-
(c) => (union = union.union(walkLabelTree(relsFromLabels, c))),
75+
(c) => (union = union.union(walkLabelTree(connectedLabels, c))),
7676
);
7777
return union;
7878
}
7979

8080
function walkLabelTree(
81-
relsFromLabels: Map<string, Set<string>>,
81+
connectedLabels: Map<string, Set<string>>,
8282
labelTree: LabelOrCondition,
8383
): Set<string> {
8484
if (isLabelLeaf(labelTree)) {
85-
return relsFromLabels.get(labelTree.value);
85+
return connectedLabels.get(labelTree.value);
8686
} else if (labelTree.andOr == 'and') {
87-
return intersectChildren(relsFromLabels, labelTree.children);
87+
return intersectChildren(connectedLabels, labelTree.children);
8888
} else {
89-
return uniteChildren(relsFromLabels, labelTree.children);
89+
return uniteChildren(connectedLabels, labelTree.children);
9090
}
9191
}
9292

93-
function getRelsFromLabelsSet(dbSchema: DbSchema): Map<string, Set<string>> {
93+
function getRelsFromNodesSet(dbSchema: DbSchema): Map<string, Set<string>> {
9494
if (dbSchema.graphSchema) {
9595
const relsFromLabelsSet: Map<string, Set<string>> = new Map();
9696
dbSchema.graphSchema.forEach((rel) => {
@@ -112,35 +112,114 @@ function getRelsFromLabelsSet(dbSchema: DbSchema): Map<string, Set<string>> {
112112
return undefined;
113113
}
114114

115-
export function completeRelationshipType(
115+
function getNodesFromRelsSet(dbSchema: DbSchema): Map<string, Set<string>> {
116+
if (dbSchema.graphSchema) {
117+
const nodesFromRelsSet: Map<string, Set<string>> = new Map();
118+
dbSchema.graphSchema.forEach((rel) => {
119+
if (!nodesFromRelsSet.has(rel.relType)) {
120+
nodesFromRelsSet.set(rel.relType, new Set());
121+
}
122+
const currentRelEntry = nodesFromRelsSet.get(rel.relType);
123+
currentRelEntry.add(rel.to);
124+
currentRelEntry.add(rel.from);
125+
});
126+
return nodesFromRelsSet;
127+
}
128+
return undefined;
129+
}
130+
131+
export function completeNodeLabel(
116132
dbSchema: DbSchema,
117133
parsingResult: ParsedStatement,
118134
symbolsInfo: SymbolsInfo,
119135
): CompletionItem[] {
120-
if (!_internalFeatureFlags.schemaBasedPatternCompletions) {
121-
return allReltypeCompletions(dbSchema);
136+
if (
137+
!_internalFeatureFlags.schemaBasedPatternCompletions ||
138+
dbSchema.graphSchema === undefined
139+
) {
140+
return allLabelCompletions(dbSchema);
122141
}
123142

124-
if (dbSchema.graphSchema === undefined) {
125-
return allReltypeCompletions(dbSchema);
126-
}
127-
128-
// limitation: not checking PathPatternNonEmptyContext
129-
// limitation: not handling parenthesized paths
130143
const callContext = findParent(
131144
parsingResult.stopNode.parentCtx,
132145
(x) => x instanceof PatternElementContext,
133146
);
134147

135148
if (callContext instanceof PatternElementContext) {
136149
const lastValidElement = callContext.children.toReversed().find((child) => {
137-
if (child instanceof ParserRuleContext) {
150+
if (child instanceof RelationshipPatternContext) {
138151
if (child.exception === null) {
139152
return true;
140153
}
141154
}
142155
});
143156

157+
// limitation: bailing out on quantifiers
158+
if (lastValidElement instanceof QuantifierContext) {
159+
return allLabelCompletions(dbSchema);
160+
}
161+
162+
if (lastValidElement instanceof RelationshipPatternContext) {
163+
// limitation: not checking anonymous variables
164+
const variable = lastValidElement.variable();
165+
if (variable === null) {
166+
return allLabelCompletions(dbSchema);
167+
}
168+
169+
const foundVariable = symbolsInfo?.symbolTables
170+
?.flat()
171+
.find((entry) => entry.references.includes(variable.start.start));
172+
173+
if (
174+
foundVariable === undefined ||
175+
('children' in foundVariable.labels &&
176+
foundVariable.labels.children.length == 0)
177+
) {
178+
return allLabelCompletions(dbSchema);
179+
}
180+
181+
// limitation: not direction-aware (ignores <- vs ->)
182+
// limitation: not checking node label repetition
183+
const nodesFromRelsSet = getNodesFromRelsSet(dbSchema);
184+
const rels = walkLabelTree(nodesFromRelsSet, foundVariable.labels);
185+
186+
return labelsToCompletions(Array.from(rels));
187+
}
188+
}
189+
190+
return allLabelCompletions(dbSchema);
191+
}
192+
193+
export function completeRelationshipType(
194+
dbSchema: DbSchema,
195+
parsingResult: ParsedStatement,
196+
symbolsInfo: SymbolsInfo,
197+
): CompletionItem[] {
198+
if (
199+
!_internalFeatureFlags.schemaBasedPatternCompletions ||
200+
dbSchema.graphSchema === undefined
201+
) {
202+
return allReltypeCompletions(dbSchema);
203+
}
204+
205+
// limitation: not checking PathPatternNonEmptyContext
206+
// limitation: not handling parenthesized paths
207+
const patternContext = findParent(
208+
parsingResult.stopNode.parentCtx,
209+
(x) => x instanceof PatternElementContext,
210+
);
211+
212+
if (patternContext instanceof PatternElementContext) {
213+
const lastValidElement = patternContext.children
214+
.toReversed()
215+
.find((child) => {
216+
if (child instanceof NodePatternContext) {
217+
if (child.exception === null) {
218+
return true;
219+
}
220+
}
221+
});
222+
144223
// limitation: bailing out on quantifiers
145224
if (lastValidElement instanceof QuantifierContext) {
146225
return allReltypeCompletions(dbSchema);
@@ -166,8 +245,8 @@ export function completeRelationshipType(
166245
}
167246

168247
// limitation: not direction-aware (ignores <- vs ->)
169-
// limitation: not checking relationship variable reuse
170-
const relsFromLabelsSet = getRelsFromLabelsSet(dbSchema);
248+
// limitation: not checking relationship type repetition
249+
const relsFromLabelsSet = getRelsFromNodesSet(dbSchema);
171250
const rels = walkLabelTree(relsFromLabelsSet, foundVariable.labels);
172251

173252
return reltypesToCompletions(Array.from(rels));

0 commit comments

Comments
 (0)