Skip to main content

Evaluating a data filter policy

To understand how SQL WHERE clauses can be derived from a partially evaluated Rego policy, it's beneficial to have a basic idea about how partial evaluation (PE) works. In this walk-through of a PE run, we'll start with a basic filter policy:

filters.rego
# METADATA
# scope: package
# custom:
# unknowns:
# - input.users
# - input.products
package filters

user := input.user

include if {
input.users.name == user
input.budget != "low"
}

include if {
input.users.name == user
input.budget == "low"
input.products.price < 500
}

include if input.products.price == "free"

In this walkthrough, we'll go through the policy in the same way the evaluator does, and use the following input:

{
"user": {
"name": "dana"
},
"budget": "low"
}

user := input.user

include if {
input.users.name == user
input.budget != "low"
}

include if {
input.users.name == user
input.budget == "low"
input.products.price < 500
}

include if input.products.price == "free"

The first include rule is evaluated.


user := input.user

include if {
input.users.name == user
input.budget != "low"
}

include if {
input.users.name == user
input.budget == "low"
input.products.price < 500
}

include if input.products.price == "free"

The expression input.users.name == user uses user, which is known, "dana". The LHS input.users.name is part of the unknowns (input.users), so the expression contributes to our conditions:

input.users.name == "dana"

Since input.user is known, it's been dereferenced.


user := input.user

include if {
input.users.name == user
input.budget != "low"
}

include if {
input.users.name == user
input.budget == "low"
input.products.price < 500
}

include if input.products.price == "free"

Our expression's LHS is known, "low", which is not different from "low". An expression with all known parts that evaluates to false makes us give up on this rule path (regardless of eventual extra expressions following), and we discard the set of conditions aggregated for this rule body.

Partial evaluation continues with the next rule body.


user := input.user

include if {
input.users.name == user
input.budget != "low"
}

include if {
input.users.name == user
input.budget == "low"
input.products.price < 500
}

include if input.products.price == "free"

Evaluating our second rule body, we again get a condition from the comparison with user, which is input.user, and known to be "dana":

input.users.name == "dana"

user := input.user

include if {
input.users.name == user
input.budget != "low"
}

include if {
input.users.name == user
input.budget == "low"
input.products.price < 500
}

include if input.products.price == "free"

The expression input.budget == "low" has only known parts, input.budget, and "low", and is indeed true. It doesn't add any condition, and lets us continue evaluating this rule body.


user := input.user

include if {
input.users.name == user
input.budget != "low"
}

include if {
input.users.name == user
input.budget == "low"
input.products.price < 500
}

include if input.products.price == "free"

The next expression, input.products.price < 500, involves a number literal and an unknown, and thus adds a condition:

input.users.name == "dana"
input.products.price < 500

There are no further expressions in this rule body, so the conditions are saved, and partial evaluation proceeds to the next rule body.


user := input.user

include if {
input.users.name == user
input.budget != "low"
}

include if {
input.users.name == user
input.budget == "low"
input.products.price < 500
}

include if input.products.price == "free"

As with every new rule body, the set of conditions is reset.

This expression includes one unknown and one literal, so it adds a condition to our set:

input.products.price == "free"

There are no further expressions, so this condition also contributes to our PE result.


We're now done with the partial evaluation of our data.filters.include rule with the given (known) inputs. It has yielded two sets of conditions, A and B, which form the basis of translation into SQL queries.

A (Rego)
input.users.name == "dana"
input.products.price < 500
B (Rego)
input.products.price == "free"

When translating, each of the sets is translated into SQL expressions:

A (SQL)
users.name = "dana" AND products.price < 500
B (SQL)
products.price = "free"

Finally, the two are combined with OR:

A OR B
(users.name = "dana" AND products.price < 500) OR products.price = "free"

Next Steps