Skip to content

[Analytics Engine] Fix IP-field comparison 400 (Unable to convert call EQUALS_IP) on the DataFusion route - #22681

Merged
mch2 merged 2 commits into
opensearch-project:mainfrom
ahkcs:feature/analytics-ip-comparison-adapter
Aug 10, 2026
Merged

[Analytics Engine] Fix IP-field comparison 400 (Unable to convert call EQUALS_IP) on the DataFusion route#22681
mch2 merged 2 commits into
opensearch-project:mainfrom
ahkcs:feature/analytics-ip-comparison-adapter

Conversation

@ahkcs

@ahkcs ahkcs commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Description

PPL equality/comparison predicates and projection expressions over ip-mapped (and binary-mapped) fields on the analytics-engine DataFusion route fail deterministically with HTTP 400:

{
  "error": {
    "reason": "Invalid Query",
    "details": "Unable to convert call EQUALS_IP(binary?, binary?).",
    "type": "IllegalArgumentException"
  },
  "status": 400
}

This makes ip/binary =, !=, <, > filters and if/case/count(eval(...)) project shapes over those fields unusable on the analytics route, and fails two sandbox-check CI tests on every run (FieldTypeCoverageIT.testIpFilters, FieldTypeCoverageIT.testIpAndBinaryProjectExpressions).

Resolves #22672.

Root cause

ip and binary fields reach the analytics route typed as VARBINARY. When the SQL plugin resolves a comparison such as where ip_field = '1.2.3.4', its PPLFuncImpTable registers two operators per PPL comparator — the IP user-defined function (e.g. EQUALS_IP) first, then the standard Calcite comparator. Because the IP-UDF's (IP_UDT, IP_UDT) operand checker accepts a plain VARBINARY operand, the UDF overload wins, and the plan carries EQUALS_IP(binary, binary).

EQUALS_IP (and its five siblings NOT_EQUALS_IP, LESS_IP, GREATER_IP, LTE_IP, GTE_IP) are SQL-plugin ImplementorUDFs with an Enumerable (codegen) implementation only. On the analytics route the RelNode is lowered to Substrait, and there is no Substrait binding and no backend adapter for these UDFs, so isthmus rejects the call with Unable to convert call EQUALS_IP(binary?, binary?). The capability gate does not catch it earlier — the UDFs carry a comparison SqlKind, so ScalarFunction.fromSqlOperatorWithFallback resolves them to the standard EQUALS/LESS_THAN/... which the backend does support; the operator's true identity only surfaces at Substrait conversion.

This regressed when ip/binary gained their UDT representation (#21807): before that, ip was plain VARBINARY, the IP-UDF operand checker did not match, and the standard comparator was selected — the working path. #21807 shipped adapters for CIDRMATCH and the ip/binary→string casts but not for the six IP comparison UDFs.

Fix

A backend adapter (IpComparisonNormalizationAdapter) that normalizes an IP-comparison UDF call back to its native SqlStdOperatorTable comparator before Substrait conversion, when both operands are VARBINARY. DataFusion then runs its native byte-wise VARBINARY comparison — the same path a plain = takes — after BinaryFunctionAdapter has resolved the string literal into matching on-disk bytes (adapter recursion runs operands bottom-up, so the literal is already VARBINARY when this parent adapter runs). The BOOLEAN result type is preserved, so the plan schema is unchanged.

Detection is structural, not by operator identity: the analytics backend cannot reference PPLBuiltinOperators.EQUALS_IP (it lives in the SQL plugin's core module, off the backend classpath). A call qualifies when its operator carries a comparison SqlKind but is not the canonical SqlStdOperatorTable singleton for that kind, and both operands are VARBINARY. This mirrors CidrMatchFunctionAdapter, which already rewrites a UDF into native Calcite comparators over VARBINARY.

The adapter is registered in the six comparison-operator slots that previously held ComparisonTemporalCoercionAdapter, and wraps that adapter — every call (rewritten or not) is still delegated to it, so char↔temporal / TIME coercion on those slots is preserved unchanged (IP operands are never temporal, so the delegation is a no-op for the rewritten calls).

Scope note. Only the four direct comparators (=, !=, <, >) emit the IP UDFs. in and between over ip/binary already lower to native operators over VARBINARY (their literals go through the same BINARY(varchar) placeholder that BinaryFunctionAdapter resolves), and cidrmatch has its own adapter — those shapes were never broken. The failing test crashes at its first = assertion, before reaching them.

Testing

test before after
IpComparisonNormalizationAdapterTests (all 6 IP comparators → native op; native/non-VARBINARY passthrough; VARBINARY guard; type preserved; temporal coercion still delegated) 6/6 pass
ComparisonTemporalCoercionAdapterTests (regression — behavior preserved through the wrapper) 10/10 10/10 pass
FieldTypeCoverageIT.testIpFilters (=, !=, >, <, in, between, cidrmatch, AND-combination over ip) 400 Unable to convert call EQUALS_IP pass
FieldTypeCoverageIT.testIpAndBinaryProjectExpressions (if/case/count(eval()) over ip = and binary =) 400 Unable to convert call EQUALS_IP pass

Check List

  • New functionality includes testing.
  • New functionality has been documented (adapter Javadoc + PR).
  • Commits are signed per the DCO using --signoff or -s.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

PPL =, !=, <, > (and if/case/count(eval())) over ip/binary fields on the
analytics route failed with HTTP 400 "Unable to convert call
EQUALS_IP(binary?, binary?)". ip/binary fields reach the route as VARBINARY,
so the SQL plugin selects the EQUALS_IP (and sibling) IP-comparison UDFs over
the standard comparators; those UDFs are Enumerable-only, have no Substrait
binding, and no backend adapter, so isthmus rejects the call.

Add IpComparisonNormalizationAdapter: when a comparison call's operator carries
a comparison SqlKind but is not the canonical SqlStdOperatorTable singleton and
both operands are VARBINARY, rewrite it to the native comparator (result type
preserved). DataFusion then runs its native byte-wise VARBINARY comparison after
BinaryFunctionAdapter resolves the string literal. Registered in the six
comparison slots, wrapping ComparisonTemporalCoercionAdapter so temporal
coercion on those slots is preserved. Mirrors CidrMatchFunctionAdapter.

Resolves opensearch-project#22672.

Signed-off-by: Kai Huang <ahkcs@amazon.com>
@ahkcs
ahkcs requested a review from a team as a code owner August 7, 2026 21:14
@github-actions github-actions Bot added bug Something isn't working Search:Query Capabilities labels Aug 7, 2026
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

PR Reviewer Guide 🔍

(Review updated until commit 0a3a594)

Here are some key observations to aid the review process:

🧪 PR contains tests
🔒 No security concerns identified
✅ No TODO sections
🔀 No multiple PR themes
⚡ No major issues detected

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Persistent review updated to latest commit 0a3a594

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

✅ Gradle check result for 0a3a594: SUCCESS

@codecov

codecov Bot commented Aug 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 71.53%. Comparing base (c8e2303) to head (0a3a594).

Additional details and impacted files
@@             Coverage Diff              @@
##               main   #22681      +/-   ##
============================================
+ Coverage     71.48%   71.53%   +0.04%     
- Complexity    76960    77045      +85     
============================================
  Files          6156     6156              
  Lines        358444   358444              
  Branches      52246    52246              
============================================
+ Hits         256240   256417     +177     
+ Misses        81792    81646     -146     
+ Partials      20412    20381      -31     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@mch2
mch2 merged commit 511751a into opensearch-project:main Aug 10, 2026
15 of 18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working Search:Query Capabilities

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG][analytics-engine] PPL ip-field equality fails with 400: Unable to convert call EQUALS_IP(binary?, binary?)

2 participants