Skip to content

GH-50988: [C++][Python] Substrait: add mappings for starts_with, ends_with and match_substring - #50989

Open
jonasdedden wants to merge 2 commits into
apache:mainfrom
jonasdedden:gh-50988-substrait-string-match
Open

jonasdedden wants to merge 2 commits into
apache:mainfrom
jonasdedden:gh-50988-substrait-string-match

Conversation

@jonasdedden

@jonasdedden jonasdedden commented Aug 25, 2026 •

Copy link
Copy Markdown
Contributor

Rationale for this change

starts_with, ends_with and match_substring have no Substrait mapping, so any expression using them fails to serialize:

import pyarrow as pa
import pyarrow.compute as pc
from pyarrow.substrait import serialize_expressions

schema = pa.schema([pa.field("cat", pa.string())])
serialize_expressions([pc.starts_with(pc.field("cat"), "al")], ["f"], schema)
# ArrowNotImplementedError: No conversion function exists to convert the
# Arrow function starts_with to a Substrait call

Comparisons, isin and arithmetic serialize fine, so this is a per-function gap. It matters for engines that ingest a PyArrow filter through Substrait, where an unmappable function becomes a hard failure rather than a fallback.

See #50988.

What changes are included in this PR?

Map the three kernels onto starts_with, ends_with and contains from Substrait's functions_string.yaml, in both directions.

The signatures do not line up. Substrait passes the pattern as a second argument, while the Arrow kernels are unary and carry it in MatchSubstringOptions. So:

  • encoding lifts MatchSubstringOptions::pattern out into a literal argument
  • decoding requires that argument to be a non-null string literal, and returns NotImplemented otherwise

Substrait's case_sensitivity option maps onto ignore_case. CASE_INSENSITIVE_ASCII has no Arrow equivalent and returns NotImplemented.

Both caveats are documented in docs/source/cpp/acero/substrait.rst.

Are these changes tested?

Yes.

Substrait.StringMatchExpressionSerialization round-trips all three functions with and without ignore_case. Substrait.StringMatchExpressionDeserialization deserializes hand-written Substrait JSON to cover the default (no option) case and the two rejection paths. test_serializing_string_match_expressions round-trips each function through pyarrow.substrait.

Are there any user-facing changes?

Yes. pc.starts_with, pc.ends_with and pc.match_substring can now be serialized to Substrait and consumed back, and Acero can consume plans using Substrait's starts_with, ends_with and contains. No existing behaviour changes: these previously raised.

…, ends_with and match_substring

The Arrow kernels starts_with, ends_with and match_substring had no Substrait
mapping, so serialize_expressions raised ArrowNotImplementedError for any
expression using them.

Map them onto the starts_with, ends_with and contains functions in Substrait's
functions_string.yaml. The signatures differ: Substrait takes the pattern as a
second argument while the Arrow kernels are unary and carry it in
MatchSubstringOptions, so the decoder requires that argument to be a non-null
string literal. Substrait's case_sensitivity option maps onto ignore_case;
CASE_INSENSITIVE_ASCII has no Arrow equivalent and returns NotImplemented.
@github-actions github-actions Bot added the awaiting review Awaiting review label Aug 25, 2026
@github-actions

Copy link
Copy Markdown

⚠️ GitHub issue #50988 has been automatically assigned in GitHub to PR creator.

@jonasdedden

Copy link
Copy Markdown
Contributor Author

CI failures:

ARM64 macOS GLib & Ruby - red-arrow-flight crashes in TestFlightSQLClient. Already tracked as #50919. Is about Flight, no Substrait involved.

AMD64 Conda C++ AVX2 - 2 of 109 tests failed, arrow-filesystem-test and arrow-s3fs-module-test. "Invalid: Attempt to initialize S3 after it has been finalized." Already tracked as #50930. Substrait test arrow-substrait-substrait-test passed.

AMD64 macOS 15-intel C++ - 1 of 107 tests failed, arrow-gcsfs-test: "Could not start GCS emulator 'storage-testbench' (failed to listen)". The emulator did not come up on the runner. Substrait test arrow-substrait-substrait-test passed.

=> I believe the CI failures are not because of changes introduced in this PR.

@jonasdedden

Copy link
Copy Markdown
Contributor Author

AMD64 macOS 15-intel C++ - 1 of 107 tests failed, arrow-gcsfs-test: "Could not start GCS emulator 'storage-testbench' (failed to listen)". => Raised here and fixed here.

@jonasdedden

Copy link
Copy Markdown
Contributor Author

@rok or @pitrou , any reviews? :)

* Deserialize a bare Expression against an ExtensionSet instead of
  hand-writing a full ExtendedExpression
* Drop the nested invert() round trip, which only exercises generic
  call serialization
* Substrait string literals always decode to utf8, so check for that
  directly
* Drop the unreachable arity check in the encoder
* Reduce the Python test to one case per function
@jonasdedden

Copy link
Copy Markdown
Contributor Author

@kou @felipecrv could you take a look when you have a chance? This adds Substrait mappings for starts_with, ends_with and match_substring, following the same pattern as the hyperbolic trig mappings in #44630.

I trimmed the PR down a bit in 7ec5d6d.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant