From be8541369a2dfba337dc4e563e1cc6fadbdbaa87 Mon Sep 17 00:00:00 2001 From: Prashant Singh Date: Tue, 7 Jul 2026 13:59:34 -0700 Subject: [PATCH 1/8] OpenAPI: Update REST spec expressions to match new expressions spec Adds UnaryPredicate, ComparisonPredicate, and SetPredicate schemas that use 'child' and 'left'/'right' value expression references, matching the JSON serialization defined in Appendix B of the Iceberg Expressions spec (format/expressions-spec.md). The existing UnaryExpression, LiteralExpression, and SetExpression schemas (which use 'term'/'value'/'values') are marked deprecated but retained in Expression.oneOf for backward compatibility with older clients per the spec's "Backward compatibility" section. A ValueExpression placeholder schema is added and referenced by the new predicates; concrete LITERAL/REFERENCE/APPLY forms will be added in a follow-up change. --- open-api/rest-catalog-open-api.py | 147 +++++++++++++++++++++++++++- open-api/rest-catalog-open-api.yaml | 82 +++++++++++++++- 2 files changed, 222 insertions(+), 7 deletions(-) diff --git a/open-api/rest-catalog-open-api.py b/open-api/rest-catalog-open-api.py index 99fe855147c8..0c8ae6d50453 100644 --- a/open-api/rest-catalog-open-api.py +++ b/open-api/rest-catalog-open-api.py @@ -18,7 +18,7 @@ from __future__ import annotations from datetime import date, timedelta -from typing import Dict, Literal +from typing import Any, Dict, Literal from uuid import UUID from pydantic import Base64Str, BaseModel, ConfigDict, Field, RootModel @@ -151,6 +151,13 @@ class ExpressionType(RootModel[str]): ) +class ValueExpression(RootModel[Any]): + root: Any = Field( + ..., + description='A value expression: a literal value, a field reference, or a function apply. See the Iceberg Expressions spec, Appendix B (JSON serialization) for the full definition. Concrete schemas for LITERAL, REFERENCE, and APPLY forms will be added in a follow-up change.\n', + ) + + class TrueExpression(BaseModel): type: Literal['true'] = Field( ..., @@ -209,6 +216,115 @@ class FalseExpression(BaseModel): ) +class UnaryPredicate(BaseModel): + """ + A predicate that tests a single value expression. Replaces the deprecated UnaryExpression that used 'term'. + + """ + + type: Literal['is-null', 'not-null', 'is-nan', 'not-nan'] = Field( + ..., + examples=[ + [ + 'true', + 'false', + 'eq', + 'and', + 'or', + 'not', + 'in', + 'not-in', + 'lt', + 'lt-eq', + 'gt', + 'gt-eq', + 'not-eq', + 'starts-with', + 'not-starts-with', + 'is-null', + 'not-null', + 'is-nan', + 'not-nan', + ] + ], + ) + child: ValueExpression + + +class ComparisonPredicate(BaseModel): + """ + A predicate that compares two value expressions. Replaces the deprecated LiteralExpression that used 'term' and 'value'. + + """ + + type: Literal[ + 'lt', 'lt-eq', 'gt', 'gt-eq', 'eq', 'not-eq', 'starts-with', 'not-starts-with' + ] = Field( + ..., + examples=[ + [ + 'true', + 'false', + 'eq', + 'and', + 'or', + 'not', + 'in', + 'not-in', + 'lt', + 'lt-eq', + 'gt', + 'gt-eq', + 'not-eq', + 'starts-with', + 'not-starts-with', + 'is-null', + 'not-null', + 'is-nan', + 'not-nan', + ] + ], + ) + left: ValueExpression + right: ValueExpression + + +class SetPredicate(BaseModel): + """ + A predicate that tests whether a value expression is in a set of literals. Replaces the deprecated SetExpression that used 'term'. + + """ + + type: Literal['in', 'not-in'] = Field( + ..., + examples=[ + [ + 'true', + 'false', + 'eq', + 'and', + 'or', + 'not', + 'in', + 'not-in', + 'lt', + 'lt-eq', + 'gt', + 'gt-eq', + 'not-eq', + 'starts-with', + 'not-starts-with', + 'is-null', + 'not-null', + 'is-nan', + 'not-nan', + ] + ], + ) + child: ValueExpression + values: list[ValueExpression] + + class Reference(RootModel[str]): root: str = Field(..., examples=[['column-name']]) @@ -1287,6 +1403,11 @@ class FunctionDefinitionVersion(BaseModel): class UnaryExpression(BaseModel): + """ + Deprecated. Use UnaryPredicate with 'child' (a ValueExpression) instead. Retained for backward compatibility with older clients per the Iceberg Expressions spec (Appendix B, Backward compatibility). + + """ + type: Literal['is-null', 'not-null', 'is-nan', 'not-nan'] = Field( ..., examples=[ @@ -1317,6 +1438,11 @@ class UnaryExpression(BaseModel): class LiteralExpression(BaseModel): + """ + Deprecated. Use ComparisonPredicate with 'left' and 'right' (ValueExpressions) instead. Retained for backward compatibility with older clients per the Iceberg Expressions spec (Appendix B, Backward compatibility). + + """ + type: Literal[ 'lt', 'lt-eq', 'gt', 'gt-eq', 'eq', 'not-eq', 'starts-with', 'not-starts-with' ] = Field( @@ -1350,6 +1476,11 @@ class LiteralExpression(BaseModel): class SetExpression(BaseModel): + """ + Deprecated. Use SetPredicate with 'child' (a ValueExpression) instead. Retained for backward compatibility with older clients per the Iceberg Expressions spec (Appendix B, Backward compatibility). + + """ + type: Literal['in', 'not-in'] = Field( ..., examples=[ @@ -1912,9 +2043,12 @@ class Expression( | FalseExpression | AndOrExpression | NotExpression - | SetExpression - | LiteralExpression + | UnaryPredicate + | ComparisonPredicate + | SetPredicate | UnaryExpression + | LiteralExpression + | SetExpression ] ): root: ( @@ -1922,9 +2056,12 @@ class Expression( | FalseExpression | AndOrExpression | NotExpression - | SetExpression - | LiteralExpression + | UnaryPredicate + | ComparisonPredicate + | SetPredicate | UnaryExpression + | LiteralExpression + | SetExpression ) diff --git a/open-api/rest-catalog-open-api.yaml b/open-api/rest-catalog-open-api.yaml index 419041d12a87..07612e9ba0b6 100644 --- a/open-api/rest-catalog-open-api.yaml +++ b/open-api/rest-catalog-open-api.yaml @@ -2534,9 +2534,12 @@ components: - $ref: '#/components/schemas/FalseExpression' - $ref: '#/components/schemas/AndOrExpression' - $ref: '#/components/schemas/NotExpression' - - $ref: '#/components/schemas/SetExpression' - - $ref: '#/components/schemas/LiteralExpression' + - $ref: '#/components/schemas/UnaryPredicate' + - $ref: '#/components/schemas/ComparisonPredicate' + - $ref: '#/components/schemas/SetPredicate' - $ref: '#/components/schemas/UnaryExpression' + - $ref: '#/components/schemas/LiteralExpression' + - $ref: '#/components/schemas/SetExpression' ExpressionType: type: string @@ -2561,6 +2564,13 @@ components: - "is-nan" - "not-nan" + ValueExpression: + description: > + A value expression: a literal value, a field reference, or a function apply. + See the Iceberg Expressions spec, Appendix B (JSON serialization) for the + full definition. Concrete schemas for LITERAL, REFERENCE, and APPLY forms + will be added in a follow-up change. + TrueExpression: type: object required: @@ -2606,7 +2616,65 @@ components: child: $ref: '#/components/schemas/Expression' + UnaryPredicate: + description: > + A predicate that tests a single value expression. Replaces the deprecated + UnaryExpression that used 'term'. + type: object + required: + - type + - child + properties: + type: + $ref: '#/components/schemas/ExpressionType' + enum: ["is-null", "not-null", "is-nan", "not-nan"] + child: + $ref: '#/components/schemas/ValueExpression' + + ComparisonPredicate: + description: > + A predicate that compares two value expressions. Replaces the deprecated + LiteralExpression that used 'term' and 'value'. + type: object + required: + - type + - left + - right + properties: + type: + $ref: '#/components/schemas/ExpressionType' + enum: ["lt", "lt-eq", "gt", "gt-eq", "eq", "not-eq", "starts-with", "not-starts-with"] + left: + $ref: '#/components/schemas/ValueExpression' + right: + $ref: '#/components/schemas/ValueExpression' + + SetPredicate: + description: > + A predicate that tests whether a value expression is in a set of literals. + Replaces the deprecated SetExpression that used 'term'. + type: object + required: + - type + - child + - values + properties: + type: + $ref: '#/components/schemas/ExpressionType' + enum: ["in", "not-in"] + child: + $ref: '#/components/schemas/ValueExpression' + values: + type: array + items: + $ref: '#/components/schemas/ValueExpression' + UnaryExpression: + deprecated: true + description: > + Deprecated. Use UnaryPredicate with 'child' (a ValueExpression) instead. + Retained for backward compatibility with older clients per the Iceberg + Expressions spec (Appendix B, Backward compatibility). type: object required: - type @@ -2619,6 +2687,11 @@ components: $ref: '#/components/schemas/Term' LiteralExpression: + deprecated: true + description: > + Deprecated. Use ComparisonPredicate with 'left' and 'right' (ValueExpressions) + instead. Retained for backward compatibility with older clients per the + Iceberg Expressions spec (Appendix B, Backward compatibility). type: object required: - type @@ -2634,6 +2707,11 @@ components: $ref: '#/components/schemas/PrimitiveTypeValue' SetExpression: + deprecated: true + description: > + Deprecated. Use SetPredicate with 'child' (a ValueExpression) instead. + Retained for backward compatibility with older clients per the Iceberg + Expressions spec (Appendix B, Backward compatibility). type: object required: - type From adb9feee3df18a14633471f8f48bcda226ebd34d Mon Sep 17 00:00:00 2001 From: Prashant Singh Date: Wed, 8 Jul 2026 11:36:48 -0700 Subject: [PATCH 2/8] OpenAPI: Support bare boolean predicate form in REST expressions Extends Expression.oneOf with a bare `boolean` alternative, matching the spec-conformant PREDICATE production in the Iceberg Expressions spec (format/expressions-spec.md, Appendix B) which defines `PREDICATE: true | false | { ... }`. The pre-existing TrueExpression and FalseExpression object-form schemas (`{"type": "true"}` / `{"type": "false"}`) are marked deprecated and retained in Expression.oneOf for backward compatibility with older clients that send the object form. --- open-api/rest-catalog-open-api.py | 16 ++++++++++++++-- open-api/rest-catalog-open-api.yaml | 11 +++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/open-api/rest-catalog-open-api.py b/open-api/rest-catalog-open-api.py index 0c8ae6d50453..d962c20ca002 100644 --- a/open-api/rest-catalog-open-api.py +++ b/open-api/rest-catalog-open-api.py @@ -159,6 +159,11 @@ class ValueExpression(RootModel[Any]): class TrueExpression(BaseModel): + """ + Deprecated. Use the bare boolean literal `true` as a predicate instead. Retained for backward compatibility with older clients per the Iceberg Expressions spec (Appendix B, JSON serialization). + + """ + type: Literal['true'] = Field( ..., examples=[ @@ -188,6 +193,11 @@ class TrueExpression(BaseModel): class FalseExpression(BaseModel): + """ + Deprecated. Use the bare boolean literal `false` as a predicate instead. Retained for backward compatibility with older clients per the Iceberg Expressions spec (Appendix B, JSON serialization). + + """ + type: Literal['false'] = Field( ..., examples=[ @@ -2039,7 +2049,8 @@ class Type(RootModel[PrimitiveType | StructType | ListType | MapType]): class Expression( RootModel[ - TrueExpression + bool + | TrueExpression | FalseExpression | AndOrExpression | NotExpression @@ -2052,7 +2063,8 @@ class Expression( ] ): root: ( - TrueExpression + bool + | TrueExpression | FalseExpression | AndOrExpression | NotExpression diff --git a/open-api/rest-catalog-open-api.yaml b/open-api/rest-catalog-open-api.yaml index 07612e9ba0b6..d733cb796ca8 100644 --- a/open-api/rest-catalog-open-api.yaml +++ b/open-api/rest-catalog-open-api.yaml @@ -2530,6 +2530,7 @@ components: Expression: oneOf: + - type: boolean - $ref: '#/components/schemas/TrueExpression' - $ref: '#/components/schemas/FalseExpression' - $ref: '#/components/schemas/AndOrExpression' @@ -2572,6 +2573,11 @@ components: will be added in a follow-up change. TrueExpression: + deprecated: true + description: > + Deprecated. Use the bare boolean literal `true` as a predicate instead. + Retained for backward compatibility with older clients per the Iceberg + Expressions spec (Appendix B, JSON serialization). type: object required: - type @@ -2581,6 +2587,11 @@ components: const: "true" FalseExpression: + deprecated: true + description: > + Deprecated. Use the bare boolean literal `false` as a predicate instead. + Retained for backward compatibility with older clients per the Iceberg + Expressions spec (Appendix B, JSON serialization). type: object required: - type From 88e2b8ec042d6ce9efcae594e367e2dd7341a83c Mon Sep 17 00:00:00 2001 From: Prashant Singh Date: Thu, 9 Jul 2026 12:18:31 -0700 Subject: [PATCH 3/8] OpenAPI: Add ValueExpression schemas per Iceberg Expressions spec Appendix B Replaces the ValueExpression placeholder with concrete oneOf schemas for LITERAL, REFERENCE, and APPLY forms, matching Appendix B of the Iceberg Expressions spec. Also adds the deprecated reference form for backward compatibility with older REST predicates. Schemas added: - Literal (bare scalar, typed literal object with/without data-type) - Literals (bare array or typed object; used by SetPredicate.values) - FieldReference (bound by id or unbound by name) - Apply (function application over ValueExpression or Expression args) - FunctionReference (four forms: name, name list, identifier object, catalog + identifier object) - DeprecatedReference (backward-compat form wired into Term's oneOf) SetPredicate.values now references Literals rather than an array of ValueExpression to match Appendix B's LITERALS grammar. --- open-api/rest-catalog-open-api.yaml | 160 +++++++++++++++++++++++++++- 1 file changed, 155 insertions(+), 5 deletions(-) diff --git a/open-api/rest-catalog-open-api.yaml b/open-api/rest-catalog-open-api.yaml index d733cb796ca8..b0009c16059d 100644 --- a/open-api/rest-catalog-open-api.yaml +++ b/open-api/rest-catalog-open-api.yaml @@ -2567,10 +2567,13 @@ components: ValueExpression: description: > - A value expression: a literal value, a field reference, or a function apply. - See the Iceberg Expressions spec, Appendix B (JSON serialization) for the - full definition. Concrete schemas for LITERAL, REFERENCE, and APPLY forms - will be added in a follow-up change. + A value expression per Iceberg Expressions spec, Appendix B: a literal, + a field reference, or a function application. The result type is + determined when the expression is bound to an input schema. + oneOf: + - $ref: '#/components/schemas/Literal' + - $ref: '#/components/schemas/FieldReference' + - $ref: '#/components/schemas/Apply' TrueExpression: deprecated: true @@ -2676,9 +2679,138 @@ components: child: $ref: '#/components/schemas/ValueExpression' values: + $ref: '#/components/schemas/Literals' + + Literal: + description: > + A literal value expression per Iceberg Expressions spec, Appendix B. + Three JSON forms are accepted: a bare scalar value, a typed literal + object without a data-type, or a typed literal object with an + explicit data-type. + oneOf: + - type: string + - type: number + - type: integer + - type: boolean + - type: 'null' + - type: object + required: + - type + - value + properties: + type: + type: string + const: "literal" + value: + $ref: '#/components/schemas/PrimitiveTypeValue' + data-type: + $ref: '#/components/schemas/PrimitiveType' + + Literals: + description: > + A set of literal values per Iceberg Expressions spec, Appendix B. + Two JSON forms: a bare array of literals, or a typed object with an + explicit data-type applied to all values. + oneOf: + - type: array + items: + $ref: '#/components/schemas/Literal' + - type: object + required: + - type + - values + properties: + type: + type: string + const: "literals" + values: + type: array + items: + $ref: '#/components/schemas/PrimitiveTypeValue' + data-type: + $ref: '#/components/schemas/PrimitiveType' + + FieldReference: + description: > + A reference to a field per Iceberg Expressions spec, Appendix B. + Either a bound reference (by field ID) or an unbound reference (by + name). The context in which an expression is used determines which + form is valid. + oneOf: + - type: object + required: + - type + - id + properties: + type: + type: string + const: "reference" + id: + type: integer + - type: object + required: + - type + - name + properties: + type: + type: string + const: "reference" + name: + type: string + + Apply: + description: > + A function application per Iceberg Expressions spec, Appendix B. + Represents the result of calling a function on zero or more value + expressions or predicates. + type: object + required: + - type + - function + - arguments + properties: + type: + type: string + const: "apply" + function: + $ref: '#/components/schemas/FunctionReference' + arguments: type: array items: - $ref: '#/components/schemas/ValueExpression' + oneOf: + - $ref: '#/components/schemas/ValueExpression' + - $ref: '#/components/schemas/Expression' + + FunctionReference: + description: > + A reference to a function per Iceberg Expressions spec, Appendix B. + Four JSON forms: a bare name (single-part identifier), a list of + names (multi-part identifier), an object with an identifier, or an + object with both a catalog and an identifier. + oneOf: + - type: string + - type: array + items: + type: string + - type: object + required: + - identifier + properties: + identifier: + type: array + items: + type: string + - type: object + required: + - catalog + - identifier + properties: + catalog: + type: string + identifier: + type: array + items: + type: string UnaryExpression: deprecated: true @@ -2743,12 +2875,30 @@ components: oneOf: - $ref: '#/components/schemas/Reference' - $ref: '#/components/schemas/TransformTerm' + - $ref: '#/components/schemas/DeprecatedReference' Reference: type: string example: - "column-name" + DeprecatedReference: + deprecated: true + description: > + Deprecated backward-compatibility form of a field reference used in + older REST predicates. Use FieldReference instead per Iceberg + Expressions spec, Appendix B (Backward compatibility). + type: object + required: + - type + - term + properties: + type: + type: string + const: "reference" + term: + type: string + TransformTerm: type: object required: From c3df8f6a4fad3f5716cf7d8e0457247fcf4eb77c Mon Sep 17 00:00:00 2001 From: Prashant Singh Date: Thu, 9 Jul 2026 12:18:31 -0700 Subject: [PATCH 4/8] OpenAPI: Regenerate rest-catalog-open-api.py --- open-api/rest-catalog-open-api.py | 310 ++++++++++++++++++++---------- 1 file changed, 213 insertions(+), 97 deletions(-) diff --git a/open-api/rest-catalog-open-api.py b/open-api/rest-catalog-open-api.py index d962c20ca002..c038d5783af8 100644 --- a/open-api/rest-catalog-open-api.py +++ b/open-api/rest-catalog-open-api.py @@ -18,7 +18,7 @@ from __future__ import annotations from datetime import date, timedelta -from typing import Any, Dict, Literal +from typing import Dict, Literal from uuid import UUID from pydantic import Base64Str, BaseModel, ConfigDict, Field, RootModel @@ -151,13 +151,6 @@ class ExpressionType(RootModel[str]): ) -class ValueExpression(RootModel[Any]): - root: Any = Field( - ..., - description='A value expression: a literal value, a field reference, or a function apply. See the Iceberg Expressions spec, Appendix B (JSON serialization) for the full definition. Concrete schemas for LITERAL, REFERENCE, and APPLY forms will be added in a follow-up change.\n', - ) - - class TrueExpression(BaseModel): """ Deprecated. Use the bare boolean literal `true` as a predicate instead. Retained for backward compatibility with older clients per the Iceberg Expressions spec (Appendix B, JSON serialization). @@ -226,119 +219,75 @@ class FalseExpression(BaseModel): ) -class UnaryPredicate(BaseModel): +class FieldReference1(BaseModel): """ - A predicate that tests a single value expression. Replaces the deprecated UnaryExpression that used 'term'. + A reference to a field per Iceberg Expressions spec, Appendix B. Either a bound reference (by field ID) or an unbound reference (by name). The context in which an expression is used determines which form is valid. """ - type: Literal['is-null', 'not-null', 'is-nan', 'not-nan'] = Field( - ..., - examples=[ - [ - 'true', - 'false', - 'eq', - 'and', - 'or', - 'not', - 'in', - 'not-in', - 'lt', - 'lt-eq', - 'gt', - 'gt-eq', - 'not-eq', - 'starts-with', - 'not-starts-with', - 'is-null', - 'not-null', - 'is-nan', - 'not-nan', - ] - ], - ) - child: ValueExpression + type: Literal['reference'] + id: int -class ComparisonPredicate(BaseModel): +class FieldReference2(BaseModel): """ - A predicate that compares two value expressions. Replaces the deprecated LiteralExpression that used 'term' and 'value'. + A reference to a field per Iceberg Expressions spec, Appendix B. Either a bound reference (by field ID) or an unbound reference (by name). The context in which an expression is used determines which form is valid. """ - type: Literal[ - 'lt', 'lt-eq', 'gt', 'gt-eq', 'eq', 'not-eq', 'starts-with', 'not-starts-with' - ] = Field( + type: Literal['reference'] + name: str + + +class FieldReference(RootModel[FieldReference1 | FieldReference2]): + root: FieldReference1 | FieldReference2 = Field( ..., - examples=[ - [ - 'true', - 'false', - 'eq', - 'and', - 'or', - 'not', - 'in', - 'not-in', - 'lt', - 'lt-eq', - 'gt', - 'gt-eq', - 'not-eq', - 'starts-with', - 'not-starts-with', - 'is-null', - 'not-null', - 'is-nan', - 'not-nan', - ] - ], + description='A reference to a field per Iceberg Expressions spec, Appendix B. Either a bound reference (by field ID) or an unbound reference (by name). The context in which an expression is used determines which form is valid.\n', ) - left: ValueExpression - right: ValueExpression -class SetPredicate(BaseModel): +class FunctionReference1(BaseModel): """ - A predicate that tests whether a value expression is in a set of literals. Replaces the deprecated SetExpression that used 'term'. + A reference to a function per Iceberg Expressions spec, Appendix B. Four JSON forms: a bare name (single-part identifier), a list of names (multi-part identifier), an object with an identifier, or an object with both a catalog and an identifier. """ - type: Literal['in', 'not-in'] = Field( + identifier: list[str] + + +class FunctionReference2(BaseModel): + """ + A reference to a function per Iceberg Expressions spec, Appendix B. Four JSON forms: a bare name (single-part identifier), a list of names (multi-part identifier), an object with an identifier, or an object with both a catalog and an identifier. + + """ + + catalog: str + identifier: list[str] + + +class FunctionReference( + RootModel[str | list[str] | FunctionReference1 | FunctionReference2] +): + root: str | list[str] | FunctionReference1 | FunctionReference2 = Field( ..., - examples=[ - [ - 'true', - 'false', - 'eq', - 'and', - 'or', - 'not', - 'in', - 'not-in', - 'lt', - 'lt-eq', - 'gt', - 'gt-eq', - 'not-eq', - 'starts-with', - 'not-starts-with', - 'is-null', - 'not-null', - 'is-nan', - 'not-nan', - ] - ], + description='A reference to a function per Iceberg Expressions spec, Appendix B. Four JSON forms: a bare name (single-part identifier), a list of names (multi-part identifier), an object with an identifier, or an object with both a catalog and an identifier.\n', ) - child: ValueExpression - values: list[ValueExpression] class Reference(RootModel[str]): root: str = Field(..., examples=[['column-name']]) +class DeprecatedReference(BaseModel): + """ + Deprecated backward-compatibility form of a field reference used in older REST predicates. Use FieldReference instead per Iceberg Expressions spec, Appendix B (Backward compatibility). + + """ + + type: Literal['reference'] + term: str + + class Transform(RootModel[str]): root: str = Field( ..., @@ -1261,6 +1210,42 @@ class RenameTableRequest(BaseModel): destination: TableIdentifier +class Literal1(BaseModel): + """ + A literal value expression per Iceberg Expressions spec, Appendix B. Three JSON forms are accepted: a bare scalar value, a typed literal object without a data-type, or a typed literal object with an explicit data-type. + + """ + + type: Literal['literal'] + value: PrimitiveTypeValue + data_type: PrimitiveType | None = Field(None, alias='data-type') + + +class LiteralModel(RootModel[str | float | int | bool | Literal1 | None]): + root: str | float | int | bool | Literal1 | None = Field( + ..., + description='A literal value expression per Iceberg Expressions spec, Appendix B. Three JSON forms are accepted: a bare scalar value, a typed literal object without a data-type, or a typed literal object with an explicit data-type.\n', + ) + + +class Literals1(BaseModel): + """ + A set of literal values per Iceberg Expressions spec, Appendix B. Two JSON forms: a bare array of literals, or a typed object with an explicit data-type applied to all values. + + """ + + type: Literal['literals'] + values: list[PrimitiveTypeValue] + data_type: PrimitiveType | None = Field(None, alias='data-type') + + +class Literals(RootModel[list[LiteralModel | None] | Literals1]): + root: list[LiteralModel | None] | Literals1 = Field( + ..., + description='A set of literal values per Iceberg Expressions spec, Appendix B. Two JSON forms: a bare array of literals, or a typed object with an explicit data-type applied to all values.\n', + ) + + class TransformTerm(BaseModel): type: Literal['transform'] transform: Transform @@ -1373,8 +1358,8 @@ class FetchScanTasksRequest(BaseModel): plan_task: PlanTask = Field(..., alias='plan-task') -class Term(RootModel[Reference | TransformTerm]): - root: Reference | TransformTerm +class Term(RootModel[Reference | TransformTerm | DeprecatedReference]): + root: Reference | TransformTerm | DeprecatedReference class SetStatisticsUpdate(BaseUpdate): @@ -1613,6 +1598,126 @@ class NotExpression(BaseModel): child: Expression +class UnaryPredicate(BaseModel): + """ + A predicate that tests a single value expression. Replaces the deprecated UnaryExpression that used 'term'. + + """ + + type: Literal['is-null', 'not-null', 'is-nan', 'not-nan'] = Field( + ..., + examples=[ + [ + 'true', + 'false', + 'eq', + 'and', + 'or', + 'not', + 'in', + 'not-in', + 'lt', + 'lt-eq', + 'gt', + 'gt-eq', + 'not-eq', + 'starts-with', + 'not-starts-with', + 'is-null', + 'not-null', + 'is-nan', + 'not-nan', + ] + ], + ) + child: ValueExpression + + +class ComparisonPredicate(BaseModel): + """ + A predicate that compares two value expressions. Replaces the deprecated LiteralExpression that used 'term' and 'value'. + + """ + + type: Literal[ + 'lt', 'lt-eq', 'gt', 'gt-eq', 'eq', 'not-eq', 'starts-with', 'not-starts-with' + ] = Field( + ..., + examples=[ + [ + 'true', + 'false', + 'eq', + 'and', + 'or', + 'not', + 'in', + 'not-in', + 'lt', + 'lt-eq', + 'gt', + 'gt-eq', + 'not-eq', + 'starts-with', + 'not-starts-with', + 'is-null', + 'not-null', + 'is-nan', + 'not-nan', + ] + ], + ) + left: ValueExpression + right: ValueExpression + + +class SetPredicate(BaseModel): + """ + A predicate that tests whether a value expression is in a set of literals. Replaces the deprecated SetExpression that used 'term'. + + """ + + type: Literal['in', 'not-in'] = Field( + ..., + examples=[ + [ + 'true', + 'false', + 'eq', + 'and', + 'or', + 'not', + 'in', + 'not-in', + 'lt', + 'lt-eq', + 'gt', + 'gt-eq', + 'not-eq', + 'starts-with', + 'not-starts-with', + 'is-null', + 'not-null', + 'is-nan', + 'not-nan', + ] + ], + ) + child: ValueExpression + values: Literals + + +class Apply(BaseModel): + """ + A function application per Iceberg Expressions spec, Appendix B. Represents the result of calling a function on zero or more value expressions or predicates. + + """ + + type: Literal['apply'] + function: FunctionReference + arguments: list[ValueExpression | Expression] + + class TableMetadata(BaseModel): format_version: int = Field(..., alias='format-version', ge=1, le=3) table_uuid: str = Field(..., alias='table-uuid') @@ -2077,6 +2182,13 @@ class Expression( ) +class ValueExpression(RootModel[LiteralModel | FieldReference | Apply | None]): + root: LiteralModel | FieldReference | Apply | None = Field( + ..., + description='A value expression per Iceberg Expressions spec, Appendix B: a literal, a field reference, or a function application. The result type is determined when the expression is bound to an input schema.\n', + ) + + class TableUpdate( RootModel[ AssignUUIDUpdate @@ -2235,6 +2347,10 @@ class PlanTableScanResult( MapType.model_rebuild() AndOrExpression.model_rebuild() NotExpression.model_rebuild() +UnaryPredicate.model_rebuild() +ComparisonPredicate.model_rebuild() +SetPredicate.model_rebuild() +Apply.model_rebuild() TableMetadata.model_rebuild() ViewMetadata.model_rebuild() AddSchemaUpdate.model_rebuild() From aaabc91944536e0ca0ad737ec3003e4986f5f0a2 Mon Sep 17 00:00:00 2001 From: Prashant Singh Date: Thu, 9 Jul 2026 16:50:58 -0700 Subject: [PATCH 5/8] OpenAPI: Tighten ValueExpression schemas for Appendix B correctness MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apply spec-mandated fixes surfaced by adversarial review against format/expressions-spec.md Appendix B: - Literal.oneOf: remove the type:integer branch — every integer already matches type:number, so the duplicate broke strict oneOf validators. - Apply.arguments.items: switch oneOf to anyOf. Spec line 265 defines FUNC_ARG as EXPR | PREDICATE (semantic "or"), and a bare boolean legitimately matches both branches; oneOf's "exactly one" rejects it. - FieldReference and DeprecatedReference: add additionalProperties:false so the shared type:"reference" discriminator resolves to exactly one branch. Without it, payloads mixing id/name/term validate ambiguously. - Literals typed-object form: mark data-type required per spec line 258 grammar (it appears with no optional marker). - FunctionReference: add additionalProperties:false to the {identifier} form so it is disjoint from the {catalog, identifier} form (spec lines 267-269 enumerate them as distinct productions). Add minItems:1 to every identifier array per spec line 82: "At least one part, the function name, is required." --- open-api/rest-catalog-open-api.yaml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/open-api/rest-catalog-open-api.yaml b/open-api/rest-catalog-open-api.yaml index b0009c16059d..dba245c2b815 100644 --- a/open-api/rest-catalog-open-api.yaml +++ b/open-api/rest-catalog-open-api.yaml @@ -2690,7 +2690,6 @@ components: oneOf: - type: string - type: number - - type: integer - type: boolean - type: 'null' - type: object @@ -2719,6 +2718,7 @@ components: required: - type - values + - data-type properties: type: type: string @@ -2738,6 +2738,7 @@ components: form is valid. oneOf: - type: object + additionalProperties: false required: - type - id @@ -2748,6 +2749,7 @@ components: id: type: integer - type: object + additionalProperties: false required: - type - name @@ -2777,7 +2779,7 @@ components: arguments: type: array items: - oneOf: + anyOf: - $ref: '#/components/schemas/ValueExpression' - $ref: '#/components/schemas/Expression' @@ -2790,14 +2792,17 @@ components: oneOf: - type: string - type: array + minItems: 1 items: type: string - type: object + additionalProperties: false required: - identifier properties: identifier: type: array + minItems: 1 items: type: string - type: object @@ -2809,6 +2814,7 @@ components: type: string identifier: type: array + minItems: 1 items: type: string @@ -2889,6 +2895,7 @@ components: older REST predicates. Use FieldReference instead per Iceberg Expressions spec, Appendix B (Backward compatibility). type: object + additionalProperties: false required: - type - term From eb7e197457565932f7accb37a6a91ec1af58858c Mon Sep 17 00:00:00 2001 From: Prashant Singh Date: Thu, 9 Jul 2026 16:50:58 -0700 Subject: [PATCH 6/8] OpenAPI: Regenerate rest-catalog-open-api.py --- open-api/rest-catalog-open-api.py | 38 +++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/open-api/rest-catalog-open-api.py b/open-api/rest-catalog-open-api.py index c038d5783af8..41f4a3de5522 100644 --- a/open-api/rest-catalog-open-api.py +++ b/open-api/rest-catalog-open-api.py @@ -225,6 +225,9 @@ class FieldReference1(BaseModel): """ + model_config = ConfigDict( + extra='forbid', + ) type: Literal['reference'] id: int @@ -235,6 +238,9 @@ class FieldReference2(BaseModel): """ + model_config = ConfigDict( + extra='forbid', + ) type: Literal['reference'] name: str @@ -246,29 +252,40 @@ class FieldReference(RootModel[FieldReference1 | FieldReference2]): ) -class FunctionReference1(BaseModel): +class FunctionReference1(RootModel[list[str]]): + root: list[str] = Field( + ..., + description='A reference to a function per Iceberg Expressions spec, Appendix B. Four JSON forms: a bare name (single-part identifier), a list of names (multi-part identifier), an object with an identifier, or an object with both a catalog and an identifier.\n', + min_length=1, + ) + + +class FunctionReference2(BaseModel): """ A reference to a function per Iceberg Expressions spec, Appendix B. Four JSON forms: a bare name (single-part identifier), a list of names (multi-part identifier), an object with an identifier, or an object with both a catalog and an identifier. """ - identifier: list[str] + model_config = ConfigDict( + extra='forbid', + ) + identifier: list[str] = Field(..., min_length=1) -class FunctionReference2(BaseModel): +class FunctionReference3(BaseModel): """ A reference to a function per Iceberg Expressions spec, Appendix B. Four JSON forms: a bare name (single-part identifier), a list of names (multi-part identifier), an object with an identifier, or an object with both a catalog and an identifier. """ catalog: str - identifier: list[str] + identifier: list[str] = Field(..., min_length=1) class FunctionReference( - RootModel[str | list[str] | FunctionReference1 | FunctionReference2] + RootModel[str | FunctionReference1 | FunctionReference2 | FunctionReference3] ): - root: str | list[str] | FunctionReference1 | FunctionReference2 = Field( + root: str | FunctionReference1 | FunctionReference2 | FunctionReference3 = Field( ..., description='A reference to a function per Iceberg Expressions spec, Appendix B. Four JSON forms: a bare name (single-part identifier), a list of names (multi-part identifier), an object with an identifier, or an object with both a catalog and an identifier.\n', ) @@ -284,6 +301,9 @@ class DeprecatedReference(BaseModel): """ + model_config = ConfigDict( + extra='forbid', + ) type: Literal['reference'] term: str @@ -1221,8 +1241,8 @@ class Literal1(BaseModel): data_type: PrimitiveType | None = Field(None, alias='data-type') -class LiteralModel(RootModel[str | float | int | bool | Literal1 | None]): - root: str | float | int | bool | Literal1 | None = Field( +class LiteralModel(RootModel[str | float | bool | Literal1 | None]): + root: str | float | bool | Literal1 | None = Field( ..., description='A literal value expression per Iceberg Expressions spec, Appendix B. Three JSON forms are accepted: a bare scalar value, a typed literal object without a data-type, or a typed literal object with an explicit data-type.\n', ) @@ -1236,7 +1256,7 @@ class Literals1(BaseModel): type: Literal['literals'] values: list[PrimitiveTypeValue] - data_type: PrimitiveType | None = Field(None, alias='data-type') + data_type: PrimitiveType = Field(..., alias='data-type') class Literals(RootModel[list[LiteralModel | None] | Literals1]): From d0a96d88362c87cf90b95417f2eb6ba0f9663727 Mon Sep 17 00:00:00 2001 From: Prashant Singh Date: Thu, 9 Jul 2026 17:23:53 -0700 Subject: [PATCH 7/8] OpenAPI: Further tighten ValueExpression schemas per adversarial review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Additional spec-fidelity fixes surfaced by iterated compliance/divergence review against format/expressions-spec.md Appendix B: - Literal typed-object, Literals typed-object, Apply, and FunctionReference {catalog, identifier} all get additionalProperties:false so their JSON shapes match the closed grammar productions in spec lines 255-269. - LiteralExpression.value now refs Literal (was PrimitiveTypeValue) — spec line 304 explicitly says the deprecated CMP_OP form takes LITERAL. - SetExpression.values now refs Literals (was inline PrimitiveTypeValue array) — spec line 305 says LITERALS, which includes bare-array and typed-object forms. - TransformTerm.term now refs Term (was Reference) — spec line 310 has term:TERM, which is recursive and admits nested TRANSFORM and DEPRECATED_REF, not only bare NAMEs. - Literal bare oneOf drops type:'null' — Iceberg VALUE grammar (spec 254, 274) is a single scalar value; the typed form's PrimitiveTypeValue has no null branch either, so the null form was asymmetric and dead-weight. - FunctionReference NAME strings (bare-string form and identifier array items across both object forms) get minLength:1 — spec line 82 requires the function name to be a real identifier, not empty. --- open-api/rest-catalog-open-api.yaml | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/open-api/rest-catalog-open-api.yaml b/open-api/rest-catalog-open-api.yaml index dba245c2b815..681f827863db 100644 --- a/open-api/rest-catalog-open-api.yaml +++ b/open-api/rest-catalog-open-api.yaml @@ -2691,8 +2691,8 @@ components: - type: string - type: number - type: boolean - - type: 'null' - type: object + additionalProperties: false required: - type - value @@ -2715,6 +2715,7 @@ components: items: $ref: '#/components/schemas/Literal' - type: object + additionalProperties: false required: - type - values @@ -2766,6 +2767,7 @@ components: Represents the result of calling a function on zero or more value expressions or predicates. type: object + additionalProperties: false required: - type - function @@ -2791,10 +2793,12 @@ components: object with both a catalog and an identifier. oneOf: - type: string + minLength: 1 - type: array minItems: 1 items: type: string + minLength: 1 - type: object additionalProperties: false required: @@ -2805,7 +2809,9 @@ components: minItems: 1 items: type: string + minLength: 1 - type: object + additionalProperties: false required: - catalog - identifier @@ -2817,6 +2823,7 @@ components: minItems: 1 items: type: string + minLength: 1 UnaryExpression: deprecated: true @@ -2853,7 +2860,7 @@ components: term: $ref: '#/components/schemas/Term' value: - $ref: '#/components/schemas/PrimitiveTypeValue' + $ref: '#/components/schemas/Literal' SetExpression: deprecated: true @@ -2873,9 +2880,7 @@ components: term: $ref: '#/components/schemas/Term' values: - type: array - items: - $ref: '#/components/schemas/PrimitiveTypeValue' + $ref: '#/components/schemas/Literals' Term: oneOf: @@ -2919,7 +2924,7 @@ components: transform: $ref: '#/components/schemas/Transform' term: - $ref: '#/components/schemas/Reference' + $ref: '#/components/schemas/Term' Transform: type: string From 9b769535b2f42ff9a65305b7d4045d17f0a74e46 Mon Sep 17 00:00:00 2001 From: Prashant Singh Date: Thu, 9 Jul 2026 17:23:53 -0700 Subject: [PATCH 8/8] OpenAPI: Regenerate rest-catalog-open-api.py --- open-api/rest-catalog-open-api.py | 250 +++++++++++++++++------------- 1 file changed, 146 insertions(+), 104 deletions(-) diff --git a/open-api/rest-catalog-open-api.py b/open-api/rest-catalog-open-api.py index 41f4a3de5522..a1e08b5ad441 100644 --- a/open-api/rest-catalog-open-api.py +++ b/open-api/rest-catalog-open-api.py @@ -252,15 +252,31 @@ class FieldReference(RootModel[FieldReference1 | FieldReference2]): ) -class FunctionReference1(RootModel[list[str]]): - root: list[str] = Field( +class FunctionReference1(RootModel[str]): + root: str = Field( ..., description='A reference to a function per Iceberg Expressions spec, Appendix B. Four JSON forms: a bare name (single-part identifier), a list of names (multi-part identifier), an object with an identifier, or an object with both a catalog and an identifier.\n', min_length=1, ) -class FunctionReference2(BaseModel): +class FunctionReference2Item(RootModel[str]): + root: str = Field(..., min_length=1) + + +class FunctionReference2(RootModel[list[FunctionReference2Item]]): + root: list[FunctionReference2Item] = Field( + ..., + description='A reference to a function per Iceberg Expressions spec, Appendix B. Four JSON forms: a bare name (single-part identifier), a list of names (multi-part identifier), an object with an identifier, or an object with both a catalog and an identifier.\n', + min_length=1, + ) + + +class IdentifierItem(RootModel[str]): + root: str = Field(..., min_length=1) + + +class FunctionReference3(BaseModel): """ A reference to a function per Iceberg Expressions spec, Appendix B. Four JSON forms: a bare name (single-part identifier), a list of names (multi-part identifier), an object with an identifier, or an object with both a catalog and an identifier. @@ -269,23 +285,36 @@ class FunctionReference2(BaseModel): model_config = ConfigDict( extra='forbid', ) - identifier: list[str] = Field(..., min_length=1) + identifier: list[IdentifierItem] = Field(..., min_length=1) -class FunctionReference3(BaseModel): +class FunctionReference4(BaseModel): """ A reference to a function per Iceberg Expressions spec, Appendix B. Four JSON forms: a bare name (single-part identifier), a list of names (multi-part identifier), an object with an identifier, or an object with both a catalog and an identifier. """ + model_config = ConfigDict( + extra='forbid', + ) catalog: str - identifier: list[str] = Field(..., min_length=1) + identifier: list[IdentifierItem] = Field(..., min_length=1) class FunctionReference( - RootModel[str | FunctionReference1 | FunctionReference2 | FunctionReference3] + RootModel[ + FunctionReference1 + | FunctionReference2 + | FunctionReference3 + | FunctionReference4 + ] ): - root: str | FunctionReference1 | FunctionReference2 | FunctionReference3 = Field( + root: ( + FunctionReference1 + | FunctionReference2 + | FunctionReference3 + | FunctionReference4 + ) = Field( ..., description='A reference to a function per Iceberg Expressions spec, Appendix B. Four JSON forms: a bare name (single-part identifier), a list of names (multi-part identifier), an object with an identifier, or an object with both a catalog and an identifier.\n', ) @@ -1236,13 +1265,16 @@ class Literal1(BaseModel): """ + model_config = ConfigDict( + extra='forbid', + ) type: Literal['literal'] value: PrimitiveTypeValue data_type: PrimitiveType | None = Field(None, alias='data-type') -class LiteralModel(RootModel[str | float | bool | Literal1 | None]): - root: str | float | bool | Literal1 | None = Field( +class LiteralModel(RootModel[str | float | bool | Literal1]): + root: str | float | bool | Literal1 = Field( ..., description='A literal value expression per Iceberg Expressions spec, Appendix B. Three JSON forms are accepted: a bare scalar value, a typed literal object without a data-type, or a typed literal object with an explicit data-type.\n', ) @@ -1254,24 +1286,21 @@ class Literals1(BaseModel): """ + model_config = ConfigDict( + extra='forbid', + ) type: Literal['literals'] values: list[PrimitiveTypeValue] data_type: PrimitiveType = Field(..., alias='data-type') -class Literals(RootModel[list[LiteralModel | None] | Literals1]): - root: list[LiteralModel | None] | Literals1 = Field( +class Literals(RootModel[list[LiteralModel] | Literals1]): + root: list[LiteralModel] | Literals1 = Field( ..., description='A set of literal values per Iceberg Expressions spec, Appendix B. Two JSON forms: a bare array of literals, or a typed object with an explicit data-type applied to all values.\n', ) -class TransformTerm(BaseModel): - type: Literal['transform'] - transform: Transform - term: Reference - - class SetPartitionStatisticsUpdate(BaseUpdate): action: Literal['set-partition-statistics'] partition_statistics: PartitionStatisticsFile = Field( @@ -1378,10 +1407,6 @@ class FetchScanTasksRequest(BaseModel): plan_task: PlanTask = Field(..., alias='plan-task') -class Term(RootModel[Reference | TransformTerm | DeprecatedReference]): - root: Reference | TransformTerm | DeprecatedReference - - class SetStatisticsUpdate(BaseUpdate): action: Literal['set-statistics'] snapshot_id: int | None = Field( @@ -1417,13 +1442,39 @@ class FunctionDefinitionVersion(BaseModel): ) -class UnaryExpression(BaseModel): - """ - Deprecated. Use UnaryPredicate with 'child' (a ValueExpression) instead. Retained for backward compatibility with older clients per the Iceberg Expressions spec (Appendix B, Backward compatibility). +class StructField(BaseModel): + id: int + name: str + type: Type + required: bool + doc: str | None = None + initial_default: PrimitiveTypeValue | None = Field(None, alias='initial-default') + write_default: PrimitiveTypeValue | None = Field(None, alias='write-default') - """ - type: Literal['is-null', 'not-null', 'is-nan', 'not-nan'] = Field( +class StructType(BaseModel): + type: Literal['struct'] + fields: list[StructField] + + +class ListType(BaseModel): + type: Literal['list'] + element_id: int = Field(..., alias='element-id') + element: Type + element_required: bool = Field(..., alias='element-required') + + +class MapType(BaseModel): + type: Literal['map'] + key_id: int = Field(..., alias='key-id') + key: Type + value_id: int = Field(..., alias='value-id') + value: Type + value_required: bool = Field(..., alias='value-required') + + +class AndOrExpression(BaseModel): + type: Literal['and', 'or'] = Field( ..., examples=[ [ @@ -1449,18 +1500,12 @@ class UnaryExpression(BaseModel): ] ], ) - term: Term - - -class LiteralExpression(BaseModel): - """ - Deprecated. Use ComparisonPredicate with 'left' and 'right' (ValueExpressions) instead. Retained for backward compatibility with older clients per the Iceberg Expressions spec (Appendix B, Backward compatibility). + left: Expression + right: Expression - """ - type: Literal[ - 'lt', 'lt-eq', 'gt', 'gt-eq', 'eq', 'not-eq', 'starts-with', 'not-starts-with' - ] = Field( +class NotExpression(BaseModel): + type: Literal['not'] = Field( ..., examples=[ [ @@ -1486,17 +1531,16 @@ class LiteralExpression(BaseModel): ] ], ) - term: Term - value: PrimitiveTypeValue + child: Expression -class SetExpression(BaseModel): +class UnaryPredicate(BaseModel): """ - Deprecated. Use SetPredicate with 'child' (a ValueExpression) instead. Retained for backward compatibility with older clients per the Iceberg Expressions spec (Appendix B, Backward compatibility). + A predicate that tests a single value expression. Replaces the deprecated UnaryExpression that used 'term'. """ - type: Literal['in', 'not-in'] = Field( + type: Literal['is-null', 'not-null', 'is-nan', 'not-nan'] = Field( ..., examples=[ [ @@ -1522,43 +1566,18 @@ class SetExpression(BaseModel): ] ], ) - term: Term - values: list[PrimitiveTypeValue] - - -class StructField(BaseModel): - id: int - name: str - type: Type - required: bool - doc: str | None = None - initial_default: PrimitiveTypeValue | None = Field(None, alias='initial-default') - write_default: PrimitiveTypeValue | None = Field(None, alias='write-default') - - -class StructType(BaseModel): - type: Literal['struct'] - fields: list[StructField] - - -class ListType(BaseModel): - type: Literal['list'] - element_id: int = Field(..., alias='element-id') - element: Type - element_required: bool = Field(..., alias='element-required') + child: ValueExpression -class MapType(BaseModel): - type: Literal['map'] - key_id: int = Field(..., alias='key-id') - key: Type - value_id: int = Field(..., alias='value-id') - value: Type - value_required: bool = Field(..., alias='value-required') +class ComparisonPredicate(BaseModel): + """ + A predicate that compares two value expressions. Replaces the deprecated LiteralExpression that used 'term' and 'value'. + """ -class AndOrExpression(BaseModel): - type: Literal['and', 'or'] = Field( + type: Literal[ + 'lt', 'lt-eq', 'gt', 'gt-eq', 'eq', 'not-eq', 'starts-with', 'not-starts-with' + ] = Field( ..., examples=[ [ @@ -1584,12 +1603,17 @@ class AndOrExpression(BaseModel): ] ], ) - left: Expression - right: Expression + left: ValueExpression + right: ValueExpression -class NotExpression(BaseModel): - type: Literal['not'] = Field( +class SetPredicate(BaseModel): + """ + A predicate that tests whether a value expression is in a set of literals. Replaces the deprecated SetExpression that used 'term'. + + """ + + type: Literal['in', 'not-in'] = Field( ..., examples=[ [ @@ -1615,12 +1639,27 @@ class NotExpression(BaseModel): ] ], ) - child: Expression + child: ValueExpression + values: Literals -class UnaryPredicate(BaseModel): +class Apply(BaseModel): """ - A predicate that tests a single value expression. Replaces the deprecated UnaryExpression that used 'term'. + A function application per Iceberg Expressions spec, Appendix B. Represents the result of calling a function on zero or more value expressions or predicates. + + """ + + model_config = ConfigDict( + extra='forbid', + ) + type: Literal['apply'] + function: FunctionReference + arguments: list[ValueExpression | Expression] + + +class UnaryExpression(BaseModel): + """ + Deprecated. Use UnaryPredicate with 'child' (a ValueExpression) instead. Retained for backward compatibility with older clients per the Iceberg Expressions spec (Appendix B, Backward compatibility). """ @@ -1650,12 +1689,12 @@ class UnaryPredicate(BaseModel): ] ], ) - child: ValueExpression + term: Term -class ComparisonPredicate(BaseModel): +class LiteralExpression(BaseModel): """ - A predicate that compares two value expressions. Replaces the deprecated LiteralExpression that used 'term' and 'value'. + Deprecated. Use ComparisonPredicate with 'left' and 'right' (ValueExpressions) instead. Retained for backward compatibility with older clients per the Iceberg Expressions spec (Appendix B, Backward compatibility). """ @@ -1687,13 +1726,13 @@ class ComparisonPredicate(BaseModel): ] ], ) - left: ValueExpression - right: ValueExpression + term: Term + value: LiteralModel -class SetPredicate(BaseModel): +class SetExpression(BaseModel): """ - A predicate that tests whether a value expression is in a set of literals. Replaces the deprecated SetExpression that used 'term'. + Deprecated. Use SetPredicate with 'child' (a ValueExpression) instead. Retained for backward compatibility with older clients per the Iceberg Expressions spec (Appendix B, Backward compatibility). """ @@ -1723,19 +1762,14 @@ class SetPredicate(BaseModel): ] ], ) - child: ValueExpression + term: Term values: Literals -class Apply(BaseModel): - """ - A function application per Iceberg Expressions spec, Appendix B. Represents the result of calling a function on zero or more value expressions or predicates. - - """ - - type: Literal['apply'] - function: FunctionReference - arguments: list[ValueExpression | Expression] +class TransformTerm(BaseModel): + type: Literal['transform'] + transform: Transform + term: Term class TableMetadata(BaseModel): @@ -2172,6 +2206,13 @@ class Type(RootModel[PrimitiveType | StructType | ListType | MapType]): root: PrimitiveType | StructType | ListType | MapType +class ValueExpression(RootModel[LiteralModel | FieldReference | Apply]): + root: LiteralModel | FieldReference | Apply = Field( + ..., + description='A value expression per Iceberg Expressions spec, Appendix B: a literal, a field reference, or a function application. The result type is determined when the expression is bound to an input schema.\n', + ) + + class Expression( RootModel[ bool @@ -2202,11 +2243,8 @@ class Expression( ) -class ValueExpression(RootModel[LiteralModel | FieldReference | Apply | None]): - root: LiteralModel | FieldReference | Apply | None = Field( - ..., - description='A value expression per Iceberg Expressions spec, Appendix B: a literal, a field reference, or a function application. The result type is determined when the expression is bound to an input schema.\n', - ) +class Term(RootModel[Reference | TransformTerm | DeprecatedReference]): + root: Reference | TransformTerm | DeprecatedReference class TableUpdate( @@ -2371,6 +2409,10 @@ class PlanTableScanResult( ComparisonPredicate.model_rebuild() SetPredicate.model_rebuild() Apply.model_rebuild() +UnaryExpression.model_rebuild() +LiteralExpression.model_rebuild() +SetExpression.model_rebuild() +TransformTerm.model_rebuild() TableMetadata.model_rebuild() ViewMetadata.model_rebuild() AddSchemaUpdate.model_rebuild()