Skip to content

Conversation

@hs-lsong
Copy link
Collaborator

Summary

This PR introduces an optimization for chained filter expressions in Jinjava templates. Instead of creating deeply nested AST nodes for expressions like {{ foo|filter1|filter2|filter3 }}, a flattened AstFilterChain node is used, which improves performance by reducing recursive call overhead.

Key changes:

  • Add AstFilterChain class that processes filters iteratively instead of recursively
  • Add FilterSpec class to represent individual filter specifications in the chain
  • Make optimization configurable via enableFilterChainOptimization in JinjavaConfig (defaults to false)
  • Note: This optimization is disabled for eager execution mode - EagerExtendedParser overrides the config to always use the traditional nested approach

Test plan

  • Added AstFilterChainPerformanceTest to verify both approaches produce identical results
  • Automated test verifies optimized version outperforms unoptimized approach
  • Existing tests pass with optimization disabled (default behavior unchanged)

hs-lsong and others added 5 commits January 12, 2026 10:31
- Add enableFilterChainOptimization config option to JinjavaConfig (defaults to false)
- Modify ExtendedParser to conditionally use AstFilterChain based on config
- Override shouldUseFilterChainOptimization() in EagerExtendedParser to always return false
- Remove EagerAstFilterChain.java as eager mode now uses old nested method approach
- Revert test files to expect old format for eager execution
- Add AstFilterChainPerformanceTest to verify optimization performance
- Tests verify both approaches produce identical results
- Includes manual benchmark test (ignored by default) for detailed comparison
- Automated test verifies optimized version is faster than unoptimized (at least 5% improvement)
- Uses nested property access (content.text) to better simulate real-world usage
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
private ExecutionMode executionMode = DefaultExecutionMode.instance();
private LegacyOverrides legacyOverrides = LegacyOverrides.NONE;
private boolean enablePreciseDivideFilter = false;
private boolean enableFilterChainOptimization = false;
Copy link
Contributor

Choose a reason for hiding this comment

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

Do all the tests pass if this is set to true?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yes, just did it.

Copy link
Contributor

@jasmith-hs jasmith-hs left a comment

Choose a reason for hiding this comment

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

Looks cool!

Comment on lines +69 to +70
String filterKey = ExtendedParser.FILTER_PREFIX + spec.getName();
interpreter.getContext().addResolvedValue(filterKey);
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be tested for parity with regular filter parsing and evaluation

Comment on lines 173 to 180
builder.append('(');
for (int i = 0; i < params.getCardinality(); i++) {
if (i > 0) {
builder.append(", ");
}
params.getChild(i).appendStructure(builder, bindings);
}
builder.append(')');
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
builder.append('(');
for (int i = 0; i < params.getCardinality(); i++) {
if (i > 0) {
builder.append(", ");
}
params.getChild(i).appendStructure(builder, bindings);
}
builder.append(')');
params.appendStructure(builder, bindings);

* Run with: mvn test -Dtest=AstFilterChainPerformanceTest
* Or run the main() method directly for more detailed output.
*/
public class AstFilterChainPerformanceTest {
Copy link
Contributor

Choose a reason for hiding this comment

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

Unit tests should be made separate from performance tests

hs-lsong and others added 3 commits January 13, 2026 11:18
…handling

The parity test ensures the optimized filter chain produces identical results
to the unoptimized nested method approach across many scenarios including
chained filters, named parameters, SafeString handling, and edge cases.

Fixed a behavioral difference where unknown filters would pass through the
original value in the optimized path but return empty in the unoptimized path.
Now both paths return null for unknown filters.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Moved unit tests to AstFilterChainTest.java, keeping only performance-related
tests in AstFilterChainPerformanceTest.java for clearer test organization.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Use params.appendStructure() instead of manually iterating through
children, reducing code duplication.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@hs-lsong hs-lsong marked this pull request as ready for review January 13, 2026 16:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants