compile: add support for "any value at all", as IS NOT NULL (#7998)

* compile: support "equal to whatever" as "IS NOT NULL"

Fixes https://github.com/open-policy-agent/opa/issues/7996.

This only deals with SQL targets for starters.

This issue also came out of the discussion with @huntkalio.

* compile: expand support for "_ = <unknown>" to prisma

It seems like the translation is pretty straightforward here, so
let's include it.

Reference: https://www.prisma.io/docs/orm/prisma-client/queries/filtering-and-sorting#filter-for-non-null-fields

* docs/compile: mention fragment addition

Signed-off-by: Stephan Renatus <stephan.renatus@gmail.com>
This commit is contained in:
Stephan Renatus
2025-10-29 13:38:28 +01:00
committed by GitHub
parent 882b28738c
commit 9a864c6398
6 changed files with 95 additions and 6 deletions
+25
View File
@@ -99,6 +99,31 @@ include if input.fruits.name != input.fruits.colour
SQL target: `WHERE name <> colour`
:::
:::info "Is Anything"
For SQL and UCAST/Prisma, it's valid to assert that a field exists by unifying it with a wildcard:
```rego
package filters
include if input.fruits.price = _
```
SQL target: `WHERE name IS NOT NULL`
A more common way to do this would be function definition shorthands, like
```rego
package filters
include if {
some pat in data.filter.patterns
matches(pat, input.fruit.name)
}
matches("*", _) # "*" matches everything
matches(x, x) # exact match
```
Here, the first `matches` definition would yield an expression like `_ = input.fruit.name` in the partial evaluation results.
:::
## Built-in Functions
+12
View File
@@ -392,6 +392,18 @@ func TestCompileHappyPathE2E(t *testing.T) {
expRows: []fruitRow{apple, banana, cherry},
prisma: true,
},
{
name: "exists",
policy: `include if input.fruits.price = _`,
expRows: []fruitRow{apple, banana, cherry},
prisma: true,
},
{
name: "exists (with var)",
policy: `include if { some v; v = input.fruits.price }`,
expRows: []fruitRow{apple, banana, cherry},
prisma: true,
},
{
name: "simple startswith",
policy: `include if startswith(input.fruits.name, "app")`,
+15 -3
View File
@@ -189,13 +189,25 @@ func checkBuiltins(c *checker, e *ast.Expr, _ []*ast.Module) *ast.Error {
}
default: // lhs or rhs needs to be ground scalar, or, if twoRefsOK is true, unknown input refs
// TODO(sr): collections might work, too, let's fix this later
found := false
for i := range 2 {
if ast.IsScalar(e.Operand(i).Value) {
found = true
return nil
}
}
if !found && !(twoRefsOK && unknownRefs == 2) { // nolint:staticcheck
if op0 == ast.Equality.Name && unknownRefs == 1 { // one unknown, the other side non-scalar
for i := range 2 {
switch e.Operand(i).Value.(type) {
case ast.Var, ast.Ref: // OK
if err0 := c.constraints.AssertFeature("existence-ref"); err0 != nil {
return err(loc, "existence of field: %s", err0.Error())
}
default:
return err(loc, "both rhs and lhs non-scalar/non-ground")
}
}
return nil
}
if !twoRefsOK || unknownRefs != 2 { // nolint:staticcheck
return err(loc, "both rhs and lhs non-scalar/non-ground")
}
}
+2 -2
View File
@@ -79,7 +79,7 @@ func NewConstraints(typ, variant string) (*Constraint, error) {
default:
return nil, fmt.Errorf("unsupported variant for %s: %s", typ, variant)
}
c.Features.Add("not", "field-ref")
c.Features.Add("not", "field-ref", "existence-ref")
case "ucast":
switch v := strings.ToLower(variant); v {
case "all":
@@ -88,7 +88,7 @@ func NewConstraints(typ, variant string) (*Constraint, error) {
c.Builtins = allBuiltins
case "prisma":
c.Variant = v
c.Features.Add("not")
c.Features.Add("not", "existence-ref")
c.Builtins = allBuiltins
case "linq":
c.Variant = "LINQ" // normalize spelling
+6
View File
@@ -107,6 +107,12 @@ func toFieldNode(op string, r ast.Ref, v ast.Value, opts *Opts, refOK bool) *uca
Field: f,
}
}
case ast.Var:
if op == ast.Equality.Name { // _ = <unknown>
op = "ne" // this will end up as `unknown IS NOT NULL`
} else { // we used to run into "var needs evaluation" below, let's just return nil here
return nil
}
default:
var err error
value, err = ast.ValueToInterface(v, nil)
+35 -1
View File
@@ -57,7 +57,7 @@ func TestPostPartialChecks(t *testing.T) {
errors []Error
mappings map[string]any
skip string
result map[string]any
result any
}{
{
note: "happy path",
@@ -552,6 +552,40 @@ include if user == input.fruits.user`,
},
},
},
{ // NOTE(sr): only supported for SQL-ish targets and prisma (see below)
note: "equality with var",
rego: `include if input.fruits.colour = _`,
target: "application/vnd.opa.ucast.linq+json",
errors: []Error{
{
Code: "pe_fragment_error",
Message: `existence of field: unsupported feature "existence-ref" for UCAST (LINQ)`,
},
},
},
{
note: "equality with var (sql)",
rego: `include if input.fruits.colour = _`,
target: "application/vnd.opa.sql.mysql+json",
result: "WHERE fruits.colour IS NOT NULL",
},
{
note: "equality with var (sql), reversed",
rego: `include if _ = input.fruits.colour`,
target: "application/vnd.opa.sql.mysql+json",
result: "WHERE fruits.colour IS NOT NULL",
},
{
note: "equality with var (prisma)",
rego: `include if input.fruits.colour = _`,
target: "application/vnd.opa.ucast.prisma+json",
result: map[string]any{
"type": "field",
"field": "fruits.colour",
"operator": "ne",
"value": nil,
},
},
{
note: "not a call/term",
rego: `include if input.fruits.colour`,