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

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


The following commit(s) were added to refs/heads/main by this push:
     new 765ec10f62 [MINOR] docs(policy): Use SQL expressions in read 
restriction design (#14233)
765ec10f62 is described below

commit 765ec10f62921081cd8693c326e8c463dfffd2b9
Author: roryqi <[email protected]>
AuthorDate: Thu Oct 29 14:22:11 2026 +0800

    [MINOR] docs(policy): Use SQL expressions in read restriction design 
(#14233)
    
    ### What changes were proposed in this pull request?
    
    Define typed SQL expressions for row filters and column masks in the
    read-restriction design.
    
    - Replace Rego with typed SQL definitions while retaining the policy
    types and single `expression` field.
    - Specify parameter binding, Boolean row filters, type-preserving masks,
    `CASE`, and SQL null semantics.
    - Define supported functions, Unicode string ordering, mask nullability,
    and resource limits; user attributes remain unsupported.
    - Document the experimental Iceberg conversion subset and its failure
    behavior separately from source validity.
    - Align Markdown tables for readability.
    
    ### Why are the changes needed?
    
    Policy authors need a clear SQL expression contract, including supported
    expressions, binding rules, validation, and enforcement limits.
    
    ### Does this PR introduce _any_ user-facing change?
    
    No runtime changes. This PR updates design documentation only.
    
    ### How was this patch tested?
    
    - Validated all nine complete SQL examples with the SQL parser.
    - Checked JSON examples and source limits.
    - `git diff --check` and `git diff --cached --check`.
    - Runtime unit and integration suites were not rerun because this change
    only updates design documentation.
---
 ...tag-based-read-restrictions-for-iceberg-rest.md | 774 +++++++++++++--------
 1 file changed, 486 insertions(+), 288 deletions(-)

diff --git a/design-docs/tag-based-read-restrictions-for-iceberg-rest.md 
b/design-docs/tag-based-read-restrictions-for-iceberg-rest.md
index 0b39d8aa52..ab776610a1 100644
--- a/design-docs/tag-based-read-restrictions-for-iceberg-rest.md
+++ b/design-docs/tag-based-read-restrictions-for-iceberg-rest.md
@@ -79,10 +79,10 @@ request context and can resolve Gravitino policies, tags, 
users, and groups dire
 
 This design introduces two built-in policy types:
 
-| Policy type | Evaluation target | Effect |
-| --- | --- | --- |
-| `system_row_filter` | Table | Retains only rows matching one resolved 
predicate. |
-| `system_column_mask` | Top-level column | Replaces visible values using one 
Iceberg mask action. |
+| Policy type          | Evaluation target | Effect                            
                 |
+| -------------------- | ----------------- | 
-------------------------------------------------- |
+| `system_row_filter`  | Table             | Retains only rows matching one 
resolved predicate. |
+| `system_column_mask` | Top-level column  | Produces a type-preserving SQL 
masking expression. |
 
 Policies are associated with tags. The existing policy-on-tag resolver selects 
enabled policies
 from effective tags. Row-filter resolution consumes effective policies for a 
table. Column-mask
@@ -108,8 +108,8 @@ sales.customers(region field-id=9)
 If a tag selecting this policy is assigned only to `sales.orders`, only that 
table receives the
 filter:
 
-```text
-filter := col("region") == "US"
+```sql
+(region STRING) RETURNS BOOLEAN -> region = 'US'
 ```
 
 The resolver binds `region` to field ID 4 when loading `orders`. If the same 
tag is assigned to the
@@ -138,10 +138,10 @@ parsed.
 Policy selection and policy effect are separate. Tags and policy-on-tag 
selectors decide whether a
 policy is applicable; a row-filter or column-mask definition states what the 
selected policy does.
 The effect content has no `rules` list and no `when` field. Conditional filter 
results are written
-inside one restricted Rego expression. This follows the Databricks ABAC 
pattern in which a single
-row-filter UDF can use conditional logic to return its Boolean result, while 
policy applicability
-and function input binding remain separate. A future principal-aware selector 
belongs to the
-selection model, not inside a restriction definition.
+inside one SQL expression. This follows Snowflake's expression model: a row 
filter produces
+a Boolean value and a column mask produces a value of the target column's 
logical type. SQL
+`CASE` expresses conditional results while policy applicability remains 
separate. A future
+principal-aware selector belongs to the selection model, not inside a 
restriction definition.
 
 ### Row-filter content
 
@@ -154,21 +154,22 @@ The content of a row-filter policy contains exactly one 
`expression`.
   "policyType": "system_row_filter",
   "enabled": false,
   "content": {
-    "expression": "filter := col(\"region\") == \"US\" if 
is_group_member(\"auditors\") else := col(\"owner\") == session_user()"
+    "expression": "(region STRING, owner STRING) RETURNS BOOLEAN -> CASE WHEN 
is_group_member('auditors') THEN region = 'US' ELSE owner = CURRENT_USER() END"
   }
 }
 ```
 
-The expression is one complete rule whose result is the row predicate. In the 
example, the Rego
-assignment means “if the subject is an auditor, then use the region predicate; 
otherwise use the
-owner predicate.” It does not select another policy rule. The expression may 
use trusted request
-context and row values in either conditions or results. The first 
policy-on-tag selector version
+The definition declares two string parameters and a Boolean result. `region` 
and `owner` bind to
+the corresponding columns of the target table. In the body, SQL `CASE` means 
“if the subject is an
+auditor, then use the region predicate; otherwise use the owner predicate.” It 
does not select
+another policy rule. The expression may use trusted request context and row 
values in either
+conditions or results. The first policy-on-tag selector version
 does not select by principal, so subject-dependent filtering can remain inside 
this one expression.
 
 ### Column-mask content
 
-The content of a column-mask policy also contains exactly one `expression`. 
Its result is an
-Iceberg mask action name.
+The content of a column-mask policy also contains exactly one `expression`. 
Its result has the
+same logical type as the target column.
 
 ```json
 {
@@ -177,17 +178,24 @@ Iceberg mask action name.
   "policyType": "system_column_mask",
   "enabled": false,
   "content": {
-    "expression": "mask := action(\"show-last-4\") if 
is_group_member(\"auditors\") else := action(\"replace-with-null\")"
+    "expression": "(value STRING) RETURNS STRING -> CASE WHEN 
is_group_member('auditors') THEN show_last_4(value) ELSE NULL END"
   }
 }
 ```
 
-The rule result is an explicit `action("name")` value, and each condition must 
use the context-only
-expression subset. A bare string is not a mask action and is invalid. A 
condition cannot contain
-`col(...)` because an Iceberg projection selects one action for the complete 
column, not a different
-action per row. Conditional results are resolved before the response is 
serialized. If more than
-one selected policy resolves to a different mask for the same field, 
resolution fails as a
-conflict.
+`value` is explicitly declared as a string parameter. Column-mask evaluation 
binds that sole
+parameter to the original value of the column selected by policy and tag 
resolution; the
+parameter name is local to this definition, not the name of a physical column.
+For example, the same expression receives a phone number when applied to 
`phone_number` and a
+card number when applied to `card`. Both columns must match the declared 
`STRING` type. Parameter
+nullability is inherited from the selected column, and the return type must 
match that column.
+
+The SQL expression returns a masked value rather than an action name. `CASE` 
selects the first
+true condition, returning the transformed column value or a typed null. A mask 
may also return a
+literal or the original column value when its type matches the target. 
Multiple selected policies
+that resolve to distinct masks for the same field remain a conflict. A 
required restriction that
+cannot be represented by the enforcement target must fail closed; SQL syntax 
alone does not add
+new target capabilities.
 
 ### Future function reference
 
@@ -223,276 +231,462 @@ the resolver can compile the function to a closed 
standard Iceberg restriction o
 specified enforcement path declares native function support. A raw function 
reference never enters
 an Iceberg `read-restrictions` response.
 
-## Restricted Rego Expressions
+## SQL Expressions
 
-Both built-in policy types use the restricted Rego subset defined below. Its 
version is part of the
-policy content schema rather than a field repeated in every policy. The subset 
supports only one
-complete rule named `filter` or `mask`; it is not an arbitrary Rego module. A 
row-filter policy
-requires `filter`, whose result and conditions must be Boolean. A column-mask 
policy requires
-`mask`, whose result must be an explicit action value and whose condition must 
be Boolean and
-context-only.
+Both built-in policy types keep exactly one `expression` string. The authoring 
language
+is `restricted-sql-v1`, using a Snowflake-style typed definition and SQL 
`CASE` for conditional
+results. Every definition contains a parameter list, `RETURNS`, a result type, 
and `->` followed
+by the SQL body. Row filters declare `BOOLEAN`; column masks declare the 
masked column's logical
+type. There is no separate function name or creation statement.
 
-The grammar is:
-
-```text
-program     := filterRule | maskRule
-filterRule  := unconditionalFilter | conditionalFilter
-unconditionalFilter := "filter" ":=" expr
-conditionalFilter := "filter" ":=" expr "if" expr filterElse* filterFallback
-filterElse  := "else" ":=" expr "if" expr
-filterFallback := "else" ":=" expr
-maskRule    := unconditionalMask | conditionalMask
-unconditionalMask := "mask" ":=" maskAction
-conditionalMask := "mask" ":=" maskAction "if" contextExpr maskElse* 
maskFallback
-maskElse    := "else" ":=" maskAction "if" contextExpr
-maskFallback := "else" ":=" maskAction
-maskAction  := "action" "(" string ")"
-contextExpr := expr
-expr        := orExpr
-orExpr      := andExpr ("or" andExpr)*
-andExpr     := notExpr ("and" notExpr)*
-notExpr     := "not" notExpr | compareExpr
-compareExpr := primary (("==" | "!=" | "<" | "<=" | ">" | ">=" | "in") 
primary)?
-primary     := colRef | sessionUser | groupMember | literal | array | "(" expr 
")"
-colRef      := "col" "(" string ")"
-sessionUser := "session_user" "(" ")"
-groupMember := "is_group_member" "(" string ")"
-literal     := string | number | boolean | null
-array       := "[" literal ("," literal)* "]"
-boolean     := "true" | "false"
-null        := "null"
-number      := "-"? ("0" | nonZeroDigit digit*) ("." digit+)?
-digit       := "0" | nonZeroDigit
-nonZeroDigit := "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
-```
+An ANTLR parser builds immutable typed operation nodes and validates complete 
SQL definitions.
+Definitions must use `restricted-sql-v1`; Rego syntax is invalid. The server 
validates SQL
+on policy creation and content updates. Metadata reads, renames, and comment 
updates do not
+reparse source. The experimental loader binds selected definitions to the 
authenticated subject
+and table schema before converting them to closed Iceberg restrictions.
 
-`filter := value-a if condition else := value-b` and
-`mask := action("action-a") if condition else := action("action-b")` have the 
semantic reading “if
-condition, then value-a, otherwise value-b.” Conditional branches are 
evaluated from left to right
-and the first true condition selects its value. A conditional rule requires an 
unconditional final
-`else`, so a selected policy never becomes undefined. Unconditional forms omit 
`if` and `else`.
+The grammar below defines authoring validity. The experimental converter 
supports the narrower
+enforcement subset documented below; a valid SQL expression that cannot be 
represented by that
+target fails the governed load. The runtime reference is 
[Policies](../docs/policies.md).
 
-Strings use JSON double-quoted syntax. Packages, imports, additional rules, 
variables, rule bodies
-in braces, comments, exponent notation, leading `+`, leading zeroes, trailing 
decimal points,
-chained comparisons, and arbitrary functions are invalid. A row-filter root 
must be Boolean, and a
-column-mask root must be an explicit `action(...)` value.
+### Authoring Examples
 
-### Keywords, identifiers, and escaping
+With a string parameter `region`, a row filter declares a Boolean result:
 
-The restricted syntax reserves the lowercase keywords `filter`, `mask`, `if`, 
`else`, `and`, `or`,
-`not`, `in`, `true`, `false`, and `null`. `:=` is the rule-result assignment 
operator; `then` is not
-a literal token in Rego syntax because the result precedes `if`. The reserved 
built-in function
-identifiers are `col`, `session_user`, and `is_group_member`. They are 
case-sensitive and are
-recognized only as complete tokens. `action` is the reserved mask-action 
constructor. For example,
-`notebook` is not `not` followed by an identifier. Bare identifiers are not 
part of the restricted
-subset, so an unknown word is always invalid rather than an implicit column 
reference or function
-call.
+```sql
+(region STRING) RETURNS BOOLEAN -> region = 'US'
+```
 
-`filter` and `mask` are Gravitino restricted-syntax keywords, not standard 
Rego keywords. `filter`
-is valid only as the row-filter rule head, and `mask` is valid only as the 
column-mask rule head.
+The same filter can express conditional behavior with SQL `CASE`:
 
-Column names, group names, and string values appear only as JSON string 
literals. Raw U+0000 through
-U+001F characters and decoded NUL are invalid. Valid JSON escapes for control 
characters other than
-NUL, such as `\n`, are decoded and preserved. U+007F is also preserved in 
literal or escaped form. A
-name equal to a keyword needs no special keyword escape: `col("and")` 
references the column named
-`and`. Backticks, single quotes, SQL delimited identifiers, and backslash 
escaping outside a JSON
-string are invalid.
+```sql
+(region STRING) RETURNS BOOLEAN -> CASE
+  WHEN CURRENT_USER() = 'alice' THEN TRUE
+  ELSE region = 'US'
+END
+```
 
-For example, this expression is valid and unambiguous:
+With a string target column, a mask declares a string parameter and a string 
result:
 
-```text
-filter := col("filter") == "mask"
+```sql
+(value STRING) RETURNS STRING -> CASE
+  WHEN CURRENT_USER() = 'alice' THEN value
+  ELSE mask_alphanum(value)
+END
 ```
 
-The first `filter` is the rule head, `"filter"` is a column name, and `"mask"` 
is a string value.
-Similarly, `col("action")` references a column named `action`; it is unrelated 
to the
-`action(...)` mask constructor. A group name can also equal a keyword:
+It may also return a literal or null of the expected type:
 
-```text
-mask := action("show-last-4") if is_group_member("mask")
-else := action("replace-with-null")
+```sql
+(value STRING) RETURNS STRING -> CASE
+  WHEN value IS NULL THEN NULL
+  ELSE 'XXXXXXXX'
+END
 ```
 
-User-provided names cannot appear as bare identifiers. The following forms are 
invalid:
+### Expression Inputs and Results
 
-```text
-filter := region == "US"
-filter := col(mask) == "US"
-filter := filter == true
-"filter" := col("region") == "US"
-```
+Every data input must be declared in the parameter list with its logical type. 
Parameter names
+must be unique under the identifier rules below. The body may reference only 
those parameters
+and allowlisted context functions; a column existing in a table does not 
implicitly declare it.
+
+- A row filter has zero or more parameters. Each parameter binds to a column 
of the target table
+  with the same name under the identifier matching rules. Its declared type 
must match the column
+  exactly. Missing or ambiguous columns are errors. A context-only or constant 
filter may use an
+  empty parameter list, for example `() RETURNS BOOLEAN -> TRUE`.
+- A column mask has exactly one parameter. It binds to the selected column 
regardless of its
+  local name, and its declared type must match that column. For example,
+  `(xxx STRING) RETURNS STRING -> show_last_4(xxx)` and
+  `(value STRING) RETURNS STRING -> show_last_4(value)` differ only in the 
local parameter name.
+  Neither requires a physical column named `xxx` or `value`.
+
+Bound parameters inherit the columns' nullability. No implicit `value` 
parameter is injected, and
+the column's physical name is not an additional alias in a mask body. Trusted 
context functions
+do not require parameter declarations. The definition also declares the result 
type:
+
+| Use         | Expected result              | Accepted expression forms       
                                             |
+| ----------- | ---------------------------- | 
---------------------------------------------------------------------------- |
+| Row filter  | `BOOLEAN`                    | Predicate, Boolean input or 
literal, Boolean function result, Boolean `CASE` |
+| Column mask | Target column's logical type | Original input, literal, null, 
function result, scalar `CASE`                |
+
+A mask's result type must match the target column, including precision, scale, 
and timezone
+characteristics. Its conditions may use its declared parameter and trusted 
context functions.
+A non-Boolean filter result or incompatible mask result is an error. When one 
policy applies to several
+columns, each mask evaluation binds the parameter independently to its 
selected column.
+
+### SQL Expression Language
+
+#### Grammar
 
-Keywords, numbers, and built-in function names must end at the end of input or 
before a valid token
-delimiter. The lexer never splits an alphanumeric or underscore sequence into 
adjacent tokens. It
-therefore rejects prefix forms such as `notcol("active")`, `notebook`, 
`truefalse`, and `1and`
-rather than interpreting them as combinations of valid tokens.
-
-There are two syntactic JSON layers in an API request. The HTTP JSON parser 
decodes the outer
-`expression` field once, and the expression parser decodes each inner JSON 
string literal once. For
-example, the request fragment
-`"expression": "filter := col(\"and\") == \"open\""` becomes the source
-`filter := col("and") == "open"`, whose decoded column name is `and`. No layer 
performs an
-additional or implicit unescape.
-
-After decoding, identifiers and values are preserved exactly. Gravitino 
performs no Unicode
-normalization, case folding, whitespace trimming, environment expansion, URL 
decoding, or SQL
-quoting. Adapters must bind typed AST nodes or parameters and must not 
concatenate decoded names or
-values into rendered text. Canonical serialization applies JSON escaping; it 
does not change the
-logical value.
-
-Supported operand shapes are:
-
-| Form | Operators | Requirements |
-| --- | --- | --- |
-| Boolean predicates | `and`, `or`, `not` | Every operand is Boolean. |
-| Column and non-null literal | `==`, `!=`, `<`, `<=`, `>`, `>=` | Either 
operand order; compatible types. |
-| String column and session user | `==`, `!=` | Either operand order. |
-| Session user and string literal | `==`, `!=` | Folded during context 
binding. |
-| Column and `null` | `==`, `!=` | Becomes `is-null` or `not-null`. |
-| Column and literal array | `in` | Column on the left; non-empty homogeneous 
array. |
-| Session user and string array | `in` | Folded during context binding. |
-| Group membership | none | Folded during context binding. |
-
-Column-to-column comparisons, literal-to-literal comparisons, ordering on 
`session_user()`, null
-array elements, nested arrays, and comparisons on `is_group_member(...)` are 
invalid.
-
-### Limits
-
-Save-time validation applies these limits before canonicalization:
-
-- source length: 16 KiB of UTF-8;
-- operation depth: 8;
-- source AST nodes: 256;
-- decoded string literal: 4 KiB of UTF-8;
-- numeric literal: 256 bytes;
-- elements per literal array: 256;
-- total array elements: 256 in the source; and
-- parenthesis nesting: 256 levels.
-
-Operation depth counts AST operation nodes. A consecutive associative `and` or 
`or` chain is one
-n-ary logical node, and parentheses do not add depth. These limits apply to 
user-provided source.
-They are not re-applied to the generated row-filter predicate.
-
-Conditional row filters use linear nested lowering. Each condition appears 
once in its selected arm
-and once, negated, in its remaining-branches arm. The source limits therefore 
bound generated work:
-lowering is linear in the number and size of source branches, and generated 
array occurrences are at
-most twice the source total. Under the 256-node source limit, 127 minimal 
branches lower to a
-predicate with depth 256 and 890 node occurrences. A generated predicate may 
exceed the source
-depth or node limits.
-
-## Context and Schema Binding
-
-The expression subset provides two request-stable context functions:
-
-| Function | Result | Binding |
-| --- | --- | --- |
-| `session_user()` | Non-null string | Authenticated effective user name. |
-| `is_group_member("group")` | Non-null Boolean | Exact flat membership in the 
current metalake. |
-
-An unknown group, group lookup failure, missing subject, or inconsistent 
identity snapshot is a
-resolution error. An existing group that does not contain the subject returns 
`false`. Nested group
-expansion is not performed.
-
-Each column name is bound exactly once against the concrete table schema and 
converted to its stable
-Iceberg field ID. Missing or ambiguous columns, incompatible types, schema 
drift, or unsupported
-field-ID mapping fail closed.
-
-All conditional results and conditions are parsed and type-checked before 
request binding. The
-resolver then binds their context, columns, and literals and lowers an 
`if`/`else` chain to one
-Boolean predicate. For example:
+The complete stored definition includes the parameter and result declarations. 
The remaining
+productions describe its SQL body:
 
 ```text
-filter := result1 if condition1
-else := result2 if condition2
-else := fallback
+expression  := "(" (parameter ("," parameter)*)? ")" RETURNS type "->" expr EOF
+parameter   := identifier type
+type        := BOOLEAN | INTEGER | LONG | FLOAT | DOUBLE | STRING | BINARY | 
DATE
+             | DECIMAL "(" unsignedInteger "," unsignedInteger ")"
+             | TIME ("(" "6" ")")?
+             | TIMESTAMP ("(" "6" ")")? ((WITH | WITHOUT) TIME ZONE)?
+expr        := orExpr
+orExpr      := andExpr (OR andExpr)*
+andExpr     := notExpr (AND notExpr)*
+notExpr     := NOT* predicate
+predicate   := scalar (compareOp scalar
+             | IS NOT? NULL
+             | NOT? IN "(" literal ("," literal)* ")"
+             | NOT? BETWEEN scalar AND scalar
+             | NOT? LIKE string)?
+scalar      := inputRef | literal | functionCall | caseExpr | "(" expr ")"
+inputRef    := identifier
+functionCall := identifier "(" (expr ("," expr)*)? ")"
+caseExpr    := CASE expr? (WHEN expr THEN expr)+ (ELSE expr)? END
+compareOp   := "=" | "<>" | "!=" | "<" | "<=" | ">" | ">="
+literal     := string | number | boolean | null | temporal
+temporal    := (DATE | TIME | TIMESTAMP) string
+             | TIMESTAMP WITH TIME ZONE string
+boolean     := TRUE | FALSE
+null        := NULL
+number      := "-"? ("0" | nonZeroDigit digit*) ("." digit+)?
+digit       := "0" | nonZeroDigit
+nonZeroDigit := "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
+unsignedInteger := "0" | nonZeroDigit digit*
+identifier  := unquotedIdentifier | quotedIdentifier
+unquotedIdentifier := (asciiLetter | "_") (asciiLetter | digit | "_")*
+quotedIdentifier := '"' (nonDoubleQuote | '""')+ '"'
+string      := "'" (nonSingleQuote | "''")* "'"
 ```
 
-is lowered to:
+`asciiLetter` means A–Z or a–z. Quoted identifiers must not be empty. Quoted 
identifiers and
+strings decode doubled quotes once; there is no backslash escape layer inside 
SQL literals.
+
+Keywords and allowlisted built-in function names are case-insensitive. 
`functionCall` does not
+permit arbitrary functions: names and signatures must match the function 
tables below. `EOF`
+requires complete consumption. Semantic validation restricts operand and 
result types.
+Decimal precision must be between 1 and 38, with scale between zero and the 
precision. Row
+definitions must declare `BOOLEAN`; mask definitions must have one parameter 
and declare its
+exact logical type as their result. A body-only string without these 
declarations is invalid.
+
+Predicate and `CASE` snippets below illustrate the body portion; the stored 
`expression` always
+includes the complete definition shown above.
+
+Precedence, from highest to lowest, is parentheses and function calls, 
predicates, `NOT`, `AND`,
+and `OR`. `NOT active = TRUE` means `NOT (active = TRUE)`. The `AND` inside 
`BETWEEN` separates
+the bounds. Comparisons do not chain: `amount > 10 AND amount < 100` is valid; 
`10 < amount < 100`
+is invalid.
+
+#### Identifiers and Literals
+
+An unquoted identifier starts with an ASCII letter or underscore, followed by 
ASCII letters,
+digits, or underscores. Keywords in this grammar are reserved. A quoted 
identifier uses double
+quotes, escaping an embedded double quote by doubling it, for example 
`"Customer Region"`.
+Unquoted input names normalize to uppercase; quoted names preserve case, 
following Snowflake's
+identifier convention. Names must be unique after that normalization. `val` 
and `VAL` refer to
+the same parameter; `"val"` is distinct. Names bind only within the declared 
parameter list;
+qualified input references and nested paths are unsupported.
+
+String literals use single quotes; an embedded single quote is doubled, as in 
`'O''Brien'`.
+Backslashes are literal characters. Unicode is not normalized; isolated 
surrogates and NUL are invalid.
+Whitespace is allowed between tokens. Numeric literals are exact base-10 
values until type binding.
+Leading `+`, exponents, leading zeroes, and trailing decimal points are 
unsupported.
+
+Examples of literals:
+
+```sql
+'US'
+'O''Brien'
+100
+-10.50
+TRUE
+NULL
+DATE '2026-01-01'
+TIME '12:30:00'
+TIMESTAMP '2026-01-01 12:30:00'
+TIMESTAMP WITH TIME ZONE '2026-01-01 12:30:00+00:00'
+```
 
-```text
-(condition1 and result1)
-or (not condition1 and (
-  (condition2 and result2)
-  or (not condition2 and fallback)
-))
+Comments, semicolons, additional statements, and square-bracket literal arrays 
are not accepted
+as part of an expression.
+
+#### Supported Predicates
+
+| Category            | Examples                                               
                                           | Requirements                       
                                |
+| ------------------- | 
-------------------------------------------------------------------------------------------------
 | ------------------------------------------------------------------ |
+| Comparison          | `amount = 100`, `amount <> 100`, `amount < 100`, 
`amount <= 100`, `amount > 100`, `amount >= 100` | Compatible scalar operands, 
including available inputs             |
+| Range               | `amount BETWEEN 10 AND 100`, `amount NOT BETWEEN 10 
AND 100`                                      | Inclusive bounds; compatible 
ordered type                          |
+| Membership          | `region IN ('US', 'CA')`, `region NOT IN ('US', 'CA')` 
                                           | Non-empty literal list; non-null 
values have compatible types      |
+| Null test           | `val IS NULL`, `region IS NOT NULL`                    
                                           | Typed scalar operand; non-null 
Boolean result                      |
+| NaN test            | `is_nan(score)`, `NOT is_nan(score)`                   
                                           | Floating-point argument            
                                |
+| Prefix matching     | `name LIKE 'Acme%'`, `name NOT LIKE 'Acme%'`           
                                           | String operand; non-empty fixed 
prefix followed by exactly one `%` |
+| Boolean composition | `region = 'US' AND amount < 100`, `region = 'US' OR 
region = 'CA'`, `NOT (region = 'US')`         | Boolean operands; parentheses 
may nest                             |
+| Boolean constant    | `TRUE`, `FALSE`                                        
                                           | Visible-row predicate              
                                |
+| Date part           | `year(order_date) = 2026`, `month(order_date) = 3`, 
`day(order_date) = 15`                        | Date-part result compared with 
a compatible integer literal        |
+| Current user        | `owner = CURRENT_USER()`                               
                                           | Compatible string operands         
                                |
+| Group membership    | `is_group_member('auditors')`                          
                                           | Non-empty literal group name; 
trusted flat membership              |
+
+`!=` is accepted as an alias for `<>`. `BETWEEN` is equivalent to inclusive 
lower and upper
+comparisons joined by `AND`; `NOT BETWEEN` negates that result. Prefix 
patterns cannot contain
+`_` or any other `%`, and the first version has no `ESCAPE` clause. Matching 
is case-sensitive.
+
+Comparisons between available inputs and constant comparisons are valid when 
types match.
+A non-Boolean result is invalid for a row filter, while a bare input is a 
valid mask expression when
+it has the expected result type. Comparisons with a null value produce 
`UNKNOWN`; use `IS NULL`
+or `IS NOT NULL` to test for null. Lists contain literals only and cannot 
contain expressions,
+references, or nested lists. Null members are accepted with ordinary SQL 
membership semantics:
+if no non-null member matches and at least one member is null, both `IN` and 
`NOT IN` are unknown.
+
+#### CASE Expressions
+
+Both Snowflake forms of `CASE` are supported:
+
+```sql
+CASE
+  WHEN region IS NULL THEN FALSE
+  WHEN region = 'US' THEN TRUE
+  ELSE FALSE
+END
+
+CASE region
+  WHEN 'US' THEN TRUE
+  WHEN 'CA' THEN TRUE
+  ELSE FALSE
+END
 ```
 
-Request-context-only conditions are folded before the final predicate is 
built, but every branch
-must still parse and type-check. For a column mask, all conditions are 
context-only: the resolver
-evaluates them in order and selects the first action whose condition is true, 
or the final fallback
-action. The conditional assignment, `if`, and `else` nodes never appear in the 
Iceberg wire
-expression or projection. This preserves the first-matching-branch semantics 
of Rego's `else`
-chain while producing the one closed Boolean predicate or one column action 
required by Iceberg.
-The nested form grows linearly rather than repeating every earlier condition 
in every later branch.
-
-The first version supports these value predicates:
-
-| Gravitino type | Iceberg type | Predicates |
-| --- | --- | --- |
-| `BOOLEAN` | `boolean` | Equality and `in` |
-| `INTEGER` | `int` | Equality, ordering, and `in` |
-| `LONG` | `long` | Equality, ordering, and `in` |
-| `FLOAT` | `float` | Equality, ordering, and `in`; finite values only |
-| `DOUBLE` | `double` | Equality, ordering, and `in`; finite values only |
-| `DECIMAL(p,s)` | `decimal(p,s)` | Equality, ordering, and `in`; no rounding |
-| `DATE` | `date` | Equality, ordering, and `in` |
-| `TIME(6)` or unset precision | `time` | Equality, ordering, and `in` |
-| `TIMESTAMP(6)` without timezone or unset precision | `timestamp` | Equality, 
ordering, and `in` |
-| `STRING` | `string` | Equality, ordering, and `in` |
-
-Null tests may apply to any nullable top-level field with a stable field ID. 
No implicit conversion
-is allowed, including numeric widening or narrowing, string-to-number 
conversion, temporal
-conversion, collation changes, signedness changes, or timezone assumptions. A 
source literal is
-assigned its expected type once from the comparison's column operand; this is 
literal typing, not a
-conversion from a runtime string or numeric value. A literal that cannot 
represent that exact type
-without reinterpretation, rounding, or loss is invalid.
-
-All context functions and named references are removed before a resolved 
predicate is serialized.
-The Iceberg wire expression contains only Boolean constants, logical 
operators, supported
-predicates, field-ID references, and typed literals.
-
-## Null Semantics
-
-Resolved predicates use Iceberg two-valued semantics. For a null field and a 
non-null literal:
-
-| Predicate | Result |
-| --- | --- |
-| `is-null` | `true` |
-| `not-null` | `false` |
-| `eq`, `gt`, `gt-eq`, or `in` | `false` |
-| `not-eq`, `lt`, or `lt-eq` | `true` |
-
-Therefore, `col("region") != "US"` retains rows where `region` is null. 
Authors who want to
-exclude nulls must add `col("region") != null`.
-
-## Column Masks
-
-The action vocabulary is defined by Iceberg read restrictions:
-
-- `mask-alphanum`;
-- `mask-to-fixed-value`;
-- `replace-with-null`;
-- `show-first-4`;
-- `show-last-4`;
-- `truncate-to-year`;
-- `truncate-to-month`;
-- `sha-256-global`; and
-- `sha-256-query-local`.
-
-Applicable types, fixed values, output encodings, Unicode behavior, and null 
behavior follow the
-pinned Iceberg specification. Unknown actions and unsupported action/type 
pairs fail closed.
-`replace-with-null` is invalid for a required field.
-
-`action("name")` constructs a typed mask action during parsing. It is not a 
runtime UDF and does not
-convert a string result into an action. A bare string, unknown action name, or 
action with an
-unsupported input type is invalid.
-
-The server returns the logical Iceberg action, and the reader owns execution. 
For
-`sha-256-query-local`, the reader also owns generation and lifecycle of the 
per-query salt defined
-by Iceberg.
-
-Only top-level fields are supported in the first version. A row filter 
evaluates original values,
-and masks apply afterward to surviving rows.
+In searched `CASE`, every condition must be Boolean. Conditions are evaluated 
in order, and the
+first true condition selects its result; false and unknown conditions do not 
match. In simple
+`CASE`, the selector and each match expression must have compatible types. 
Matching uses SQL
+equality, so a null selector does not match a null match value. Only the 
selected result is
+evaluated. No match returns the `ELSE` result, or null if `ELSE` is omitted.
+
+All result branches of one `CASE` must resolve to one logical type compatible 
with its surrounding
+expression. Null literals take the type expected at that position. The 
complete expression must
+match the expected result type. Every branch is statically validated, 
including branches that
+cannot be reached for a particular row.
+Nested `CASE` expressions and built-in calls are allowed within the resource 
limits.
+
+### Type and Context Binding
+
+Parameter names and types are explicit in the definition. Type checking 
validates the SQL body
+against those declarations and the result type before evaluation. Binding 
verifies row-filter
+parameters against their target table columns, or the sole mask parameter 
against the selected
+column. Declared types must match exactly; bound nullability comes from the 
column. A mask's local
+parameter name is independent of the physical column name.
+Missing or ambiguous names, unavailable inputs, unsupported types, 
incompatible operands, and
+invalid function arguments are errors.
+
+| Gravitino type                          | Supported value predicates         
                             |
+| --------------------------------------- | 
--------------------------------------------------------------- |
+| `BOOLEAN`                               | Equality and membership            
                             |
+| Signed `INTEGER`, signed `LONG`         | Equality, ordering, membership, 
and ranges                      |
+| `FLOAT`, `DOUBLE`                       | Equality, ordering, membership, 
ranges, and NaN tests           |
+| `DECIMAL(p,s)`                          | Equality, ordering, membership, 
and ranges; no rounding         |
+| `STRING`                                | Equality, ordering, membership, 
ranges, and prefix matching     |
+| `DATE`                                  | Equality, ordering, membership, 
ranges, and date-part functions |
+| `TIME(6)`                               | Equality, ordering, membership, 
and ranges                      |
+| `TIMESTAMP(6)` with or without timezone | Equality, ordering, membership, 
ranges, and date-part functions |
+
+Unset temporal precision means six fractional digits in this profile. Other 
value-predicate types
+and temporal precisions are unsupported. Null tests may apply to any top-level 
column. On a
+required column, `IS NULL` is false and `IS NOT NULL` is true.
+
+There is no implicit cross-family conversion, signedness conversion, rounding, 
or timezone
+assumption. Integer values must be mathematical integers within range. Decimal 
values must fit
+the target precision and scale exactly. Floating-point literals preserve 
negative zero and reject
+overflow, infinity, and NaN. Strings compare lexicographically by Unicode code 
point,
+case-sensitively and without Unicode normalization. Constant/context 
comparisons and bound string
+column predicates use the same ordering. `BINARY` supports null tests and 
compatible scalar masks,
+but no value comparisons.
+Temporal literals must use the matching type keyword, valid calendar or clock 
values, and at
+most six fractional-second digits. Timestamp-with-timezone literals require an 
explicit offset.
+
+#### Context Functions
+
+| Function                   | Result             | Meaning                    
                   |
+| -------------------------- | ------------------ | 
--------------------------------------------- |
+| `CURRENT_USER()`           | Non-null `STRING`  | Trusted effective subject 
name                |
+| `is_group_member('group')` | Non-null `BOOLEAN` | Exact flat membership in 
the current metalake |
+
+`CURRENT_USER()` follows Snowflake's context-function spelling. 
`is_group_member` retains the
+trusted group-membership behavior and is a Gravitino extension, not a 
Snowflake built-in.
+Group names must be non-empty string literals. An unknown group, group lookup 
failure, missing
+subject, or inconsistent identity snapshot is an error. An existing group 
without the subject
+returns false; nested group expansion is unsupported. Context values are 
stable for one conversion
+and use the effective subject. Group lookups are cached within that conversion.
+
+Membership comes from the authenticated principal's supplied groups. An empty 
group list is valid
+and means non-membership. Enabling authorization does not populate groups; 
authenticators such
+as `simple` that supply none take the non-member branch. The referenced group 
must still exist
+in Gravitino. User attributes, including `user_attribute(...)`, are 
unsupported.
+
+#### Date-Part Functions
+
+`year(column)`, `month(column)`, and `day(column)` accept date and supported 
timestamp columns.
+They return an integer year, month of year, or day of month. Null input 
returns null.
+Timestamp-with-timezone values use UTC; timestamps without timezone use their 
recorded calendar
+fields. Dates use the proleptic Gregorian calendar.
+
+`month(order_date) = 3` matches March dates in every year; it does not imply a 
particular year.
+
+### Null Semantics
+
+Row filters use SQL three-valued logic: `TRUE`, `FALSE`, and `UNKNOWN` (SQL 
`NULL`). Only `TRUE`
+retains a row. A null column produces:
+
+| Predicate                                | Result    |
+| ---------------------------------------- | --------- |
+| `column IS NULL`                         | `TRUE`    |
+| `column IS NOT NULL`                     | `FALSE`   |
+| Comparison with a non-null value         | `UNKNOWN` |
+| `IN`, `NOT IN`, `BETWEEN`, `NOT BETWEEN` | `UNKNOWN` |
+| `LIKE`, `NOT LIKE`, `is_nan(column)`     | `UNKNOWN` |
+
+`NOT UNKNOWN` is unknown. `FALSE AND UNKNOWN` is false; `TRUE AND UNKNOWN` is 
unknown.
+`TRUE OR UNKNOWN` is true; `FALSE OR UNKNOWN` is unknown.
+
+Consequently, `region <> 'US'` and `NOT (region = 'US')` both exclude null 
regions. To include
+them, write `region IS NULL OR region <> 'US'`. A constant-false filter 
returns an empty result,
+rather than denying access to the table.
+
+### Mask Functions
+
+The Gravitino mask functions defined by this profile remain available inside a 
SQL mask expression.
+They are ordinary typed scalar calls and may appear in `CASE` branches or 
nested calls with matching
+types. They are Gravitino functions rather than a claim of Snowflake function 
compatibility. Each
+function preserves its input logical type. In the examples below, `value` 
denotes the explicitly
+declared mask parameter, which must have a type accepted by the function.
+
+| SQL expression               | Input types                                   
                     | Meaning                                                  
                                   |
+| ---------------------------- | 
------------------------------------------------------------------ | 
-------------------------------------------------------------------------------------------
 |
+| `mask_alphanum(value)`       | `STRING`                                      
                     | ASCII digits become `n`; `(),.@-` are preserved; every 
other Unicode code point becomes `x` |
+| `show_first_4(value)`        | `STRING`                                      
                     | Keep the first four code points; apply the same masking 
rules to the remainder              |
+| `show_last_4(value)`         | `STRING`                                      
                     | Keep the last four code points; apply the same masking 
rules to the preceding portion       |
+| `mask_to_fixed_value(value)` | `STRING`, numeric, `BOOLEAN`, `DATE`, or 
supported timestamp types | Replace with the type's fixed value, including null 
input                                   |
+| `replace_with_null(value)`   | Any typed scalar                              
                     | Return a null of the input's type; the result is 
nullable                                   |
+| `truncate_to_year(value)`    | Date and supported timestamp types            
                     | Truncate to the beginning of the year                    
                                   |
+| `truncate_to_month(value)`   | Date and supported timestamp types            
                     | Truncate to the beginning of the month                   
                                   |
+| `sha256_global(value)`       | `STRING`, signed `INTEGER`, signed `LONG`, 
`BINARY`                | Stable type-preserving SHA-256 transformation         
                                      |
+| `sha256_query_local(value)`  | Same as `sha256_global`                       
                     | Type-preserving SHA-256 transformation salted per query  
                                   |
+
+The mask behaviors remain the same. All masks except `mask_to_fixed_value` 
preserve null input.
+Strings of four or fewer Unicode code points are returned unchanged by
+`show_first_4` and `show_last_4`. Timestamp-with-timezone truncation uses UTC.
+
+For example, `show_last_4(value)` changes `4111-1111-1111-4444` to 
`nnnn-nnnn-nnnn-4444`.
+`mask_to_fixed_value` returns `'XXXXXXXX'` for strings, zero for numbers, 
false for booleans,
+and the epoch date or timestamp for those temporal types.
+
+Hash inputs use UTF-8 for strings, little-endian two's-complement bytes for 
signed integers, and
+raw bytes for binary. String output is the 64-character lowercase hexadecimal 
digest; integer
+output uses the first four or eight digest bytes interpreted as the 
corresponding signed
+little-endian type; binary output is the complete 32-byte digest. Query-local 
hashing prepends
+a cryptographically random salt of at least 16 bytes, shared by every 
evaluation in one query;
+separate queries use independent salts.
+
+Only allowlisted functions and the shared expression grammar are accepted. 
Each mask function
+takes one compatible scalar argument; additional arguments to these calls are 
invalid. Conditions
+may use the declared mask parameter and trusted context functions. Conditional 
disclosure is
+expressed with ordinary SQL `CASE`, as in the column-mask example above. 
Row-dependent conditions
+and nested mask calls may be valid source but are outside the experimental 
conversion subset.
+
+A mask expression must have the expected result type on every branch. 
Returning null, explicitly or
+through an omitted `ELSE`, is allowed only for a nullable target column. An 
expression whose inferred
+nullability is incompatible with a required target is rejected. For example, a 
string-returning
+mask cannot return a number in one branch, and a required target cannot use 
`THEN NULL`, even in
+an unselected arm. `IS NULL` and `IS NOT NULL` always produce non-null Boolean 
results;
+`mask_to_fixed_value` also produces a non-null result for null input. 
Nullability is propagated
+by operation rather than inherited unconditionally from every child.
+
+### Validation and Failure Behavior
+
+Static validation checks complete definitions, declared parameter and result 
types, unique parameter
+names, Boolean row-filter results, exact mask result types, every `CASE` 
branch, supported operand
+shapes, allowlisted functions, function arguments, and resource limits. 
Binding and context
+validation check compatible types, literal ranges, nullability, and trusted 
context values.
+Invalid content is rejected; an input or context error must never silently 
remove a required
+restriction. The parser builds typed operation nodes without executing 
expressions. Binding
+checks every declared input before selecting branches and checks mask 
nullability across all
+result arms. Conversion and context lookups evaluate only selected `CASE` 
results and stop
+evaluating later conditions after a match.
+
+The expression limits apply to the complete stored definition, including its 
parameter list,
+return type, and SQL body:
+
+- Source length: 16 KiB of UTF-8.
+- Operation depth: 16, including nested `CASE` expressions and function calls.
+- Expression nodes: 1024.
+- Declared parameters: 256.
+- Each decoded quoted identifier or string literal: 4 KiB of UTF-8.
+- Numeric literal: 256 bytes.
+- `IN` list elements: 1000 per list and 1000 in the complete source.
+- Parenthesis nesting: 64 levels.
+
+A consecutive `AND` or `OR` chain is one n-ary operation. Parentheses alone 
add no AST nodes or
+operation depth. Literal lists and all `CASE` arms count toward the node 
limit, even when an arm
+is unreachable. These source limits are separate from the target conversion 
limits below.
+
+Limits apply before normalization. Unknown functions, unsupported types, and 
expressions outside
+this grammar are errors. This Snowflake-style expression profile supports 
conditional SQL
+expressions without adopting every Snowflake SQL feature. The first version 
rejects SQL statements,
+casts, arithmetic, undeclared column references, nested fields, subqueries, 
custom functions,
+general regular expressions, and general wildcard patterns such as `'%Acme'` 
or `'A_me'`.
+
+### Experimental Iceberg Conversion
+
+The experimental converter supports the following subset of native Iceberg 
restrictions:
+
+| SQL form                                                                     
| Iceberg conversion                                           |
+| ---------------------------------------------------------------------------- 
| ------------------------------------------------------------ |
+| Column compared with a compatible literal or bound context scalar            
| Field-ID predicate with SQL null semantics                   |
+| Boolean inputs, comparisons, `AND`, `OR`, and `NOT`                          
| Three-valued lowering; only true retains a row               |
+| `IN`, `NOT IN`, ranges, null tests, prefix `LIKE`, and NaN tests             
| Closed predicates with explicit unknown handling             |
+| Searched/simple Boolean `CASE` and scalar `CASE` inside supported predicates 
| Ordered lowering; false and unknown conditions fall through  |
+| `year(date_or_timestamp_column)`                                             
| Iceberg year transform with calendar-year literal adjustment |
+| `month` or `day` on constant temporal values                                 
| Scalar constant evaluation                                   |
+| Mask returning its local parameter                                           
| No projection; retain the original selected column           |
+| Null or `replace_with_null(value)`                                           
| Null projection on a nullable column                         |
+| One of the nine direct mask functions applied to the local parameter         
| Corresponding Iceberg projection                             |
+| Literal equal to the target type's fixed mask value                          
| Fixed-value projection                                       |
+| Mask `CASE` whose conditions resolve to constants after binding              
| First matching compatible projection                         |
+
+General column-to-column comparisons, month/day extraction from row values, 
row-dependent mask
+selection, composed mask calls, and arbitrary literal mask values are not 
representable by the
+current target and are rejected. An incompatible selected mask fails the table 
load. SQL
+authoring support does not imply that the Iceberg target supports every 
expression form.
+
+The converter tracks separate predicates for rows where a SQL expression is 
true and false;
+unknown matches neither. Negation exchanges those predicates instead of 
treating unknown as
+false. Ordered `CASE` lowering preserves this distinction for both conditions 
and results.
+Generated Iceberg expressions are limited to 16384 nodes and depth 256, with 
an additional bound
+on compilation work. Exceeding a target limit rejects the governed load.
+
+### Conformance
+
+Examples and validation cases cover every supported operator and type; SQL 
quoting and escaping;
+numeric boundaries, decimal scale, and temporal precision; null comparisons 
and nested negation;
+prefix matching; NaN tests; date parts; current-user and group binding; every 
mask function;
+expected result types; available inputs; searched and simple `CASE`; 
first-match
+evaluation; null conditions and selectors; omitted `ELSE`; nested expressions; 
incompatible branch
+types and nullability; invalid type/function pairs; and all resource limits.
+
+Input cases must verify unknown names, incompatible types, and expected result 
types. Identifier
+cases must verify unquoted case folding and quoted exact names. All expression 
forms share the
+same null, type, and resource-limit rules.
+
+Definition cases must reject missing parameter lists, missing `RETURNS`, 
duplicate names,
+undeclared body references, non-Boolean filter results, and mismatched mask 
parameter/result types.
+Mask-input cases must apply `(value STRING) RETURNS STRING -> 
show_last_4(value)` to differently
+named string columns and verify independent binding, local parameter renaming, 
inherited
+nullability, and type checks. A reference to `phone_number` or `email` in a 
mask body is invalid
+unless it is the explicitly declared parameter. Row-filter cases must resolve 
declared parameters
+against the target table schema and reject mismatched column types.
+
+Conformance also covers rejection of user attributes, NUL, and empty quoted 
identifiers;
+Unicode code-point string ordering for constants, context values, and bound 
columns;
+operation-specific mask nullability, including unselected null arms; and lazy 
context lookup
+on selected `CASE` paths. Versioned golden fixtures pin parsed ASTs and 
rejected-input diagnostics.
+Conversion cases cover the supported target subset, unsupported source-valid 
expressions,
+three-valued negation, and generated-expression and compilation-work limits.
 
 ## Resolution and Conflicts
 
@@ -512,9 +706,9 @@ A constant-true row filter is omitted. Constant false 
remains a deny-all filter.
 parsing, context, schema, type, action, or conflict error aborts the governed 
load.
 
 Canonicalization binds context and literals, normalizes comparisons with the 
field reference first,
-rewrites null comparisons, folds Boolean constants, sorts and deduplicates 
commutative children and
-`in` values, and validates the final closed Iceberg expression. 
Canonicalization is deterministic
-and idempotent.
+preserves SQL three-valued null semantics, folds Boolean constants, sorts and 
deduplicates
+commutative children and `IN` values, and validates the final closed Iceberg 
expression.
+Canonicalization is deterministic and idempotent.
 
 ## Iceberg REST Response
 
@@ -601,13 +795,13 @@ mechanism. Governed data credentials must not be issued 
when that trust check fa
 
 The server applies these outcomes:
 
-| Active restriction | `read-restrictions.v1` | Trusted reader | Result |
-| --- | --- | --- | --- |
-| No | Missing or present | Any | Return the normal response without read 
restrictions. |
-| Yes | Missing | Any | Return `406 Not Acceptable`; do not return 
unrestricted metadata. |
-| Yes | Unsupported version | Any | Return `406 Not Acceptable`; do not return 
unrestricted metadata. |
-| Yes | Present | No | Return `403 Forbidden`; the header does not establish 
trust. |
-| Yes | Present | Yes | Return `200 OK` with the resolved read restrictions. |
+| Active restriction | `read-restrictions.v1` | Trusted reader | Result        
                                                    |
+| ------------------ | ---------------------- | -------------- | 
----------------------------------------------------------------- |
+| No                 | Missing or present     | Any            | Return the 
normal response without read restrictions.             |
+| Yes                | Missing                | Any            | Return `406 
Not Acceptable`; do not return unrestricted metadata. |
+| Yes                | Unsupported version    | Any            | Return `406 
Not Acceptable`; do not return unrestricted metadata. |
+| Yes                | Present                | No             | Return `403 
Forbidden`; the header does not establish trust.      |
+| Yes                | Present                | Yes            | Return `200 
OK` with the resolved read restrictions.              |
 
 An applicable restriction that cannot be loaded, parsed, bound, canonicalized, 
serialized, or
 enforced also fails the request. No error path falls back to an unrestricted 
response.
@@ -645,7 +839,7 @@ request ID; policy details remain subject to policy-view 
authorization.
 
 1. Applicable restrictions fail closed on parse, lookup, binding, 
serialization, or enforcement
    capability errors.
-2. Only the authenticated end user is bound to `session_user()`.
+2. Only the authenticated end user is bound to `CURRENT_USER()`.
 3. Policy enforcement uses the unfiltered internal policy result and is 
independent of whether the
    subject may view policy metadata.
 4. Restriction errors and user-facing responses do not reveal policy, tag, or 
group names without
@@ -679,10 +873,10 @@ Coverage includes:
 - session user, group membership, unknown groups, and identity lookup failure;
 - common keyword boundaries, nested JSON escaping, keyword-named columns, and 
invalid identifiers;
 - exact literal typing and rejection of every implicit-conversion path;
-- unconditional filters, context and row-dependent conditions, multi-branch 
`else` chains, missing
-  final fallbacks, and deterministic conditional lowering;
-- unconditional masks, context-dependent mask branches, row-dependent 
mask-condition rejection,
-  bare-string results, invalid action names, and deterministic action 
selection;
+- direct SQL filters, context and row-dependent conditions, searched and 
simple `CASE`, omitted
+  `ELSE`, null conditions, and deterministic conditional evaluation;
+- scalar masks, context and row-dependent `CASE` branches, literals and typed 
nulls, invalid
+  result types, unsupported target expressions, and deterministic branch 
selection;
 - reserved function definitions, missing function versions, exact argument and 
result types, and
   invalid argument bindings;
 - duplicate and conflicting row-filter definitions and mask actions;
@@ -700,8 +894,8 @@ Coverage includes:
 
 ## Implementation Plan
 
-1. Add the restricted Rego parser, conditional lowering, and typed row-filter 
and column-mask
-   content; reject unsupported function content explicitly.
+1. Add the SQL expression parser, `CASE` evaluation, and typed row-filter and 
column-mask
+   validation; reject unsupported function content explicitly.
 2. Add a Gravitino-owned Iceberg read-restriction model and exact wire 
serializer.
 3. Implement policy-on-tag selection, subject binding, schema binding, 
canonicalization, and conflict
    detection.
@@ -713,6 +907,7 @@ Coverage includes:
 
 ## References
 
+- [Policy expression runtime reference](../docs/policies.md)
 - [Policy-on-tag design](policy-on-tag.md)
 - [Apache Iceberg read-restrictions 
specification](https://github.com/apache/iceberg/pull/13879)
 - [Iceberg client-capabilities 
proposal](https://github.com/apache/iceberg/pull/16394)
@@ -722,4 +917,7 @@ Coverage includes:
 - [Databricks ABAC core 
concepts](https://docs.databricks.com/aws/en/data-governance/unity-catalog/abac/core-concepts)
 - [Databricks ABAC policy 
management](https://docs.databricks.com/aws/en/data-governance/unity-catalog/abac/policies)
 - [Databricks row-filter performance and conditional UDF 
examples](https://docs.databricks.com/aws/en/data-governance/unity-catalog/abac/performance)
-- [Open Policy Agent Rego policy 
language](https://www.openpolicyagent.org/docs/policy-language)
+- [Snowflake row access policy 
expressions](https://docs.snowflake.com/en/sql-reference/sql/create-row-access-policy)
+- [Snowflake masking policy 
expressions](https://docs.snowflake.com/en/sql-reference/sql/create-masking-policy)
+- [Snowflake CASE 
expressions](https://docs.snowflake.com/en/sql-reference/functions/case)
+- [Snowflake 
CURRENT_USER](https://docs.snowflake.com/en/sql-reference/functions/current_user)

Reply via email to