You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iceberg.apache.org by si...@apache.org on 2026/07/27 18:52:13 UTC

(iceberg) branch main updated: OpenAPI: Update REST spec expressions to match new expressions spec (#17138)

This is an automated email from the ASF dual-hosted git repository.

singhpk234 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg.git


The following commit(s) were added to refs/heads/main by this push:
     new 832941eae9 OpenAPI: Update REST spec expressions to match new expressions spec (#17138)
832941eae9 is described below

commit 832941eae9e9e25269dcc84283b0e11e28bf188b
Author: Prashant Singh <35...@users.noreply.github.com>
AuthorDate: Mon Jul 27 11:52:08 2026 -0700

    OpenAPI: Update REST spec expressions to match new expressions spec (#17138)
    
    * 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.
    
    * 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.
    
    * 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.
    
    * OpenAPI: Regenerate rest-catalog-open-api.py
    
    * OpenAPI: Tighten ValueExpression schemas for Appendix B correctness
    
    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."
    
    * OpenAPI: Regenerate rest-catalog-open-api.py
    
    * OpenAPI: Further tighten ValueExpression schemas per adversarial review
    
    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.
    
    * OpenAPI: Regenerate rest-catalog-open-api.py
    
    * OpenAPI: Address reviewer feedback on ValueExpression schemas
    
    Adopt Nevin's review comments in narrower form:
    - Strip "Replaces the deprecated X" clauses from UnaryPredicate,
      ComparisonPredicate, and SetPredicate descriptions
    - Rewrite SetPredicate description in operand-neutral form:
      "A predicate that operates on a list of literals"
    - Rename Literals from "set of literal values" to "list of literal
      values" to match wire form (arrays are ordered, admit duplicates)
    - Dedupe "Iceberg Expressions spec, Appendix B" citations across
      Literal, Literals, FieldReference, Apply, and FunctionReference;
      keep the citation on ValueExpression as the parent anchor
    - Split FieldReference into named FieldReferenceById and
      FieldReferenceByName joined via oneOf, preserving the
      bound-vs-unbound context clause in the parent description
    
    * OpenAPI: Regenerate rest-catalog-open-api.py
    
    * OpenAPI: Tighten predicate/literal schemas per second-round review
    
    - Add additionalProperties: false to UnaryPredicate, ComparisonPredicate,
      and SetPredicate. Without this, a payload carrying both a new field
      (e.g. child) and a legacy field (e.g. term) matches both the new
      predicate and its deprecated counterpart under Expression.oneOf,
      which is a validation error. Matches the pattern already applied to
      Literal, FieldReference*, and Apply.
    - Replace the bare-value Literal branch (string | number | boolean) with
      a $ref to PrimitiveTypeValue. Per the expressions spec, VALUE is a
      single value JSON drawn from the table spec's primitive types, which
      includes dates, decimals, UUIDs, and other types the previous three-
      primitive union excluded.
    - Rewrite Literal, Literals, and FunctionReference descriptions using
      the "X is either A, B, C" convention for consistency across the
      expression schemas.
    
    * OpenAPI: Regenerate rest-catalog-open-api.py
    
    * OpenAPI: Consolidate predicate schemas per Ryan's feedback
    
    Merge deprecated UnaryExpression, LiteralExpression, and SetExpression
    into UnaryPredicate, ComparisonPredicate, and SetPredicate by adding
    optional deprecated 'term'/'value' fields. Rename Expression to
    Predicate, extract FunctionArgument and FunctionIdentifier types,
    rename FieldReferenceById/ByName to IdReference/NamedReference, and
    remove DeprecatedReference (never produced by implementations).
    
    * OpenAPI: Complete naming pass per Ryan's feedback
    
    Rename AndOrExpression/NotExpression to AndOrPredicate/NotPredicate
    for consistency with the Predicate top-level schema. Rename
    FieldReference to Reference (the new structured oneOf) and rename
    the old bare-string Reference to TermReference (deprecated), matching
    Ryan's stated preference for the prestigious name on the new type.
    
    * OpenAPI: Inline function reference and reuse CatalogObjectIdentifier
    
    Delete the FunctionReference and FunctionIdentifier wrapper schemas
    (each was used only once) and inline the function oneOf directly
    into Apply.function. Reuse the existing CatalogObjectIdentifier for
    identifier arrays instead of redefining them inline, per Ryan's
    narrow ask on the two identifier fields.
    
    * OpenAPI: Drop Appendix B references from deprecation notices
    
    Apply Ryan's principle ("this is its own spec definition") to the
    TrueExpression and FalseExpression deprecation descriptions, removing
    the Appendix B / JSON serialization references.
    
    * OpenAPI: Restrict TransformTerm.term to TermReference
    
    Per Ryan's feedback, TransformTerm.term should be a direct name
    reference rather than a recursive Term union, so a transform can no
    longer wrap another transform.
    
    * OpenAPI: Mark legacy term types as deprecated
    
    Per Ryan's feedback, deprecate the legacy term types consistently so
    they are not reused: Term and TransformTerm now carry deprecated: true
    alongside TermReference.
    
    * OpenAPI: Allow Apply.function identifier object without catalog
    
    The expressions spec permits a FUNC_REF of the form { "identifier":
    [ NAME* ] } where catalog is missing/null. Make catalog optional on
    the object variant of Apply.function so this form is representable.
    
    ---------
    
    Co-authored-by: Prashant Singh <pr...@snowflake.com>
---
 open-api/rest-catalog-open-api.py   | 322 ++++++++++++++++++++++++++++--------
 open-api/rest-catalog-open-api.yaml | 222 ++++++++++++++++++++++---
 2 files changed, 444 insertions(+), 100 deletions(-)

diff --git a/open-api/rest-catalog-open-api.py b/open-api/rest-catalog-open-api.py
index cb0ec1dcb5..de95134766 100644
--- a/open-api/rest-catalog-open-api.py
+++ b/open-api/rest-catalog-open-api.py
@@ -152,6 +152,11 @@ class ExpressionType(RootModel[str]):
 
 
 class TrueExpression(BaseModel):
+    """
+    Deprecated. Use the bare boolean literal `true` as a predicate instead.
+
+    """
+
     type: Literal['true'] = Field(
         ...,
         examples=[
@@ -181,6 +186,11 @@ class TrueExpression(BaseModel):
 
 
 class FalseExpression(BaseModel):
+    """
+    Deprecated. Use the bare boolean literal `false` as a predicate instead.
+
+    """
+
     type: Literal['false'] = Field(
         ...,
         examples=[
@@ -209,8 +219,49 @@ class FalseExpression(BaseModel):
     )
 
 
-class Reference(RootModel[str]):
-    root: str = Field(..., examples=[['column-name']])
+class IdReference(BaseModel):
+    """
+    A bound reference to a field by field ID.
+    """
+
+    model_config = ConfigDict(
+        extra='forbid',
+    )
+    type: Literal['reference']
+    id: int
+
+
+class NamedReference(BaseModel):
+    """
+    An unbound reference to a field by name.
+    """
+
+    model_config = ConfigDict(
+        extra='forbid',
+    )
+    type: Literal['reference']
+    name: str
+
+
+class Function(RootModel[str]):
+    root: str = Field(..., min_length=1)
+
+
+class Function1(BaseModel):
+    model_config = ConfigDict(
+        extra='forbid',
+    )
+    catalog: str | None = None
+    identifier: CatalogObjectIdentifier
+
+
+class TermReference(RootModel[str]):
+    root: str = Field(
+        ...,
+        deprecated=True,
+        description='Deprecated string-form field reference used in older REST predicates. Use Reference (IdReference or NamedReference) instead.\n',
+        examples=[['column-name']],
+    )
 
 
 class Transform(RootModel[str]):
@@ -1134,10 +1185,64 @@ class RenameTableRequest(BaseModel):
     destination: TableIdentifier
 
 
+class Literal1(BaseModel):
+    """
+    A literal is either a bare value, an untyped literal object, or a typed literal object.
+
+    """
+
+    model_config = ConfigDict(
+        extra='forbid',
+    )
+    type: Literal['literal']
+    value: PrimitiveTypeValue
+    data_type: PrimitiveType | None = Field(None, alias='data-type')
+
+
+class LiteralModel(RootModel[PrimitiveTypeValue | Literal1]):
+    root: PrimitiveTypeValue | Literal1 = Field(
+        ...,
+        description='A literal is either a bare value, an untyped literal object, or a typed literal object.\n',
+    )
+
+
+class Literals1(BaseModel):
+    """
+    Literals is either a bare array of literals or a typed object with an explicit data-type applied to all values.
+
+    """
+
+    model_config = ConfigDict(
+        extra='forbid',
+    )
+    type: Literal['literals']
+    values: list[PrimitiveTypeValue]
+    data_type: PrimitiveType = Field(..., alias='data-type')
+
+
+class Literals(RootModel[list[LiteralModel] | Literals1]):
+    root: list[LiteralModel] | Literals1 = Field(
+        ...,
+        description='Literals is either a bare array of literals or a typed object with an explicit data-type applied to all values.\n',
+    )
+
+
+class Reference(RootModel[IdReference | NamedReference]):
+    root: IdReference | NamedReference = Field(
+        ...,
+        description='A reference to a field. 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',
+    )
+
+
 class TransformTerm(BaseModel):
+    """
+    Deprecated. Legacy transform-applied-to-a-term form. Use Apply with a Reference argument instead.
+
+    """
+
     type: Literal['transform']
     transform: Transform
-    term: Reference
+    term: TermReference
 
 
 class SetPartitionStatisticsUpdate(BaseUpdate):
@@ -1246,8 +1351,12 @@ class FetchScanTasksRequest(BaseModel):
     plan_task: PlanTask = Field(..., alias='plan-task')
 
 
-class Term(RootModel[Reference | TransformTerm]):
-    root: Reference | TransformTerm
+class Term(RootModel[TermReference | TransformTerm]):
+    root: TermReference | TransformTerm = Field(
+        ...,
+        deprecated=True,
+        description='Deprecated. Legacy term form used by older REST predicates. Use Reference or Apply instead.\n',
+    )
 
 
 class SetStatisticsUpdate(BaseUpdate):
@@ -1285,8 +1394,39 @@ class FunctionDefinitionVersion(BaseModel):
     )
 
 
-class UnaryExpression(BaseModel):
-    type: Literal['is-null', 'not-null', 'is-nan', 'not-nan'] = Field(
+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')
+
+
+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 AndOrPredicate(BaseModel):
+    type: Literal['and', 'or'] = Field(
         ...,
         examples=[
             [
@@ -1312,13 +1452,12 @@ class UnaryExpression(BaseModel):
             ]
         ],
     )
-    term: Term
+    left: Predicate
+    right: Predicate
 
 
-class LiteralExpression(BaseModel):
-    type: Literal[
-        'lt', 'lt-eq', 'gt', 'gt-eq', 'eq', 'not-eq', 'starts-with', 'not-starts-with'
-    ] = Field(
+class NotPredicate(BaseModel):
+    type: Literal['not'] = Field(
         ...,
         examples=[
             [
@@ -1344,12 +1483,19 @@ class LiteralExpression(BaseModel):
             ]
         ],
     )
-    term: Term
-    value: PrimitiveTypeValue
+    child: Predicate
 
 
-class SetExpression(BaseModel):
-    type: Literal['in', 'not-in'] = Field(
+class UnaryPredicate(BaseModel):
+    """
+    A predicate that tests a single value expression. Accepts either 'child' (preferred) or 'term' (deprecated) to identify the operand.
+
+    """
+
+    model_config = ConfigDict(
+        extra='forbid',
+    )
+    type: Literal['is-null', 'not-null', 'is-nan', 'not-nan'] = Field(
         ...,
         examples=[
             [
@@ -1375,43 +1521,24 @@ 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 | None = None
+    term: Term | None = Field(
+        None, deprecated=True, description="Deprecated. Use 'child' instead."
+    )
 
 
-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. Accepts either 'left'/'right' (preferred) or 'term'/'value' (deprecated).
 
+    """
 
-class AndOrExpression(BaseModel):
-    type: Literal['and', 'or'] = Field(
+    model_config = ConfigDict(
+        extra='forbid',
+    )
+    type: Literal[
+        'lt', 'lt-eq', 'gt', 'gt-eq', 'eq', 'not-eq', 'starts-with', 'not-starts-with'
+    ] = Field(
         ...,
         examples=[
             [
@@ -1437,12 +1564,26 @@ class AndOrExpression(BaseModel):
             ]
         ],
     )
-    left: Expression
-    right: Expression
+    left: ValueExpression | None = None
+    right: ValueExpression | None = None
+    term: Term | None = Field(
+        None, deprecated=True, description="Deprecated. Use 'left' instead."
+    )
+    value: LiteralModel | None = Field(
+        None, deprecated=True, description="Deprecated. Use 'right' instead."
+    )
 
 
-class NotExpression(BaseModel):
-    type: Literal['not'] = Field(
+class SetPredicate(BaseModel):
+    """
+    A predicate that tests whether a value is in a set of literals. Accepts either 'child' (preferred) or 'term' (deprecated) to identify the operand.
+
+    """
+
+    model_config = ConfigDict(
+        extra='forbid',
+    )
+    type: Literal['in', 'not-in'] = Field(
         ...,
         examples=[
             [
@@ -1468,7 +1609,25 @@ class NotExpression(BaseModel):
             ]
         ],
     )
-    child: Expression
+    child: ValueExpression | None = None
+    term: Term | None = Field(
+        None, deprecated=True, description="Deprecated. Use 'child' instead."
+    )
+    values: Literals
+
+
+class Apply(BaseModel):
+    """
+    A function application on zero or more value expressions or predicates.
+
+    """
+
+    model_config = ConfigDict(
+        extra='forbid',
+    )
+    type: Literal['apply']
+    function: Function | CatalogObjectIdentifier | Function1
+    arguments: list[FunctionArgument]
 
 
 class TableMetadata(BaseModel):
@@ -1695,7 +1854,7 @@ class LoadViewResult(BaseModel):
 class ScanReport(BaseModel):
     table_name: str = Field(..., alias='table-name')
     snapshot_id: int = Field(..., alias='snapshot-id')
-    filter: Expression
+    filter: Predicate
     schema_id: int = Field(..., alias='schema-id')
     projected_field_ids: list[int] = Field(..., alias='projected-field-ids')
     projected_field_names: list[str] = Field(..., alias='projected-field-names')
@@ -1847,8 +2006,8 @@ class PlanTableScanRequest(BaseModel):
     select: list[FieldName] | None = Field(
         None, description='List of selected schema fields'
     )
-    filter: Expression | None = Field(
-        None, description='Expression used to filter the table data'
+    filter: Predicate | None = Field(
+        None, description='Predicate used to filter the table data'
     )
     min_rows_requested: int | None = Field(
         None,
@@ -1889,7 +2048,7 @@ class FileScanTask(BaseModel):
         alias='delete-file-references',
         description='A list of indices in the delete files array (0-based)',
     )
-    residual_filter: Expression | None = Field(
+    residual_filter: Predicate | None = Field(
         None,
         alias='residual-filter',
         description='An optional filter to be applied to rows in this file scan task.\nIf the residual is not present, the client must produce the residual or use the original filter.',
@@ -1905,25 +2064,34 @@ class Type(RootModel[PrimitiveType | StructType | ListType | MapType]):
     root: PrimitiveType | StructType | ListType | MapType
 
 
-class Expression(
+class Predicate(
     RootModel[
-        TrueExpression
+        bool
+        | TrueExpression
         | FalseExpression
-        | AndOrExpression
-        | NotExpression
-        | SetExpression
-        | LiteralExpression
-        | UnaryExpression
+        | AndOrPredicate
+        | NotPredicate
+        | UnaryPredicate
+        | ComparisonPredicate
+        | SetPredicate
     ]
 ):
     root: (
-        TrueExpression
+        bool
+        | TrueExpression
         | FalseExpression
-        | AndOrExpression
-        | NotExpression
-        | SetExpression
-        | LiteralExpression
-        | UnaryExpression
+        | AndOrPredicate
+        | NotPredicate
+        | UnaryPredicate
+        | ComparisonPredicate
+        | SetPredicate
+    )
+
+
+class ValueExpression(RootModel[LiteralModel | Reference | Apply]):
+    root: LiteralModel | Reference | Apply = Field(
+        ...,
+        description='A value expression: a literal, a field reference, or a function application.\n',
     )
 
 
@@ -2039,6 +2207,10 @@ class FunctionDataType(
     )
 
 
+class FunctionArgument(RootModel[ValueExpression | Predicate]):
+    root: ValueExpression | Predicate
+
+
 class CompletedPlanningWithIDResult(CompletedPlanningResult):
     plan_id: str = Field(
         ..., alias='plan-id', description='ID used to track a planning request'
@@ -2083,8 +2255,12 @@ class PlanTableScanResult(
 StructField.model_rebuild()
 ListType.model_rebuild()
 MapType.model_rebuild()
-AndOrExpression.model_rebuild()
-NotExpression.model_rebuild()
+AndOrPredicate.model_rebuild()
+NotPredicate.model_rebuild()
+UnaryPredicate.model_rebuild()
+ComparisonPredicate.model_rebuild()
+SetPredicate.model_rebuild()
+Apply.model_rebuild()
 TableMetadata.model_rebuild()
 ViewMetadata.model_rebuild()
 AddSchemaUpdate.model_rebuild()
diff --git a/open-api/rest-catalog-open-api.yaml b/open-api/rest-catalog-open-api.yaml
index 997e08f4cc..dfa97292d0 100644
--- a/open-api/rest-catalog-open-api.yaml
+++ b/open-api/rest-catalog-open-api.yaml
@@ -2528,15 +2528,16 @@ components:
               items:
                 type: integer
 
-    Expression:
+    Predicate:
       oneOf:
+        - type: boolean
         - $ref: '#/components/schemas/TrueExpression'
         - $ref: '#/components/schemas/FalseExpression'
-        - $ref: '#/components/schemas/AndOrExpression'
-        - $ref: '#/components/schemas/NotExpression'
-        - $ref: '#/components/schemas/SetExpression'
-        - $ref: '#/components/schemas/LiteralExpression'
-        - $ref: '#/components/schemas/UnaryExpression'
+        - $ref: '#/components/schemas/AndOrPredicate'
+        - $ref: '#/components/schemas/NotPredicate'
+        - $ref: '#/components/schemas/UnaryPredicate'
+        - $ref: '#/components/schemas/ComparisonPredicate'
+        - $ref: '#/components/schemas/SetPredicate'
 
     ExpressionType:
       type: string
@@ -2561,7 +2562,18 @@ components:
         - "is-nan"
         - "not-nan"
 
+    ValueExpression:
+      description: >
+        A value expression: a literal, a field reference, or a function application.
+      oneOf:
+        - $ref: '#/components/schemas/Literal'
+        - $ref: '#/components/schemas/Reference'
+        - $ref: '#/components/schemas/Apply'
+
     TrueExpression:
+      deprecated: true
+      description: >
+        Deprecated. Use the bare boolean literal `true` as a predicate instead.
       type: object
       required:
         - type
@@ -2571,6 +2583,9 @@ components:
           const: "true"
 
     FalseExpression:
+      deprecated: true
+      description: >
+        Deprecated. Use the bare boolean literal `false` as a predicate instead.
       type: object
       required:
         - type
@@ -2579,7 +2594,7 @@ components:
           $ref: '#/components/schemas/ExpressionType'
           const: "false"
 
-    AndOrExpression:
+    AndOrPredicate:
       type: object
       required:
         - type
@@ -2590,11 +2605,11 @@ components:
           $ref: '#/components/schemas/ExpressionType'
           enum: ["and", "or"]
         left:
-          $ref: '#/components/schemas/Expression'
+          $ref: '#/components/schemas/Predicate'
         right:
-          $ref: '#/components/schemas/Expression'
+          $ref: '#/components/schemas/Predicate'
 
-    NotExpression:
+    NotPredicate:
       type: object
       required:
         - type
@@ -2604,63 +2619,216 @@ components:
           $ref: '#/components/schemas/ExpressionType'
           const: "not"
         child:
-          $ref: '#/components/schemas/Expression'
+          $ref: '#/components/schemas/Predicate'
 
-    UnaryExpression:
+    UnaryPredicate:
+      description: >
+        A predicate that tests a single value expression. Accepts either
+        'child' (preferred) or 'term' (deprecated) to identify the operand.
       type: object
+      additionalProperties: false
       required:
         - type
-        - term
       properties:
         type:
           $ref: '#/components/schemas/ExpressionType'
           enum: ["is-null", "not-null", "is-nan", "not-nan"]
+        child:
+          $ref: '#/components/schemas/ValueExpression'
         term:
+          deprecated: true
+          description: "Deprecated. Use 'child' instead."
           $ref: '#/components/schemas/Term'
 
-    LiteralExpression:
+    ComparisonPredicate:
+      description: >
+        A predicate that compares two value expressions. Accepts either
+        'left'/'right' (preferred) or 'term'/'value' (deprecated).
       type: object
+      additionalProperties: false
       required:
         - type
-        - term
-        - value
       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'
         term:
+          deprecated: true
+          description: "Deprecated. Use 'left' instead."
           $ref: '#/components/schemas/Term'
         value:
-          $ref: '#/components/schemas/PrimitiveTypeValue'
+          deprecated: true
+          description: "Deprecated. Use 'right' instead."
+          $ref: '#/components/schemas/Literal'
 
-    SetExpression:
+    SetPredicate:
+      description: >
+        A predicate that tests whether a value is in a set of literals. Accepts
+        either 'child' (preferred) or 'term' (deprecated) to identify the operand.
       type: object
+      additionalProperties: false
       required:
         - type
-        - term
         - values
       properties:
         type:
           $ref: '#/components/schemas/ExpressionType'
           enum: ["in", "not-in"]
+        child:
+          $ref: '#/components/schemas/ValueExpression'
         term:
+          deprecated: true
+          description: "Deprecated. Use 'child' instead."
           $ref: '#/components/schemas/Term'
         values:
+          $ref: '#/components/schemas/Literals'
+
+    Literal:
+      description: >
+        A literal is either a bare value, an untyped literal object, or a
+        typed literal object.
+      oneOf:
+        - $ref: '#/components/schemas/PrimitiveTypeValue'
+        - type: object
+          additionalProperties: false
+          required:
+            - type
+            - value
+          properties:
+            type:
+              type: string
+              const: "literal"
+            value:
+              $ref: '#/components/schemas/PrimitiveTypeValue'
+            data-type:
+              $ref: '#/components/schemas/PrimitiveType'
+
+    Literals:
+      description: >
+        Literals is either 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
+          additionalProperties: false
+          required:
+            - type
+            - values
+            - data-type
+          properties:
+            type:
+              type: string
+              const: "literals"
+            values:
+              type: array
+              items:
+                $ref: '#/components/schemas/PrimitiveTypeValue'
+            data-type:
+              $ref: '#/components/schemas/PrimitiveType'
+
+    Reference:
+      description: >
+        A reference to a field. 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:
+        - $ref: '#/components/schemas/IdReference'
+        - $ref: '#/components/schemas/NamedReference'
+
+    IdReference:
+      description: A bound reference to a field by field ID.
+      type: object
+      additionalProperties: false
+      required:
+        - type
+        - id
+      properties:
+        type:
+          type: string
+          const: "reference"
+        id:
+          type: integer
+
+    NamedReference:
+      description: An unbound reference to a field by name.
+      type: object
+      additionalProperties: false
+      required:
+        - type
+        - name
+      properties:
+        type:
+          type: string
+          const: "reference"
+        name:
+          type: string
+
+    Apply:
+      description: >
+        A function application on zero or more value expressions or predicates.
+      type: object
+      additionalProperties: false
+      required:
+        - type
+        - function
+        - arguments
+      properties:
+        type:
+          type: string
+          const: "apply"
+        function:
+          oneOf:
+            - type: string
+              minLength: 1
+            - $ref: '#/components/schemas/CatalogObjectIdentifier'
+            - type: object
+              additionalProperties: false
+              required:
+                - identifier
+              properties:
+                catalog:
+                  type: string
+                identifier:
+                  $ref: '#/components/schemas/CatalogObjectIdentifier'
+        arguments:
           type: array
           items:
-            $ref: '#/components/schemas/PrimitiveTypeValue'
+            $ref: '#/components/schemas/FunctionArgument'
+
+    FunctionArgument:
+      anyOf:
+        - $ref: '#/components/schemas/ValueExpression'
+        - $ref: '#/components/schemas/Predicate'
 
     Term:
+      deprecated: true
+      description: >
+        Deprecated. Legacy term form used by older REST predicates.
+        Use Reference or Apply instead.
       oneOf:
-        - $ref: '#/components/schemas/Reference'
+        - $ref: '#/components/schemas/TermReference'
         - $ref: '#/components/schemas/TransformTerm'
 
-    Reference:
+    TermReference:
+      deprecated: true
+      description: >
+        Deprecated string-form field reference used in older REST predicates.
+        Use Reference (IdReference or NamedReference) instead.
       type: string
       example:
         - "column-name"
 
     TransformTerm:
+      deprecated: true
+      description: >
+        Deprecated. Legacy transform-applied-to-a-term form.
+        Use Apply with a Reference argument instead.
       type: object
       required:
         - type
@@ -2673,7 +2841,7 @@ components:
         transform:
           $ref: '#/components/schemas/Transform'
         term:
-          $ref: '#/components/schemas/Reference'
+          $ref: '#/components/schemas/TermReference'
 
     Transform:
       type: string
@@ -4251,7 +4419,7 @@ components:
           type: integer
           format: int64
         filter:
-          $ref: '#/components/schemas/Expression'
+          $ref: '#/components/schemas/Predicate'
         schema-id:
           type: integer
         projected-field-ids:
@@ -5134,8 +5302,8 @@ components:
             $ref: '#/components/schemas/FieldName'
         filter:
           description:
-            Expression used to filter the table data
-          $ref: '#/components/schemas/Expression'
+            Predicate used to filter the table data
+          $ref: '#/components/schemas/Predicate'
         min-rows-requested:
           description:
             The minimum number of rows requested for the scan. This is used as a hint
@@ -5225,7 +5393,7 @@ components:
             If the residual is not present, the client must produce the
             residual or use the original filter.
           allOf:
-            - $ref: '#/components/schemas/Expression'
+            - $ref: '#/components/schemas/Predicate'
 
     MultiValuedMap:
       description: A map of string keys where each key can map to multiple string values.