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


##########
open-api/rest-catalog-open-api.yaml:
##########
@@ -1051,6 +1051,16 @@ paths:
         table. The configuration key "token" is used to pass an access token 
to be used as a bearer token
         for table requests. Otherwise, a token may be passed using a RFC 8693 
token type as a configuration
         key. For example, "urn:ietf:params:oauth:token-type:jwt=<JWT-token>".
+
+
+        The response may include a read-restrictions field. A reader that 
supports read
+        restrictions must fail any read against the loaded table that cannot 
apply a returned
+        restriction in full. This includes unrecognized action or expression 
types, and actions
+        whose definition cannot be loaded, parsed, or evaluated. These 
restrictions apply to every
+        read performed using this response (including subsequent planTableScan 
and fetchScanTasks
+        calls), but subsequent requests or requests made from a different 
authentication scope
+        may have different restrictions. The response should not be cached 
outside of the
+        authenticated scope.

Review Comment:
   wondering if we should be more explicit about `should not be cached outside 
of the authenticated scope`.
   
   The `ETag` parameter is still described as identifying a version of table 
metadata. If two principals get the same metadata (and thus the same ETag) but 
different `read-restrictions. Either:
   
   - ETags for `loadTable` MUST distinguish responses that differ in 
`read-restrictions` (and storage credentials), or
   - 304 MUST NOT be returned when `read-restrictions` can vary across 
authentication scopes.
   



##########
open-api/rest-catalog-open-api.yaml:
##########
@@ -3839,6 +3849,315 @@ 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 that supports read-restrictions 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.

Review Comment:
   `cannot apply any returned restriction` is easy to read as "if it can apply 
none of them" rather than "if there is a restriction it cannot apply." The 
loadTable wording (`cannot apply a returned restriction in full`) is 
unambiguous — please use that here.
   
   This also conflicts with required-column-projections rule 5 (unread columns 
"do not apply"). If an unrecognized action arrives for a column the query does 
not read, does the reader fail (this paragraph) or skip (rule 5)? Pick one and 
state it in both places.
   
   Nit: this section uses lowercase `must` while `MaskToFixedValue` uses `MUST 
NOT`. Please stick to RFC 2119 keywords throughout.



##########
open-api/rest-catalog-open-api.yaml:
##########
@@ -3839,6 +3849,315 @@ 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 that supports read-restrictions 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.
+
+          A server must not return projections on both a nested-typed field
+          (struct, list, or map) and any field-id nested within it at any
+          depth. A reader that receives such a response must fail the query.
+
+          A missing or empty ReadRestrictions object (no 
required-column-projections and no
+          required-row-filter) imposes no restrictions.
+      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
+          left:
+            type: reference
+            id: 14
+          right:
+            type: literal
+            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. A reader must enforce projections on the columns it is actually 
reading.
+              Projections referencing columns that are not being read do not 
apply.
+          type: array
+          items:
+            $ref: '#/components/schemas/Action'
+        required-row-filter:
+          description: >
+            An expression that limits which rows the reader may return.
+
+            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.
+
+            3. Column references within the expression must use field IDs 
(IdReference),
+              not column names. This ensures the filter remains valid across 
column renames,
+              consistent with required-column-projections which also reference 
columns by field-id.
+          allOf:
+            - $ref: '#/components/schemas/Predicate'
+
+    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: {}

Review Comment:
   `{}` is JSON object syntax. Iceberg `variant` is a Parquet binary 
(`metadata` + `value`); see 
[VariantEncoding.md](https://github.com/apache/parquet-format/blob/master/VariantEncoding.md).
 Iceberg also forbids a non-null variant as `initial-default` / 
`write-default`, so there is no existing table-spec constant to copy.
   
   Please name the physical value, for example an empty variant object:
   - metadata: empty v1 dictionary `[0x01, 0x00, 0x00]` 
(`VariantMetadata.empty()`)
   - value: empty object `[0x02, 0x00, 0x00]` (`basic_type=OBJECT`, 
`num_elements=0`)
   
   Same style as `fixed(n): n zero bytes` and `binary: empty byte sequence`.
   



##########
open-api/rest-catalog-open-api.yaml:
##########
@@ -3839,6 +3849,315 @@ 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 that supports read-restrictions 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.
+
+          A server must not return projections on both a nested-typed field
+          (struct, list, or map) and any field-id nested within it at any
+          depth. A reader that receives such a response must fail the query.
+
+          A missing or empty ReadRestrictions object (no 
required-column-projections and no
+          required-row-filter) imposes no restrictions.
+      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
+          left:
+            type: reference
+            id: 14
+          right:
+            type: literal
+            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.

Review Comment:
   Pin the actor, matching the nested-overlap rule and rule 3: a server MUST 
NOT return a projection that targets a map key field-id; a reader that receives 
one MUST fail the query.
   
   "A projection must not" leaves it unclear whether a non-conforming catalog 
response is something the reader may ignore.



##########
open-api/rest-catalog-open-api.yaml:
##########
@@ -3839,6 +3849,315 @@ 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 that supports read-restrictions 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.
+
+          A server must not return projections on both a nested-typed field

Review Comment:
   should we move this paragraph as rule 6 in the required-column-projections 
section below.



##########
open-api/rest-catalog-open-api.yaml:
##########
@@ -3839,6 +3849,315 @@ 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 that supports read-restrictions 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.
+
+          A server must not return projections on both a nested-typed field
+          (struct, list, or map) and any field-id nested within it at any
+          depth. A reader that receives such a response must fail the query.
+
+          A missing or empty ReadRestrictions object (no 
required-column-projections and no
+          required-row-filter) imposes no restrictions.
+      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
+          left:
+            type: reference
+            id: 14
+          right:
+            type: literal
+            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. A reader must enforce projections on the columns it is actually 
reading.
+              Projections referencing columns that are not being read do not 
apply.
+          type: array
+          items:
+            $ref: '#/components/schemas/Action'
+        required-row-filter:
+          description: >
+            An expression that limits which rows the reader may return.
+
+            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.
+
+            3. Column references within the expression must use field IDs 
(IdReference),
+              not column names. This ensures the filter remains valid across 
column renames,
+              consistent with required-column-projections which also reference 
columns by field-id.
+          allOf:
+            - $ref: '#/components/schemas/Predicate'
+
+    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: the types with a fixed value defined above. A catalog 
server
+        MUST NOT return mask-to-fixed-value for any other type.
+      allOf:
+        - $ref: '#/components/schemas/Action'
+      properties:
+        action:
+          type: string
+          const: "mask-to-fixed-value"
+
+    ReplaceWithNull:
+      description: >
+        Replaces the column value with NULL. NULL input is preserved (NULL -> 
NULL).
+
+        Applicable to: all optional types. Applying to a required 
(non-nullable) column is invalid.

Review Comment:
   "is invalid" doesn't say who rejects it. Suggest the same pairing used for 
nested overlap: a server MUST NOT return `replace-with-null` for a required 
field; a reader that receives it MUST fail the query.



-- 
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