Masking policy: MASK_PARTIAL function implementation mismatch with design spec
- Dominant language
- Go
- Stars
- 40.5k
- Forks
- 6.2k
- PR merge metrics
- PR metrics pending
Description
## Issue Description
The `MASK_PARTIAL` function implementation in the `demo` branch (commit 7b061d9918) does not match the design specification in `docs/design/2026-02-27-column-level-masking.md`.
## Current Implementation (demo branch)
The code in `pkg/expression/builtin_masking.go` implements:
- Function signature: `MASK_PARTIAL(col, mask_char, start, length)`
- `start` = zero-based position where masking begins
- `length` = number of characters to mask
Test usage: `MASK_PARTIAL('alpha', '*', 1, 2)` → `'a**ha'`
- Preserves position 0: `'a'`
- Masks positions 1-2: `'**'`
- Preserves positions 3-4: `'ha'`
## Design Specification
The design document specifies:
```
MASK_PARTIAL(col, preserve_left, preserve_right, mask_char)
```
Expected behavior for `MASK_PARTIAL('alpha', '*', 1, 2)`:
- preserve_left=1: Keep first 1 character `'a'`
- preserve_right=2: Keep last 2 characters `'ha'`
- Mask middle (5-1-2=2): `'**'`
- Result: `'a**ha'`
## Expected Behavior (per design spec)
| Function Call | Result | Explanation |
|--------------|--------|-------------|
| `MASK_PARTIAL('4532111111111111', '*', 4, 4)` | `'4532********1111'` | Show first 4, mask middle 8, show last 4 |
| `MASK_PARTIAL('1234567890', '*', 3, 4)` | `'123***7890'` | Show first 3, mask middle 3, show last 4 |
| `MASK_PARTIAL('123456789', '*', 3, 4)` | `'123**6789'` | Show first 3, mask middle 2, show last 4 |
## Correct Implementation
The `origin/release-8.5-20260312-v8.5.5` branch has the correct implementation that follows the design specification.
## Impact
The current `demo` branch implementation makes it difficult to use `MASK_PARTIAL` for common masking patterns:
- Credit card masking `MASK_PARTIAL(cc, '*', 4, 4)` requires knowing the exact card length and calculating `start`/`length`
- The design spec semantics `MASK_PARTIAL(cc, '*', 4, 4)` (show first 4, show last 4) is more intuitive
## Suggested Fix
1. Update `pkg/expression/builtin_masking.go` to implement the design spec semantics: `MASK_PARTIAL(col, mask_char, preserve_left, preserve_right)`
2. Update integration tests in `tests/integrationtest/t/privilege/column_masking_policy.test`
3. Update expected results in `tests/integrationtest/r/privilege/column_masking_policy.result`
## References
- Design doc: `docs/design/2026-02-27-column-level-masking.md` (line 166)
- Current implementation: `pkg/expression/builtin_masking.go` (line 333-395)
- System table constraint: `UNIQUE KEY uk_table_policy (table_id, policy_name)` (table-scoped policy names)
**Note**: There may also be a related issue with policy name uniqueness being checked at database scope instead of table scope in the `demo` branch. The system table constraint `uk_table_policy (table_id, policy_name)` indicates policy names should be unique per table, not per database.
Contributor guide
Assessment
This issue has not been assessed yet.