laurentgo commented on code in PR #13879:
URL: https://github.com/apache/iceberg/pull/13879#discussion_r3533840249


##########
open-api/rest-catalog-open-api.yaml:
##########
@@ -3480,6 +3480,309 @@ components:
           additionalProperties:
             type: string
 
+    ReadRestrictions:
+      type: object
+      description: >
+          Read restrictions for a table, including column projections and row 
filter expressions.
+
+          A client MUST enforce the restrictions defined in this object when 
reading data
+          from the table.
+
+          These restrictions apply only to the authenticated principal, user, 
or account
+          associated with the request. They MUST NOT be interpreted as global 
policy and
+          MUST NOT be applied beyond the entity identified by the 
Authentication header
+          (or other applicable authentication mechanism).
+
+          If both properties are absent or empty, the ReadRestrictions object 
imposes no
+          restrictions and is equivalent to the field being absent from the 
response.
+          A server MUST NOT return an action for a column whose type is not 
listed in
+          that action's "Applicable to" set.
+          For all actions, if the input column value is NULL, the output MUST 
be NULL.
+
+          If a column projection targets a struct-typed field, other column 
projections
+          in the same ReadRestrictions MUST NOT target any of that struct's 
subfields
+          (at any depth). This avoids ambiguity about which action governs a 
given
+          leaf value.
+      properties:
+        required-column-projections:
+          description: >
+            A list of columns that require specific actions to be applied when 
reading.
+
+            If this property is absent, a reader MAY access all columns of the 
table as-is
+            without any mandatory transformations.
+
+            If this property is present, each listed column MUST have its 
specified
+            action applied. Columns not listed in required-column-projections
+            are not subject to any read restrictions.
+
+            When this list is present:
+
+            1. For each column listed in required-column-projections, the 
reader MUST apply
+              the specified action before returning values for that column.
+
+            2. The reader MUST replace all output references to the column 
with the result
+              of the action, presenting the result under the original column 
name. For
+              example, if the action for column cc is mask-alphanum, the 
reader MUST
+              return the masked value as cc in the query output.
+
+            3. Columns not listed in required-column-projections MAY be 
projected normally
+              by the reader without any mandatory transformations.
+
+            4. A column MUST appear at most once in 
required-column-projections.
+
+            5. If a projected column's action cannot be evaluated by the reader
+              (including unrecognized action types), the reader MUST fail 
rather than
+              ignore or skip the action.
+
+            6. Each action defines the output type for its column. For all 
predefined
+              actions except apply-expression, the output type matches the 
input column
+              type. For apply-expression, the output type is determined by the 
expression.
+
+          type: array
+          items:
+            $ref: '#/components/schemas/Action'
+        required-row-filter:
+          description: >
+            An expression that filters rows in the table that the 
authenticated principal does not have access to.
+
+            1. The expression MUST evaluate to a boolean. A reader MUST 
discard any row for which
+              the filter evaluates to FALSE, and no information derived from 
discarded rows
+              MAY be included in the query result.
+
+            2. Row filters MUST be evaluated against the original, 
untransformed column values.
+              Required projections MUST be applied only after row filters are 
applied.
+
+            3. If a client cannot interpret or evaluate a provided filter 
expression, it MUST fail.
+
+            4. If this property is absent, null, or always true then no 
mandatory filtering is required.
+          $ref: '#/components/schemas/Expression'
+
+    Action:
+      discriminator:
+        propertyName: action
+        mapping:
+          mask-alphanum: '#/components/schemas/MaskAlphanum'
+          mask-to-fixed-value: '#/components/schemas/MaskToFixedValue'
+          replace-with-null: '#/components/schemas/ReplaceWithNull'
+          show-first-4: '#/components/schemas/ShowFirst4'
+          show-last-4: '#/components/schemas/ShowLast4'
+          truncate-to-year: '#/components/schemas/TruncateToYear'
+          truncate-to-month: '#/components/schemas/TruncateToMonth'
+          sha-256-global: '#/components/schemas/Sha256Global'
+          sha-256-query-local: '#/components/schemas/Sha256QueryLocal'
+          apply-expression: '#/components/schemas/ApplyExpression'
+      type: object
+      required:
+        - action
+        - field-id
+      properties:
+        action:
+          type: string
+        field-id:
+          type: integer
+          description: field id of the column being projected.
+
+    MaskAlphanum:
+      description: >
+        Redacts the column value Unicode code point by code point using the 
following rules:
+
+        - Digits (U+0030–U+0039, 0-9) are replaced with 'n'
+        - The following punctuation characters are kept as-is:
+            U+0028 '('  LEFT PARENTHESIS
+            U+0029 ')'  RIGHT PARENTHESIS
+            U+002C ','  COMMA
+            U+002E '.'  FULL STOP
+            U+002D '-'  HYPHEN-MINUS
+            U+0040 '@'  COMMERCIAL AT
+        - All other Unicode characters (including letters, whitespace, and any 
punctuation
+          not listed above) are replaced with 'x'
+
+        For example: "[email protected]" → "[email protected]"
+
+        Applicable to: string
+      allOf:
+        - $ref: '#/components/schemas/Action'
+      properties:
+        action:
+          type: string
+          const: "mask-alphanum"
+
+    MaskToFixedValue:
+      description: >
+        Replaces the column value with a predefined type-specific fixed value.
+        Engines MUST use exactly the values listed below to ensure consistency
+        across implementations.
+
+        Fixed values by type:
+        - boolean: false
+        - int: 0
+        - long: 0
+        - float: 0.0
+        - double: 0.0
+        - decimal(p, s): 0 (zero with s digits after the decimal point, e.g. 
0.00 for decimal(p,2))
+        - string: "XXXXXXXX"

Review Comment:
   I don't care as well, discussing it more is just bikeshedding at this point.



##########
open-api/rest-catalog-open-api.yaml:
##########
@@ -3632,6 +3632,343 @@ components:
           additionalProperties:
             type: string
 
+    ReadRestrictions:
+      type: object
+      description: >
+          Read restrictions for a table, including column projections and row 
filter expressions.
+
+          A client SHOULD support the read-restrictions field. If a client 
supports
+          read-restrictions, it MUST fail if it cannot apply any returned 
restriction
+          (including unrecognized action or expression types). Read 
restrictions returned
+          in a loadTable response apply to every read operation on the loaded 
table
+          performed using this response, including subsequent planTableScan and
+          fetchScanTasks calls.
+
+          In this section, "reader" refers to the read-side actor that applies 
restrictions
+          per row or per column. "Engine" refers to the broader 
query-execution context
+          that defines query lifetime and scope (e.g. a SQL session, a single 
PyIceberg
+          scan), and is the actor responsible for query-scoped behavior such 
as salt
+          generation in sha-256-query-local.
+
+          These restrictions apply only to the authenticated principal, user, 
or account
+          associated with the request. They MUST NOT be interpreted as global 
policy and
+          MUST NOT be applied beyond the entity identified by the 
Authentication header
+          (or other applicable authentication mechanism).
+
+          An empty ReadRestrictions object (no required-column-projections and 
no
+          required-row-filter) imposes no restrictions and is equivalent to 
the field
+          being absent from the response.
+          A server MUST NOT return an action for a column whose type is not 
listed in
+          that action's "Applicable to" set.
+
+          NULL handling is action-specific. Each action's description 
specifies its
+          behavior on NULL input.
+
+          If a column projection targets a struct-typed field, other column 
projections
+          in the same ReadRestrictions MUST NOT target any of that struct's 
subfields
+          (at any depth). This avoids ambiguity about which action governs a 
given
+          leaf value.
+
+          Example:
+
+            {
+              "required-column-projections": [
+                { "field-id": 4, "action": "show-last-4" },
+                { "field-id": 6, "action": "replace-with-null" },
+                { "field-id": 8, "action": "truncate-to-year" },
+                { "field-id": 10, "action": "sha-256-global" },
+                { "field-id": 12, "action": "mask-alphanum" }
+              ],
+              "required-row-filter": {
+                "type": "eq",
+                "term": "region",
+                "value": "US"
+              }
+            }
+      properties:
+        required-column-projections:
+          description: >
+            A list of columns that require specific actions to be applied when 
reading.
+
+            If this property is absent, a reader MAY access all columns of the 
table as-is
+            without any mandatory transformations.
+
+            If this property is present, each listed column MUST have its 
specified
+            action applied. Columns not listed in required-column-projections
+            are not subject to any read restrictions.
+
+            When this list is present:
+
+            1. For each column listed in required-column-projections, the 
reader MUST apply
+              the specified action before returning values for that column.
+
+            2. The reader MUST replace all output references to the column 
with the result
+              of the action, presenting the result under the original column 
name. For
+              example, if the action for column cc is mask-alphanum, the 
reader MUST
+              return the masked value as cc in the query output.
+
+            3. Columns not listed in required-column-projections MAY be 
projected normally
+              by the reader without any mandatory transformations.
+
+            4. A column MUST appear at most once in 
required-column-projections.
+
+            5. If a projected column's action cannot be evaluated by the 
reader (including
+              unrecognized action types), the reader MUST fail the query with 
an error to
+              the caller. The reader MUST NOT silently return raw, partial, or 
empty
+              results to mask the failure.
+
+            6. Each action defines the output type for its column. For all 
predefined
+              actions, the output type matches the input column type.
+
+          type: array
+          items:
+            $ref: '#/components/schemas/Action'
+        required-row-filter:
+          description: >
+            An expression that filters rows in the table that the 
authenticated principal does not have access to.
+
+            1. The expression MUST evaluate to a boolean (TRUE or FALSE; 
Iceberg expressions
+              never produce NULL). A reader MUST discard any row for which
+              the filter evaluates to FALSE, and no information derived from 
discarded rows
+              MAY be included in the query result.
+
+            2. Row filters MUST be evaluated against the original, 
untransformed column values.
+              Required projections MUST be applied only after row filters are 
applied.
+
+            3. If a reader cannot interpret or evaluate a provided filter 
expression, it MUST
+              fail the query with an error to the caller. The reader MUST NOT 
silently return
+              partial, raw, or empty results to mask the failure.
+
+            4. If this property is absent, null, or always true then no 
mandatory filtering is required.
+          allOf:
+            - $ref: '#/components/schemas/Expression'
+
+    Action:
+      discriminator:
+        propertyName: action
+        mapping:
+          mask-alphanum: '#/components/schemas/MaskAlphanum'
+          mask-to-fixed-value: '#/components/schemas/MaskToFixedValue'
+          replace-with-null: '#/components/schemas/ReplaceWithNull'
+          show-first-4: '#/components/schemas/ShowFirst4'
+          show-last-4: '#/components/schemas/ShowLast4'
+          truncate-to-year: '#/components/schemas/TruncateToYear'
+          truncate-to-month: '#/components/schemas/TruncateToMonth'
+          sha-256-global: '#/components/schemas/Sha256Global'
+          sha-256-query-local: '#/components/schemas/Sha256QueryLocal'
+      type: object
+      required:
+        - action
+        - field-id
+      properties:
+        action:
+          type: string
+        field-id:
+          type: integer
+          description: Field ID of the column being projected.
+
+    MaskAlphanum:
+      description: >
+        Redacts the column value Unicode code point by code point using the 
following rules:
+
+        - Digits (U+0030–U+0039, 0-9) are replaced with 'n'
+        - The following punctuation characters are kept as-is:
+            U+0028 '('  LEFT PARENTHESIS
+            U+0029 ')'  RIGHT PARENTHESIS
+            U+002C ','  COMMA
+            U+002E '.'  FULL STOP
+            U+002D '-'  HYPHEN-MINUS
+            U+0040 '@'  COMMERCIAL AT
+        - All other Unicode characters (including letters, whitespace, and any 
punctuation
+          not listed above) are replaced with 'x'
+
+        For example: "[email protected]" → "[email protected]"
+
+        NULL input is preserved (NULL → NULL).
+
+        Applicable to: string
+      allOf:
+        - $ref: '#/components/schemas/Action'
+      properties:
+        action:
+          type: string
+          const: "mask-alphanum"
+
+    MaskToFixedValue:
+      description: >
+        Replaces the column value with a predefined type-specific fixed value.
+        Readers MUST use exactly the values listed below to ensure consistency
+        across implementations.
+
+        Fixed values by type:
+        - boolean: false
+        - int: 0
+        - long: 0
+        - float: 0.0
+        - double: 0.0
+        - decimal(p, s): 0 (zero with s digits after the decimal point, e.g. 
0.00 for decimal(p,2))
+        - string: "XXXXXXXX"
+        - date: 1970-01-01
+        - time: 00:00:00
+        - timestamp: 1970-01-01T00:00:00
+        - timestamptz: 1970-01-01T00:00:00+00:00
+        - timestamp_ns: 1970-01-01T00:00:00.000000000
+        - timestamptz_ns: 1970-01-01T00:00:00.000000000+00:00
+        - uuid: 00000000-0000-0000-0000-000000000000
+        - fixed(n): n zero bytes
+        - binary: empty byte sequence
+        - variant: {}
+        - geometry: POINT EMPTY
+        - geography: POINT EMPTY
+        - list: empty list []
+        - map: empty map {}
+        - struct: struct with each field set to its type-specific default 
(applied recursively)

Review Comment:
   same



##########
open-api/rest-catalog-open-api.yaml:
##########
@@ -3663,6 +3672,313 @@ components:
           additionalProperties:
             type: string
 
+    ReadRestrictions:
+      type: object
+      description: >
+          Read restrictions for a table.
+
+          A reader evaluates the row filter against original, untransformed 
column
+          values, then applies required-column-projections to the surviving 
rows.
+          Each action must produce a value of the same type as the input 
column.
+          If a reader cannot apply any returned restriction (a filter 
expression
+          or an action), it must fail the query and must not silently return 
raw,
+          partial, or empty results.
+
+          If a projection targets a nested-typed field (struct, list, or map),
+          other projections in the same ReadRestrictions must not target any
+          nested field-id (struct subfields, list elements, or map keys/values)
+          at any depth. This avoids ambiguity about which action governs a 
given
+          leaf value.
+
+          An empty ReadRestrictions object (no required-column-projections and 
no
+          required-row-filter) imposes no restrictions and is equivalent to the
+          field being absent from the response.
+      example:
+        required-column-projections:
+          - field-id: 4
+            action: show-last-4
+          - field-id: 6
+            action: replace-with-null
+          - field-id: 8
+            action: truncate-to-year
+          - field-id: 10
+            action: sha-256-global
+          - field-id: 12
+            action: mask-alphanum
+        required-row-filter:
+          type: eq
+          term: region

Review Comment:
   required-row-filter is an iceberg expression which refers to one or many 
field ids



##########
open-api/rest-catalog-open-api.yaml:
##########
@@ -3663,6 +3672,313 @@ components:
           additionalProperties:
             type: string
 
+    ReadRestrictions:
+      type: object
+      description: >
+          Read restrictions for a table.
+
+          A reader evaluates the row filter against original, untransformed 
column
+          values, then applies required-column-projections to the surviving 
rows.
+          Each action must produce a value of the same type as the input 
column.
+          If a reader cannot apply any returned restriction (a filter 
expression
+          or an action), it must fail the query and must not silently return 
raw,
+          partial, or empty results.
+
+          If a projection targets a nested-typed field (struct, list, or map),
+          other projections in the same ReadRestrictions must not target any
+          nested field-id (struct subfields, list elements, or map keys/values)
+          at any depth. This avoids ambiguity about which action governs a 
given
+          leaf value.
+
+          An empty ReadRestrictions object (no required-column-projections and 
no
+          required-row-filter) imposes no restrictions and is equivalent to the
+          field being absent from the response.
+      example:
+        required-column-projections:
+          - field-id: 4
+            action: show-last-4
+          - field-id: 6
+            action: replace-with-null
+          - field-id: 8
+            action: truncate-to-year
+          - field-id: 10
+            action: sha-256-global
+          - field-id: 12
+            action: mask-alphanum
+        required-row-filter:
+          type: eq
+          term: region
+          value: US
+      properties:
+        required-column-projections:
+          description: >
+            A list of columns that require specific actions to be applied when 
reading.
+            A server must not return an action for a column whose type is not 
listed in
+            that action's "Applicable to" set. If absent or empty, no required 
actions
+            apply; columns not listed are not subject to any required action.
+
+            1. For each column listed, the reader must apply the specified 
action before
+              returning values for that column.
+
+            2. The reader must replace all output references to the column 
with the result
+              of the action, presenting the result under the original 
field-id. For
+              example, if the action for field-id `9` is mask-alphanum, the 
reader must
+              return the masked value as field-id `9` in the query output.
+
+            3. A server must not return more than one projection for the same 
field-id
+              in required-column-projections. If a duplicate field-id appears, 
the reader
+              must fail the query.
+
+            4. A projection must not target a map's key field-id. Applying an 
action
+              to keys can produce duplicate or null keys, which readers 
silently
+              coalesce or reject, causing data loss.
+
+            5. The reader must fail the query if a projection references a 
field-id
+              that is not present in the read schema.
+          type: array
+          items:
+            $ref: '#/components/schemas/Action'
+        required-row-filter:
+          description: >
+            An expression that filters rows in the table that the 
authenticated principal does not have access to.
+
+            1. The expression must evaluate to a boolean (TRUE or FALSE; 
Iceberg predicates
+              never produce NULL). A reader must discard any row for which the 
filter
+              evaluates to FALSE, and no information derived from discarded 
rows may be
+              included in the query result.
+
+            2. If this property is absent, null, or always true then no 
mandatory filtering is required.
+          allOf:
+            - $ref: '#/components/schemas/Expression'
+
+    Action:
+      discriminator:
+        propertyName: action
+        mapping:
+          mask-alphanum: '#/components/schemas/MaskAlphanum'
+          mask-to-fixed-value: '#/components/schemas/MaskToFixedValue'
+          replace-with-null: '#/components/schemas/ReplaceWithNull'
+          show-first-4: '#/components/schemas/ShowFirst4'
+          show-last-4: '#/components/schemas/ShowLast4'
+          truncate-to-year: '#/components/schemas/TruncateToYear'
+          truncate-to-month: '#/components/schemas/TruncateToMonth'
+          sha-256-global: '#/components/schemas/Sha256Global'
+          sha-256-query-local: '#/components/schemas/Sha256QueryLocal'
+      type: object
+      required:
+        - action
+        - field-id
+      properties:
+        action:
+          type: string
+        field-id:
+          type: integer
+          description: Field ID of the column being projected.
+
+    MaskAlphanum:
+      description: >
+        Redacts the column value using the following rules to transform 
Unicode code points:
+
+        - Digits (U+0030–U+0039, 0-9) are replaced with 'n'
+        - The following punctuation characters are kept as-is:
+            U+0028 '('  LEFT PARENTHESIS
+            U+0029 ')'  RIGHT PARENTHESIS
+            U+002C ','  COMMA
+            U+002E '.'  FULL STOP
+            U+002D '-'  HYPHEN-MINUS
+            U+0040 '@'  COMMERCIAL AT
+        - All other Unicode characters (including letters, whitespace, and any 
punctuation
+          not listed above) are replaced with 'x'
+
+        For example: "[email protected]" -> "[email protected]"
+
+        NULL input is preserved (NULL -> NULL).
+
+        Applicable to: string
+      allOf:
+        - $ref: '#/components/schemas/Action'
+      properties:
+        action:
+          type: string
+          const: "mask-alphanum"
+
+    MaskToFixedValue:
+      description: >
+        Replaces the column value with a type-specific fixed value.
+        Readers must use exactly the values listed below to ensure consistency
+        across implementations.
+
+        Fixed values by type:
+        - boolean: false
+        - int: 0
+        - long: 0
+        - float: 0.0
+        - double: 0.0
+        - decimal(p, s): 0 (the unscaled value is 0)
+        - string: "XXXXXXXX"
+        - date: 1970-01-01
+        - time: 00:00:00
+        - timestamp: 1970-01-01T00:00:00
+        - timestamptz: 1970-01-01T00:00:00+00:00
+        - timestamp_ns: 1970-01-01T00:00:00.000000000
+        - timestamptz_ns: 1970-01-01T00:00:00.000000000+00:00
+        - uuid: 00000000-0000-0000-0000-000000000000
+        - fixed(n): n zero bytes
+        - binary: empty byte sequence
+        - variant: {}
+        - list: empty list []
+        - map: empty map {}
+        - struct: struct with each field set to its type-specific default 
(applied recursively)
+
+        NULL input is also replaced with the type-specific fixed value; NULL 
is not preserved.
+
+        Applicable to: all data types except unknown, geometry, and geography.

Review Comment:
   (nit) not sure if it is better to come up with a list where this action is 
supported vs a list where this action is not supported. It seems safer to use 
the former so that new types would be "blocked" until the spec is updated, vs 
making the sure this part of the spec is also updated when you type is being 
addred (if needed).
   
   Or maybe Iceberg spec could define a default value per type when it makes 
sense and refer to that generically



##########
open-api/rest-catalog-open-api.py:
##########
@@ -589,6 +589,154 @@ class StorageCredential(BaseModel):
     config: dict[str, str]
 
 
+class Action(BaseModel):
+    action: str
+    field_id: int = Field(
+        ..., alias='field-id', description='Field ID of the column being 
projected.'
+    )
+
+
+class MaskAlphanum(Action):
+    """
+    Redacts the column value using the following rules to transform Unicode 
code points:
+    - Digits (U+0030–U+0039, 0-9) are replaced with 'n' - The following 
punctuation characters are kept as-is:
+        U+0028 '('  LEFT PARENTHESIS
+        U+0029 ')'  RIGHT PARENTHESIS
+        U+002C ','  COMMA
+        U+002E '.'  FULL STOP
+        U+002D '-'  HYPHEN-MINUS
+        U+0040 '@'  COMMERCIAL AT
+    - All other Unicode characters (including letters, whitespace, and any 
punctuation
+      not listed above) are replaced with 'x'
+
+    For example: "[email protected]" -> "[email protected]"
+    NULL input is preserved (NULL -> NULL).
+    Applicable to: string
+
+    """
+
+    action: Literal['mask-alphanum'] | None = None
+
+
+class MaskToFixedValue(Action):
+    """
+    Replaces the column value with a type-specific fixed value. Readers must 
use exactly the values listed below to ensure consistency across 
implementations.
+    Fixed values by type: - boolean: false - int: 0 - long: 0 - float: 0.0 - 
double: 0.0 - decimal(p, s): 0 (the unscaled value is 0) - string: "XXXXXXXX" - 
date: 1970-01-01 - time: 00:00:00 - timestamp: 1970-01-01T00:00:00 - 
timestamptz: 1970-01-01T00:00:00+00:00 - timestamp_ns: 
1970-01-01T00:00:00.000000000 - timestamptz_ns: 
1970-01-01T00:00:00.000000000+00:00 - uuid: 
00000000-0000-0000-0000-000000000000 - fixed(n): n zero bytes - binary: empty 
byte sequence - variant: {} - list: empty list [] - map: empty map {} - struct: 
struct with each field set to its type-specific default (applied recursively)
+    NULL input is also replaced with the type-specific fixed value; NULL is 
not preserved.
+    Applicable to: all data types except unknown, geometry, and geography. 
Because those three types have no fixed value defined above, a catalog server 
must not return mask-to-fixed-value for a struct, list, or map that contains a 
field of type unknown, geometry, or geography at any nesting depth.

Review Comment:
   (nit) not sure if it is better to come up with a list where this action is 
supported vs a list where this action is not supported. It seems safer to use 
the former so that new types would be "blocked" until the spec is updated, vs 
making the sure this part of the spec is also updated when you type is being 
addred (if needed).
   
   Or maybe Iceberg spec could define a default value per type when it makes 
sense and refer to that generically



##########
open-api/rest-catalog-open-api.yaml:
##########
@@ -3663,6 +3672,313 @@ components:
           additionalProperties:
             type: string
 
+    ReadRestrictions:
+      type: object
+      description: >
+          Read restrictions for a table.
+
+          A reader evaluates the row filter against original, untransformed 
column
+          values, then applies required-column-projections to the surviving 
rows.
+          Each action must produce a value of the same type as the input 
column.
+          If a reader cannot apply any returned restriction (a filter 
expression
+          or an action), it must fail the query and must not silently return 
raw,
+          partial, or empty results.
+
+          If a projection targets a nested-typed field (struct, list, or map),
+          other projections in the same ReadRestrictions must not target any
+          nested field-id (struct subfields, list elements, or map keys/values)
+          at any depth. This avoids ambiguity about which action governs a 
given
+          leaf value.
+
+          An empty ReadRestrictions object (no required-column-projections and 
no
+          required-row-filter) imposes no restrictions and is equivalent to the
+          field being absent from the response.
+      example:
+        required-column-projections:
+          - field-id: 4
+            action: show-last-4
+          - field-id: 6
+            action: replace-with-null
+          - field-id: 8
+            action: truncate-to-year
+          - field-id: 10
+            action: sha-256-global
+          - field-id: 12
+            action: mask-alphanum
+        required-row-filter:
+          type: eq
+          term: region
+          value: US
+      properties:
+        required-column-projections:
+          description: >
+            A list of columns that require specific actions to be applied when 
reading.
+            A server must not return an action for a column whose type is not 
listed in
+            that action's "Applicable to" set. If absent or empty, no required 
actions
+            apply; columns not listed are not subject to any required action.
+
+            1. For each column listed, the reader must apply the specified 
action before
+              returning values for that column.
+
+            2. The reader must replace all output references to the column 
with the result
+              of the action, presenting the result under the original 
field-id. For
+              example, if the action for field-id `9` is mask-alphanum, the 
reader must
+              return the masked value as field-id `9` in the query output.
+
+            3. A server must not return more than one projection for the same 
field-id
+              in required-column-projections. If a duplicate field-id appears, 
the reader
+              must fail the query.
+
+            4. A projection must not target a map's key field-id. Applying an 
action
+              to keys can produce duplicate or null keys, which readers 
silently
+              coalesce or reject, causing data loss.
+
+            5. The reader must fail the query if a projection references a 
field-id
+              that is not present in the read schema.
+          type: array
+          items:
+            $ref: '#/components/schemas/Action'
+        required-row-filter:
+          description: >
+            An expression that filters rows in the table that the 
authenticated principal does not have access to.
+
+            1. The expression must evaluate to a boolean (TRUE or FALSE; 
Iceberg predicates
+              never produce NULL). A reader must discard any row for which the 
filter
+              evaluates to FALSE, and no information derived from discarded 
rows may be
+              included in the query result.
+
+            2. If this property is absent, null, or always true then no 
mandatory filtering is required.
+          allOf:
+            - $ref: '#/components/schemas/Expression'
+
+    Action:
+      discriminator:
+        propertyName: action
+        mapping:
+          mask-alphanum: '#/components/schemas/MaskAlphanum'
+          mask-to-fixed-value: '#/components/schemas/MaskToFixedValue'
+          replace-with-null: '#/components/schemas/ReplaceWithNull'
+          show-first-4: '#/components/schemas/ShowFirst4'
+          show-last-4: '#/components/schemas/ShowLast4'
+          truncate-to-year: '#/components/schemas/TruncateToYear'
+          truncate-to-month: '#/components/schemas/TruncateToMonth'
+          sha-256-global: '#/components/schemas/Sha256Global'
+          sha-256-query-local: '#/components/schemas/Sha256QueryLocal'
+      type: object
+      required:
+        - action
+        - field-id
+      properties:
+        action:
+          type: string
+        field-id:
+          type: integer
+          description: Field ID of the column being projected.
+
+    MaskAlphanum:
+      description: >
+        Redacts the column value using the following rules to transform 
Unicode code points:
+
+        - Digits (U+0030–U+0039, 0-9) are replaced with 'n'
+        - The following punctuation characters are kept as-is:
+            U+0028 '('  LEFT PARENTHESIS
+            U+0029 ')'  RIGHT PARENTHESIS
+            U+002C ','  COMMA
+            U+002E '.'  FULL STOP
+            U+002D '-'  HYPHEN-MINUS
+            U+0040 '@'  COMMERCIAL AT
+        - All other Unicode characters (including letters, whitespace, and any 
punctuation
+          not listed above) are replaced with 'x'
+
+        For example: "[email protected]" -> "[email protected]"
+
+        NULL input is preserved (NULL -> NULL).
+
+        Applicable to: string
+      allOf:
+        - $ref: '#/components/schemas/Action'
+      properties:
+        action:
+          type: string
+          const: "mask-alphanum"
+
+    MaskToFixedValue:
+      description: >
+        Replaces the column value with a type-specific fixed value.
+        Readers must use exactly the values listed below to ensure consistency
+        across implementations.
+
+        Fixed values by type:
+        - boolean: false
+        - int: 0
+        - long: 0
+        - float: 0.0
+        - double: 0.0
+        - decimal(p, s): 0 (the unscaled value is 0)
+        - string: "XXXXXXXX"
+        - date: 1970-01-01
+        - time: 00:00:00
+        - timestamp: 1970-01-01T00:00:00
+        - timestamptz: 1970-01-01T00:00:00+00:00
+        - timestamp_ns: 1970-01-01T00:00:00.000000000
+        - timestamptz_ns: 1970-01-01T00:00:00.000000000+00:00
+        - uuid: 00000000-0000-0000-0000-000000000000
+        - fixed(n): n zero bytes
+        - binary: empty byte sequence
+        - variant: {}
+        - list: empty list []
+        - map: empty map {}
+        - struct: struct with each field set to its type-specific default 
(applied recursively)
+
+        NULL input is also replaced with the type-specific fixed value; NULL 
is not preserved.
+
+        Applicable to: all data types except unknown, geometry, and geography.
+        Because those three types have no fixed value defined above, a catalog 
server
+        must not return mask-to-fixed-value for a struct, list, or map that 
contains a
+        field of type unknown, geometry, or geography at any nesting depth.
+      allOf:
+        - $ref: '#/components/schemas/Action'
+      properties:
+        action:
+          type: string
+          const: "mask-to-fixed-value"
+
+    ReplaceWithNull:
+      description: >
+        Replaces the entire column value with NULL. NULL input is preserved 
(NULL -> NULL).

Review Comment:
   (nit) Is the word `entire` needed? it is not used in other similar actions 
although the whole content is being replaced



##########
open-api/rest-catalog-open-api.yaml:
##########
@@ -3663,6 +3672,313 @@ components:
           additionalProperties:
             type: string
 
+    ReadRestrictions:
+      type: object
+      description: >
+          Read restrictions for a table.
+
+          A reader evaluates the row filter against original, untransformed 
column
+          values, then applies required-column-projections to the surviving 
rows.
+          Each action must produce a value of the same type as the input 
column.
+          If a reader cannot apply any returned restriction (a filter 
expression
+          or an action), it must fail the query and must not silently return 
raw,
+          partial, or empty results.
+
+          If a projection targets a nested-typed field (struct, list, or map),
+          other projections in the same ReadRestrictions must not target any
+          nested field-id (struct subfields, list elements, or map keys/values)
+          at any depth. This avoids ambiguity about which action governs a 
given
+          leaf value.
+
+          An empty ReadRestrictions object (no required-column-projections and 
no
+          required-row-filter) imposes no restrictions and is equivalent to the
+          field being absent from the response.
+      example:
+        required-column-projections:
+          - field-id: 4
+            action: show-last-4
+          - field-id: 6
+            action: replace-with-null
+          - field-id: 8
+            action: truncate-to-year
+          - field-id: 10
+            action: sha-256-global
+          - field-id: 12
+            action: mask-alphanum
+        required-row-filter:
+          type: eq
+          term: region
+          value: US
+      properties:
+        required-column-projections:
+          description: >
+            A list of columns that require specific actions to be applied when 
reading.
+            A server must not return an action for a column whose type is not 
listed in
+            that action's "Applicable to" set. If absent or empty, no required 
actions
+            apply; columns not listed are not subject to any required action.
+
+            1. For each column listed, the reader must apply the specified 
action before
+              returning values for that column.
+
+            2. The reader must replace all output references to the column 
with the result
+              of the action, presenting the result under the original 
field-id. For
+              example, if the action for field-id `9` is mask-alphanum, the 
reader must
+              return the masked value as field-id `9` in the query output.
+
+            3. A server must not return more than one projection for the same 
field-id
+              in required-column-projections. If a duplicate field-id appears, 
the reader
+              must fail the query.
+
+            4. A projection must not target a map's key field-id. Applying an 
action
+              to keys can produce duplicate or null keys, which readers 
silently
+              coalesce or reject, causing data loss.
+
+            5. The reader must fail the query if a projection references a 
field-id
+              that is not present in the read schema.
+          type: array
+          items:
+            $ref: '#/components/schemas/Action'
+        required-row-filter:
+          description: >
+            An expression that filters rows in the table that the 
authenticated principal does not have access to.
+
+            1. The expression must evaluate to a boolean (TRUE or FALSE; 
Iceberg predicates
+              never produce NULL). A reader must discard any row for which the 
filter
+              evaluates to FALSE, and no information derived from discarded 
rows may be
+              included in the query result.
+
+            2. If this property is absent, null, or always true then no 
mandatory filtering is required.
+          allOf:
+            - $ref: '#/components/schemas/Expression'
+
+    Action:
+      discriminator:
+        propertyName: action
+        mapping:
+          mask-alphanum: '#/components/schemas/MaskAlphanum'
+          mask-to-fixed-value: '#/components/schemas/MaskToFixedValue'
+          replace-with-null: '#/components/schemas/ReplaceWithNull'
+          show-first-4: '#/components/schemas/ShowFirst4'
+          show-last-4: '#/components/schemas/ShowLast4'
+          truncate-to-year: '#/components/schemas/TruncateToYear'
+          truncate-to-month: '#/components/schemas/TruncateToMonth'
+          sha-256-global: '#/components/schemas/Sha256Global'
+          sha-256-query-local: '#/components/schemas/Sha256QueryLocal'
+      type: object
+      required:
+        - action
+        - field-id
+      properties:
+        action:
+          type: string
+        field-id:
+          type: integer
+          description: Field ID of the column being projected.
+
+    MaskAlphanum:
+      description: >
+        Redacts the column value using the following rules to transform 
Unicode code points:

Review Comment:
   (nit) it seems we are using the word replaces for most actions, I wonder if 
we should align by saying "Replaces the column value with a redacted value 
created using the following rules..."



##########
open-api/rest-catalog-open-api.py:
##########
@@ -589,6 +589,154 @@ class StorageCredential(BaseModel):
     config: dict[str, str]
 
 
+class Action(BaseModel):
+    action: str
+    field_id: int = Field(
+        ..., alias='field-id', description='Field ID of the column being 
projected.'
+    )
+
+
+class MaskAlphanum(Action):
+    """
+    Redacts the column value using the following rules to transform Unicode 
code points:
+    - Digits (U+0030–U+0039, 0-9) are replaced with 'n' - The following 
punctuation characters are kept as-is:
+        U+0028 '('  LEFT PARENTHESIS
+        U+0029 ')'  RIGHT PARENTHESIS
+        U+002C ','  COMMA
+        U+002E '.'  FULL STOP
+        U+002D '-'  HYPHEN-MINUS
+        U+0040 '@'  COMMERCIAL AT
+    - All other Unicode characters (including letters, whitespace, and any 
punctuation
+      not listed above) are replaced with 'x'
+
+    For example: "[email protected]" -> "[email protected]"
+    NULL input is preserved (NULL -> NULL).
+    Applicable to: string
+
+    """
+
+    action: Literal['mask-alphanum'] | None = None
+
+
+class MaskToFixedValue(Action):
+    """
+    Replaces the column value with a type-specific fixed value. Readers must 
use exactly the values listed below to ensure consistency across 
implementations.
+    Fixed values by type: - boolean: false - int: 0 - long: 0 - float: 0.0 - 
double: 0.0 - decimal(p, s): 0 (the unscaled value is 0) - string: "XXXXXXXX" - 
date: 1970-01-01 - time: 00:00:00 - timestamp: 1970-01-01T00:00:00 - 
timestamptz: 1970-01-01T00:00:00+00:00 - timestamp_ns: 
1970-01-01T00:00:00.000000000 - timestamptz_ns: 
1970-01-01T00:00:00.000000000+00:00 - uuid: 
00000000-0000-0000-0000-000000000000 - fixed(n): n zero bytes - binary: empty 
byte sequence - variant: {} - list: empty list [] - map: empty map {} - struct: 
struct with each field set to its type-specific default (applied recursively)
+    NULL input is also replaced with the type-specific fixed value; NULL is 
not preserved.
+    Applicable to: all data types except unknown, geometry, and geography. 
Because those three types have no fixed value defined above, a catalog server 
must not return mask-to-fixed-value for a struct, list, or map that contains a 
field of type unknown, geometry, or geography at any nesting depth.
+
+    """
+
+    action: Literal['mask-to-fixed-value'] | None = None
+
+
+class ReplaceWithNull(Action):
+    """
+    Replaces the entire column value with NULL. NULL input is preserved (NULL 
-> NULL). A server must not return this action for a required (non-nullable) 
column.

Review Comment:
   (nit) do we need the word `entire` here? only used once



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to