Skip to content

Offer indexes on synthetic record types to the planner when a query reads all their stored constituents - #4840

Merged
g31pranjal merged 5 commits into
mainfrom
apple/g31pranjal/plan-context-synthetic-indexes
Nov 9, 2026
Merged

g31pranjal merged 5 commits into
mainfrom
apple/g31pranjal/plan-context-synthetic-indexes

Conversation

@g31pranjal

@g31pranjal g31pranjal commented Nov 4, 2026 •

Copy link
Copy Markdown
Member

MetaDataPlanContext never offered indexes on synthetic record types to the planner. Both forRecordQuery and forRootReference now also offer the readable indexes of every synthetic record type whose stored constituents are all read by the query, found with the new RecordMetaData.getSyntheticRecordTypesFromRecordTypes(Collection<RecordType>).

SyntheticRecordType.getStoredRecordTypes() gives those constituents: all of a joined type's, only the parent of an unnested type. Outer-joined constituents are still required for now.

This changes no plans yet: synthetic primary keys are ListKeyExpressions, which index expansion does not support, so such indexes are skipped. It is groundwork for #4817. Tests are in RecordMetaDataBuilderTest.

@g31pranjal g31pranjal added the enhancement New feature or request label Nov 4, 2026
@g31pranjal
g31pranjal marked this pull request as ready for review November 4, 2026 15:23
Comment on lines +117 to +119
@Nonnull final RecordStoreState recordStoreState,
@Nonnull final Collection<String> queriedRecordTypeNames) {
final var indexes = Lists.<Index>newArrayList();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: Run the IntelliJ formatter over this.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

done

*/
@Nonnull
@VisibleForTesting
static List<Index> syntheticIndexesOf(@Nonnull final RecordMetaData metaData,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Rather readableSyntheticIndexesOf(), since we also apply the readableOf() filter to the result.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

But actually, I would vote to keep that readableOf() filtering out of this method and let the caller do the filtering. That will make the call site more harmonious with how the other indexes are collected, via indexList.addAll(readableOf(recordStoreState, metaData.getUniversalIndexes())).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

done

final var indexes = Lists.<Index>newArrayList();
for (final var syntheticRecordType : metaData.getSyntheticRecordTypes().values()) {
final var storedConstituentNames =
syntheticRecordType.getConstituents().stream()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What about the constituents that are isOuterJoined(), are they intentionally included in this stream?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes, I have included them for now. In the work, I am not considering JoinedRecordType with outer join.

.map(constituent -> constituent.getRecordType().getName())
.filter(metaData.getRecordTypes()::containsKey)
.collect(ImmutableSet.toImmutableSet());
if (!storedConstituentNames.isEmpty() && queriedRecordTypeNames.containsAll(storedConstituentNames)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don’t like the String-based “join” we’re doing here via containsAll(). It seems unnecessary to take the detour through the strings, since the caller can just as well pass the queriedRecordTypes directly as a list of RecordType. Then this becomes a more efficient containsAll() over objects.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

done

final var storedConstituentNames =
syntheticRecordType.getConstituents().stream()
.map(constituent -> constituent.getRecordType().getName())
.filter(metaData.getRecordTypes()::containsKey)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

There’s got to be a more direct and efficient way to check that it’s a stored record type instead of this string-based containsKey() check. How about !constitutent.getRecordType().isSynthetic()?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

not needed anymore

@Nonnull final Collection<String> queriedRecordTypeNames) {
final var indexes = Lists.<Index>newArrayList();
for (final var syntheticRecordType : metaData.getSyntheticRecordTypes().values()) {
final var storedConstituentNames =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It could be worth added a dedicated helper to get the stored constituent record types, instead of doing an ad-hoc stream for it in here. How about SyntheticRecordType.getStoredConstitutentRecordTypes()? With that, the helper here would become a simple for (syntheticRecordType) { if (queriesRecordTypes.containsAll(type.getStoredConstitutentRecordTypes())) } } loop.

* @return the readable indexes on the synthetic record types the query reads all the stored constituents of
*/
@Nonnull
@VisibleForTesting

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe there’s a more natural place for this method where it “makes sense” and can then be official and public, so we’d not need the @VisibleForTesting mark. Maybe a method in RecordMetaData or SyntheticRecordType? Then we’d also not need to add a whole new MetaDataPlanContextSyntheticIndexTest test file but could just extend the existing test files.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Shifted to RecordMetadata

if (queriedRecordTypeNamesOptional.isEmpty()) { // ALL_TYPES
commonPrimaryKey = commonPrimaryKey(metaData.getRecordTypes().values());
queriedRecordTypeNames = metaData.getRecordTypes().keySet();
queriedRecordTypes = metaData.getRecordTypes().values();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Consider adding ImmutableSet.copyOf() here to wrap this into an ImmutableSet as well (like in the else branch below). That should make the containsAll() we do on this set more efficient. Otherwise it’ll be a linear scan per element.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

done

final List<SyntheticRecordType<?>> result = new ArrayList<>();
for (SyntheticRecordType<?> syntheticRecordType : syntheticRecordTypes.values()) {
final Set<RecordType> storedRecordTypes = syntheticRecordType.getStoredRecordTypes();
if (!storedRecordTypes.isEmpty() && recordTypes.containsAll(storedRecordTypes)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can storedRecordTypes.isEmpty() ever happen? Does the API technically allow defining e.g. a joined type with no constituents? (Could be worth a comment or even a verify.)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

done

@g31pranjal
g31pranjal merged commit 8ae0d3a into main Nov 9, 2026
14 checks passed
@g31pranjal
g31pranjal deleted the apple/g31pranjal/plan-context-synthetic-indexes branch November 9, 2026 19:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants