Hi Andrei,

thanks for bringing up collations in the last community sync. If I got it right, the next step would be to gather a group of interested folks and set up a dedicated sync - preferably with 2+ weeks headsup so that everyone can plan accordingly.

I'm definitely interested in participating in that meeting. How about, e.g., Aug 5th or 7th?

Best,
Alex

On 7/15/26 12:06, Alexander Löser wrote:
Hi Andrei,

I have some questions/thoughts on the suggestions in your latest mail, but I'm happy to defer those for now in favor of a more general discussion.

With regards to your question:
> how much cross-engine pruning interoperability should the format guarantee, versus leave to convention?

I think this is the right question to ask. In fact, I would set the pruning aspect aside for now and only ask: How much cross-engine interoperability should the format guarantee?

If I understand your proposal correctly, you were suggesting that we should allow each engine to choose their ICU version on their own (and skipping pruning if need be), rather than pin a specific ICU version at the table/schema level.

I think this suggestion has merit; for example, it will make it much easier for engines to upgrade their ICU version, independently of Iceberg version changes.

However, allowing each engine to choose its own ICU version has implications beyond pruning - it also impacts execution. ICU does not guarantee the stability of orderings across different versions, i.e., for two strings x and y, x < y may hold in version N, but not in version N+1. While this usually affects only a small subset of code points, ordering changes have occurred with every other ICU release for the last couple of years. Consequently, the same query may return different results on different engines (using different ICU versions). This can manifest in various ways - different sort orders, more/fewer aggregation results, filtering more/less, etc. - which can be very surprising for users. A colleague mentioned that at a previous company, they had to roll back an ICU library upgrade because their users complained about the sort order differences.

Equality deletes are another complication. Until now, whether an equality delete removes a given row is unambiguous - every engine agrees. With different ICU versions, however, engines may draw different conclusions. For example, select * could return different rows if two different ICU versions disagree whether a string matches one of the deleted values. This is, imho, particularly concerning as one DML has the potential to cause different results for all subsequent queries, even if those queries don't use any collated columns. I'm not sure if there would be a way to solve this without disallowing equality deletes on collated columns.

Both of the problems described above would disappear if we align on one specified ICU version.

To close the loop, I think the question we need to answer is: how much interoperability should the spec guarantee? I don't have a strong opinion, yet - I mostly want to make sure we decide this consciously rather than by omission.
I definitely see value in having consistent results across engines.
At the same time, I'm not sure if we can expect consistent results across different engines even today: for example, queries involving upper() or lower() may produce different results depending on the case mappings used by the engine - which depend on the Unicode version, too. That said, upper() and lower() are not part of the Iceberg spec while collations would be, so I'm not sure if this is a good reference point.

Would be curious to hear what you and the others think.

Best, Alex


On 7/3/26 15:56, Andrei Tserakhau via dev wrote:
Hi Alex,

Thanks, these are the right questions. Let me answer them, but I think all three are really facets of one decision worth pulling out, so I'll do that at the end.

Original values vs sort keys. I don't think the two limitations are symmetric. You're right that a bound stored under version X may not hold under Y for either representation, and a naive reader prunes only on an exact version match either way. But they degrade differently: a sort key from X is incomparable under Y (nothing a Y reader can do with it) while an original value is the actual string, so a Y reader can re-interpret it. In the case you describe at the end of your mail, where only a small code-point range moved and a file's values fall outside it, that reader can prove the X bound still holds and prune across versions. Sort keys foreclose that; original values keep it open. So original values are a superset: worst case they match sort keys, best case they prune across versions.

Your two sort-key advantages are real, I just don't think they belong in the format. Truncation: agreed it's hard for collated strings (your abcเก contraction case is exactly the trap), so I sidestepped it, collation bounds must be tight, a writer that can't store the exact min/max omits the bound. Collation-aware truncation with CollationElementIterator is a possible later optimization. Compare cost: pruning is per-file at planning time, not per-row, so collator vs byte compare is in the noise; and an engine that wants the byte path can derive and cache the sort key from the stored value. Original values don't block that, they just don't bake a version-specific encoding into the format.

Column vs file-level version. As you say, every engine can read regardless, so this is a pruning-performance choice, not correctness. In the schema-registered-metrics design a file carries bounds under a declared (collation, version), and a reader prunes any file with a metric for a version it can produce, not only files it wrote. So convergence on one ICU version gives full cross-engine pruning, same as column-level, and a writer or compaction can populate several versions at once. It gives the column-level benefit by convention without making a version bump a format-breaking change.

[One data point from the engine side, since I'm coming at this from the Databricks runtime: our runtime is effectively versionless (customers don't pin an ICU version, and upgrades happen under them) so "the same table read by clients on different ICU versions" isn't a corner case for us, it's the default. That's what pushes me toward per-file versioning: pinning one version per table or column means either forcing the whole fleet to upgrade in lockstep or breaking pruning on every bump, and neither survives a versionless fleet. And in practice most version bumps we've gone through don't reorder the data in a given column at all, which is exactly why keeping original values, and eventually your code-point-range idea, lets a reader keep pruning across a bump instead of falling back to a scan.]

Providers. Agreed. I'll tighten the spec to a registered set like geo, icu to start, utf8 reserved, non-ICU collations added by spec change rather than ad hoc. Interop is the whole point and an open namespace undercuts it.

-----------
Stepping back: I think the three above collapse into one question that needs broader alignment than the two of us:

> how much cross-engine pruning interoperability should the format guarantee, versus leave to convention?

Original-vs-sortkey, column-vs-file version, and open-vs-restricted providers are all that same tradeoff from different angles. It's a values call more than a correctness one, and it binds every engine that hasn't weighed in yet: Trino, Flink, Spark, PyIceberg, rust.

From the DBR side I can say the multi-version case is real rather than theoretical, but that's one engine's vantage point. I'd like to get the interop question in front of the other implementers before we fix field IDs, the dev list is probably enough for now, and a community sync is there if it needs more than async. No rush on that; I'd rather let the thread settle the mechanics first.

Best,
Andrei

On Fri, Jul 3, 2026 at 12:22 AM Alexander Löser <[email protected]> wrote:

    Hi Andrei,

    Thanks for putting together the spec PR and the detailed
    write-up! The approach mostly looks solid to me. I have a few
    questions/initial thoughts regarding the changes you proposed
    (compared to the original proposal):

    > 1 - Bounds store original values, not sort keys, tagged with a
    per-file collation version. ICU/CLDR sort keys aren't stable
    across versions, so storing keys ties every reader to one exact
    version; original values plus a per-file version (readers prune
    only on an exact match) degrade gracefully instead of breaking.
    The schema keeps the collation name unversioned so anyone can read

    If I understand correctly, we’re talking about two separate
    things here:

     1. Tagging a column vs a single file with a certain ICU version
     2. Using collation keys vs original strings (the ones that will
        produce the min/max collation keys)


    For 1, it comes down to a tradeoff:

      * If we tag the column with the ICU version, we’d force engines
        to support one agreed-on ICU version if they want to prune
        files. Engines would be able to prune every file (if they
        support the specific ICU version), or none at all, so there
        is more incentive to support a specific version
      * If we tag individual files with ICU versions, we gain the big
        advantage that engines do not need to agree on a single ICU
        version. However, if I understand correctly, this comes at
        the cost of “fractured” pruning - engines will only be able
        to prune files that were written by themselves (or rather,
        with the same ICU version). As a consequence, performance
        might not really be interoperable between different engines.

    Regardless of the approach we choose, all engines should be able
    to read the data - they might just not be able to prune files.

    For 2, I’m not sure if I understand the advantages of original
    strings yet. As you already pointed out, the collation keys
    depend on the ICU version. However, if I understand correctly,
    the same limitation would apply to the original strings: the sort
    order may (and does) change between different ICU versions, too.
    As a consequence, we can’t assume the original lower/upper bound
    strings we stored for version X will also be lower/upper bounds
    for version Y - at least in the general case. So if I understand
    correctly, we would not gain additional pruning opportunities
    compared to using collation keys. Or am I missing something here?

    At the same time, sort keys do have advantages:

      * Iceberg allows the truncation of upper- and lower bounds.
        This is trivial for binary collation keys. For original
        strings, the task becomes significantly harder: truncating at
        a character boundary, for example, would lead to wrong
        results, as there are some context-sensitive sequences: e.g.,
        with the CLDR root locale, abcเก < abcเ. I think it might be
        doable with ICU’s CollationElementIterator, but it will be
        tricky to get right.
      * Lower/upper bounds are computed once, but will be compared
        many times. With original strings, we would need to either
        convert to the collation key on the fly, or use ICU’s
        collator for a direct comparison. Both options will be slower
        than a raw byte-sequence comparison


    There is one scenario where original values would shine, though.
    I analyzed the order-changes between various ICU versions: in
    many cases, only a small range of code points changes/is moved.
    If we had additional metadata about which code point ranges a
    file contains (e.g., whether it is ASCII only), engines might be
    able to prove that the original string bounds for version X are
    still valid for version Y.
    If I'm not mistaken, this could allow to prune across different
    ICU versions in certain situations, which I’d consider a point in
    favor of original values (and file-level ICU versions).



    > 2 - A provider-qualified identifier (icu.en_US-ci), leaving
    room for non-ICU collations like Spark's UTF8_LCASE, rather than
    assuming ICU as the sole provider.

    Adding a provider-mechanism sounds like a good approach to keep
    the spec open for future collations :slightly_smiling_face: I
    wonder whether we should restrict the set of allowed providers,
    though, similar to how it was done with geo
    <https://lists.apache.org/thread/r5x0do8f241bpf565rx8s5s3wc9ogp0f>.
    My main motivation for this proposal is interoperability. I worry
    that interoperability might suffer or vanish completely if every
    engine can come up with their own definitions.



    Happy to hear your thoughts on this!

    Best, Alex


    On 6/27/26 01:37, Szehon Ho wrote:
    Very nice direction, left some comments on the spec proposal.

    Thanks to you folks for working on it !
    Szehon

    On Fri, Jun 26, 2026 at 3:29 AM Andrei Tserakhau via dev
    <[email protected]> wrote:

        Hi all,

        I've spend some cycle on the collation discussion and make
        something more concrete to react to: a spec-change PR plus
        reference implementations (go and java).

        - Spec change (apache/iceberg#16972): a "collation"
        annotation on string fields, and a
        data_file.collation_bounds field so collated columns stay
        prunable.
        - Reference implementation in iceberg-go
        (apache/iceberg-go#1318): the full path end to end - schema
        annotation, collation-aware comparison (CLDR/UCA), collation
        bounds in the manifest, and version-gated data-file pruning,
        with an Avro round-trip and pruning tests.
        - A lightweight Java POC (link below): the schema annotation
        plus a Collator-backed comparator, to match where the
        discussion is. I deliberately left the manifest/bounds side
        out of Java for now.

        The design follows the original proposal but takes a few
        different turns, mostly to adopt what we learned in Delta.
        The ones I'd most like input on:

        1 - Bounds store original values, not sort keys, tagged with
        a per-file collation version. ICU/CLDR sort keys aren't
        stable across versions, so storing keys ties every reader to
        one exact version; original values plus a per-file version
        (readers prune only on an exact match) degrade gracefully
        instead of breaking. The schema keeps the collation name
        unversioned so anyone can read.

        2 - A provider-qualified identifier (icu.en_US-ci), leaving
        room for non-ICU collations like Spark's UTF8_LCASE, rather
        than assuming ICU as the sole provider.

        3 - One structural question I don't have a strong opinion on
        yet: I put collation_bounds on data_file as a standalone v3
        field, but field id 146 is already the v4 content_stats
        struct, and collation bounds might belong inside that
        typed-stats framework instead. Worth settling before we fix
        field ids.

        The full set of differences and the reader/writer rules are
        in the PR description and the write-up. Comments very
        welcome — both on the calls above and on whether the
        standalone-field vs content_stats direction is the right one.

        Best, Andrei

        - original proposal:
        
https://docs.google.com/document/d/1m8b7u97uteHYjXk-4DNglJSpQO8OcZOCzW2tApCNTW4/edit?tab=t.0
        - spec change: https://github.com/apache/iceberg/pull/16972
        - POC in go: https://github.com/apache/iceberg-go/pull/1318
        - java POC:
        
https://github.com/laskoviymishka/iceberg/tree/prototype/collation-support

        On Mon, Mar 30, 2026 at 10:54 PM Alexander Löser
        <[email protected]> wrote:

            Hi Andrei,

            I'm glad you're interested. Looking forward to
            collaborate with you!
            Thanks for all the feedback here and in the doc. I only
            had a quick glance, but I think you raised some good
            points.  I'll address/respond to your comments as soon
            as I get the chance, hopefully tomorrow.
            I think you also left some comments in this mail that
            are not yet in the doc - I'll move those to a dedicated
            section at the end of the doc, so we can use the doc as
            a single source of truth/discussion.

            > Happy to share our Delta design doc and implementation
            learnings in more detail.

            Sure, sounds good :)

            Best,
            Alex

            On 3/29/26 01:25, Andrei Tserakhau via dev wrote:
            Hi Alexander,

            This looks really interesting. We've been working on
            collation support in Delta and have shipped it in
            production for some time, so this is an area we care
            about a lot. If this proposal moves forward we'd be
            happy to collaborate on the design and implementation.

            The pseudo-field approach for collation metrics is
            clean and composes well with existing Iceberg
            infrastructure. The specifier coverage is comprehensive.

            A few areas worth discussing as this evolves:

            1 - Sort key stability and versioning

            ICU sort keys are not stable across versions, so a
            pinned ICU version bump in a future Iceberg release
            would invalidate all existing collation metrics. In
            multi-engine environments, requiring all engines to
            converge on one ICU version is unrealistic.

            We store original string values instead of sort keys
            and allow per-file version annotations -- worth
            discussing whether something similar could work here.

            2 - Provider abstraction

            The proposal assumes ICU as the sole provider, but
            Spark ships non-ICU collations like UTF8_LCASE that are
            widely used. A provider or namespace layer would
            prevent name collisions and support engine-specific
            collations without future spec changes.

            3 - Operational surface

            A few things that turned out correctness-critical in
            our implementation: partition transforms on collated
            columns (collation-equal but byte-distinct values in
            different directories), sort order semantics, equality
            deletes under collation, and Parquet filter pushdown
            (must be disabled since Parquet has no collation concept).

            These don't all need to be solved in v1 but would help
            to scope them.

            4 - Smaller items (nit's)

            UTF-8 bounds for the original field id should be "must
            write" not "should" -- otherwise backward compat breaks
            for non-aware engines. Engine fallback behavior
            (case-sensitive vs older ICU vs fail) could use a
            recommended preference order to avoid divergent results
            across engines. The collation specifier syntax would
            benefit from a formal grammar.

            ---

            Happy to share our Delta design doc and implementation
            learnings in more detail. Looking forward to the
            discussion.

            Best,
            Andrei

            On Sat, Mar 28, 2026 at 11:49 PM Alexander Löser
            <[email protected]> wrote:

                Hi everyone,

                this is my first interaction with the Iceberg
                community, so here a few words about myself:
                - I'm Alex, a Berlin-based software engineer
                - I've been working at Snowflake for 4 years now
                - I spend most of my time on data types,
                particularly binary, strings and collations.

                I'd like to start a discussion about adding
                collations to the Iceberg spec.

                Conceptually, collations are an annotation on the
                string data type. By default, most engines perform
                string operations case-sensitively.
                Collations allow specifying alternative comparison
                rules. This is useful for achieving, e.g., case- or
                accent-insensitive string operations, or
                language-specific string sorting.
                Collations are supported by many engines:
                Databricks
                
<https://docs.databricks.com/aws/en/sql/language-manual/sql-ref-collation>,
                Spark
                
<https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.functions.collate.html>,
                Snowflake
                <https://docs.snowflake.com/en/sql-reference/collation>,
                Oracle
                
<https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/COLLATION.html>
 - to
                name just a few - this list is not complete.

                In Snowflake, we see heavy use of the collation
                feature. Several users have approached us,
                mentioning they want to migrate to Iceberg tables,
                but are currently blocked by Iceberg's lack of
                collation support.

                Given the widespread support for collations across
                different engines, I believe introducing collations
                to Iceberg will increase interoperability and boost
                its adoption.
                I'd be curious about your thoughts.

                *Goal of the proposal*
                - Support collation specifications for columns
                - Define how collation bounds should be stored -
                UTF-8 based bounds are not useful for collated columns

                *Required Changes*
                - Extend the schema to let (string) fields be
                annotated with a collation

                More details can be found in this doc
                
<https://docs.google.com/document/d/1m8b7u97uteHYjXk-4DNglJSpQO8OcZOCzW2tApCNTW4/edit?tab=t.0#heading=h.y1ant4w2163k>.

                I'm also hoping to present the idea in the next
                community sync.

                Best, Alex

Reply via email to