Skip to content

[CALCITE-7780] Avoid redundant map lookups - #5262

Merged
mihaibudiu merged 1 commit into
apache:mainfrom
michaelbraun:CALCITE-7780
Sep 15, 2026
Merged

mihaibudiu merged 1 commit into
apache:mainfrom
michaelbraun:CALCITE-7780

Conversation

@michaelbraun

@michaelbraun michaelbraun commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Jira Link

CALCITE-7780

Changes Proposed

Several places look up the same map key two or three times in a row, where one
call to an existing Map method does the same job. For example,
HepPlanner#updateVertex:

if (mapDigestToVertex.get(oldKey) == vertex) {
  mapDigestToVertex.remove(oldKey);
}

is Map#remove(key, value):

mapDigestToVertex.remove(oldKey, vertex);

The same shape occurs in a handful of other classes, each replaced with the
corresponding single-lookup idiom: a get plus null check for containsKey
then get; putIfAbsent or computeIfAbsent for containsKey then put;
iterating entrySet, with Map.Entry#setValue for in-place updates, for
iterating keySet and calling get per key.

There is no functional change, so no tests are added; the existing suites cover
these paths. Every map involved holds non-null values, so replacing
containsKey with a null check on get preserves behaviour, and
HepRelVertex does not override equals, so the two-argument Map#remove
performs the identity comparison the old code did explicitly.

The single lambda introduced, in Match, captures nothing, so that
computeIfAbsent call site allocates nothing per call. AggregateNode#send
keeps an explicit get plus null check for the same reason: a mapping function
there would have to read accumulatorFactories, allocating a capturing lambda
on every row.

Some sites are on per-row paths (AggregateNode#send, UncollectNode, and the
expression caches in RexToLixTranslator); the rest is planner and validator
code. No benchmark is claimed, only strictly less work.

./gradlew build passes.

🤖 Generated with Claude Code

@michaelbraun
michaelbraun marked this pull request as ready for review September 13, 2026 22:01
// it is not necessary to visit it again, just return the result.
if (rexWithStorageTypeResultMap.containsKey(key)) {
return rexWithStorageTypeResultMap.get(key);
final Result cached = rexWithStorageTypeResultMap.get(key);

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.

Some Java maps allow null keys. Have you checked that this one doesn't?
If it does, the semantics is not the same.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

In this case the values will always be non-null. Should I comment in these cases to make it more clear this is safe?

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.

Fortunately we have @Nullable annotations, so if the ksy is not nullable, hopefully the invariant is maintained.

continue;
}
for (Object key : map.keySet()) {
for (Map.Entry<?, ?> entry : map.entrySet()) {

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.

Map.forEach(BiConsumer) may be more efficient here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The int i is being modified within this loop, so that would need to be wrapped in order to modify it, if we went that route. entrySet seems like a better fit here

@mihaibudiu

Copy link
Copy Markdown
Contributor

Please squash the commits so we can merge

@mihaibudiu mihaibudiu added the LGTM-will-merge-soon Overall PR looks OK. Only minor things left. label Sep 15, 2026
Several places look up the same map key two or three times in a row,
where one call to an existing Map method does the same job. For example,
HepPlanner#updateVertex removes a digest only if it still maps to the
vertex being updated, which is Map#remove(key, value).

The same shape occurs in a handful of other classes, and is replaced
with the corresponding single-lookup idiom: a get plus null check for
containsKey followed by get; putIfAbsent or computeIfAbsent for
containsKey followed by put; iteration over entrySet, with
Map.Entry#setValue for in-place updates, for iterating keySet and
calling get per key.

Some sites are on per-row paths (AggregateNode#send, UncollectNode and
the expression caches in RexToLixTranslator); the rest is planner and
validator code. No lambda introduced captures enclosing state, so the
computeIfAbsent call sites allocate nothing.

All values involved are non-null, so replacing containsKey with a null
check on get preserves behaviour. There is no functional change.

MongoFilter and QueryBuilders were reverted from this change: in
MongoFilter the map is declared Map<String, @nullable Object>, and in
QueryBuilders the map is supplied by the caller, so in neither case is a
non-null value guaranteed by the type. Treating "absent" and "mapped to
null" alike is safe for the keys these two sites use, but it rests on an
invariant rather than on the declaration, so leave both as they were.

Strong#createPolicyMap is reverted for a different reason: the map is an
EnumMap, which does not override putIfAbsent, so the default Map
implementation still performs a get followed by a put. No lookup is
saved.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@michaelbraun

Copy link
Copy Markdown
Contributor Author

Please squash the commits so we can merge

I believe it should be good to go now

@sonarqubecloud

Copy link
Copy Markdown

@mihaibudiu
mihaibudiu merged commit b8cb105 into apache:main Sep 15, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

LGTM-will-merge-soon Overall PR looks OK. Only minor things left.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants