Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,7 @@ private IdHolderList queryByUserprop(ConditionQuery query) {
}
}
}
Set<MatchedIndex> indexes = this.collectMatchedIndexes(query);
Set<MatchedIndex> indexes = this.collectMatchedIndexes(query, true);
Comment thread
contrueCT marked this conversation as resolved.
if (indexes.isEmpty()) {
Id label = query.condition(HugeKeys.LABEL);
throw noIndexException(this.graph(), query, label);
Expand All @@ -492,16 +492,20 @@ private IdHolderList queryByUserprop(ConditionQuery query) {
return IdHolderList.empty(paging);
}

// Do index query
IdHolderList holders = new IdHolderList(paging);
// Validate the whole plan before opening any backend iterators. A later
// label's unavailable index must not leave earlier iterators unowned.
for (MatchedIndex index : indexes) {
for (IndexLabel il : index.indexLabels()) {
validateIndexLabel(il);
}
if (paging && index.indexLabels().size() > 1) {
throw new NotSupportException("joint index query in paging");
}
}

// Do index query
IdHolderList holders = new IdHolderList(paging);
for (MatchedIndex index : indexes) {
if (index.containsSearchIndex()) {
// Do search-index query
holders.addAll(this.doSearchIndex(query, index));
Expand Down Expand Up @@ -765,8 +769,13 @@ private PageIds doIndexQueryOnce(IndexLabel indexLabel,
}
}

@Watched(prefix = "index")
private Set<MatchedIndex> collectMatchedIndexes(ConditionQuery query) {
return this.collectMatchedIndexes(query, false);
}

@Watched(prefix = "index")
private Set<MatchedIndex> collectMatchedIndexes(ConditionQuery query,
boolean requireCompleteCoverage) {
ISchemaTransaction schema = this.params().schemaTransaction();
Id label = query.condition(HugeKeys.LABEL);

Expand Down Expand Up @@ -797,6 +806,18 @@ private Set<MatchedIndex> collectMatchedIndexes(ConditionQuery query) {
}
}

if (requireCompleteCoverage) {
// Match the public result visibility, including flattened queries.
// Removal jobs drop indexes before data/schema; those labels must
// neither require coverage nor contribute unavailable index plans.
// Cleanup callers still need to inspect invisible labels' indexes.
Query visibility = query.rootOriginQuery();
schemaLabels = schemaLabels.stream().filter(schemaLabel ->

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Important: If the only candidate label for a valid property query is hidden or deleting, this filter removes it and leaves indexes empty; queryByUserprop() then throws NoIndexException at lines 483-486. Before this change, the indexed result was filtered to an empty public result by GraphTransaction.invalidRecord(). Please return an empty holder when every candidate is excluded by visibility, while retaining NoIndexException for visible candidates without usable indexes. Evidence: exact-head trace of these methods and the result-visibility check.

(visibility.showHidden() || !schemaLabel.hidden()) &&
(visibility.showDeleting() || !schemaLabel.status().deleting()))
.collect(Collectors.toList());
}

// Collect MatchedIndex for each SchemaLabel
Set<MatchedIndex> matchedIndexes = InsertionOrderUtil.newSet();
for (SchemaLabel schemaLabel : schemaLabels) {
Expand All @@ -805,9 +826,57 @@ private Set<MatchedIndex> collectMatchedIndexes(ConditionQuery query) {
matchedIndexes.add(index);
}
}
// An invalid value returns no results after an index is found. Keep
// that result before deciding whether index coverage is complete.
if (requireCompleteCoverage && !matchedIndexes.isEmpty() &&
validQueryConditionValues(this.graph(), query)) {
this.checkIndexCoverage(query, schemaLabels, matchedIndexes);
Comment thread
contrueCT marked this conversation as resolved.
}
return matchedIndexes;
}

private void checkIndexCoverage(ConditionQuery query,
List<? extends SchemaLabel> labels,
Set<MatchedIndex> indexes) {
// Check at execution, before creating any lazy index iterator. Cleanup
// callers only need matching indexes and must not require full coverage.
Set<Id> requiredProperties = query.userpropKeys();
if (this.graph().readMode().showOlap()) {
// OLAP properties are shared rather than declared on every label.
requiredProperties.removeIf(id -> this.graph().propertyKey(id).olap());
Comment thread
contrueCT marked this conversation as resolved.
Comment thread
contrueCT marked this conversation as resolved.
}
if (requiredProperties.isEmpty()) {
// A shared OLAP index can match several labels, while MatchedIndex
// deduplicates those matches by index labels alone.
return;
}
Set<Id> covered = new HashSet<>();
for (MatchedIndex index : indexes) {
covered.add(index.schemaLabel().id());
}
for (SchemaLabel label : labels) {
Comment thread
contrueCT marked this conversation as resolved.
if (covered.contains(label.id()) ||
!label.properties().containsAll(requiredProperties)) {
continue;
}
if (label instanceof EdgeLabel) {
EdgeLabel edgeLabel = (EdgeLabel) label;
// Base labels have no direct edges; their property indexes
// include edges of their sub-labels (see updateEdgeIndex()).
if (edgeLabel.isFather() ||
edgeLabel.hasFather() && covered.contains(edgeLabel.fatherId())) {
continue;
}
}
// Authorization filters elements after the backend query. Do not
// disclose an implicitly discovered schema label in this error.
throw new NoIndexException("Incomplete index coverage for properties %s; " +
Comment thread
contrueCT marked this conversation as resolved.
Comment thread
contrueCT marked this conversation as resolved.
"specify a label or create compatible indexes " +
"for every candidate label",
this.graph().mapPkId2Name(query.userpropKeys()));
}
}

/**
* Collect matched IndexLabel(s) in a SchemaLabel for a query
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -479,10 +479,10 @@ public static void testLeftIndexProcess(final HugeGraph graph) {
.has("city", "Hangzhou").toList();
assert vertices.isEmpty();
// set breakpoint here to see secondary_indexes and range_indexes table
vertices = graph.traversal().V().has("age", 28).toList();
vertices = graph.traversal().V().hasLabel("person").has("age", 28).toList();
assert vertices.isEmpty();
// set breakpoint here to see secondary_indexes and range_indexes table
vertices = graph.traversal().V().has("city", "Hangzhou").toList();
vertices = graph.traversal().V().hasLabel("person").has("city", "Hangzhou").toList();
assert vertices.isEmpty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
EdgeLabelCoreTest.class,
IndexLabelCoreTest.class,
VertexCoreTest.class,
SourceIndexCoverageTest.class,
EdgeCoreTest.class,
CountStrategyCoreTest.class,
PrimaryKeyStrategyCoreTest.class,
Expand Down
Loading
Loading