PageSourceSearch

https://www.openpolicyagent.org/assets/js/b1fc8bf6.864b576e.js

js openpolicyagent.org collected 2026-09-24 08:28:52 UTC 15,685 bytes, 1 lines download raw bytes

1"use strict";(self.webpackChunkopa_website=self.webpackChunkopa_website||[]).push([[10703],{11197:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>o,contentTitle:()=>l,default:()=>u,frontMatter:()=>a,metadata:()=>t,toc:()=>c});const t=JSON.parse('{"id":"filtering/fragment","title":"Writing valid Data Filtering Policies","description":"To be able to use Rego for data filtering, the policy needs to be constructed to","source":"@site/docs/filtering/fragment.md","sourceDirName":"filtering","slug":"/filtering/fragment","permalink":"/docs/filtering/fragment","draft":false,"unlisted":false,"tags":[],"version":"current","sidebarPosition":3,"frontMatter":{"title":"Writing valid Data Filtering Policies","sidebar_position":3},"sidebar":"docsSidebar","previous":{"title":"Evaluating a Data Filter Policy","permalink":"/docs/filtering/partial-evaluation"},"next":{"title":"Writing Column Masking Policies","permalink":"/docs/filtering/column-masks"}}');var s=i(74848),r=i(28453);const a={title:"Writing valid Data Filtering Policies",sidebar_position:3},l=void 0,o={},c=[{value:"What is Partial Evaluation?",id:"what-is-partial-evaluation",level:2},{value:"Example Preamble",id:"example-preamble",level:2},{value:"Context data for Partial Evaluation",id:"context-data-for-partial-evaluation",level:2},{value:"Unknowns: database rows",id:"unknowns-database-rows",level:3},{value:"Known values: request context",id:"known-values-request-context",level:3},{value:"Simple comparisons",id:"simple-comparisons",level:2},{value:"Built-in Functions",id:"built-in-functions",level:2},{value:"Rules and functions",id:"rules-and-functions",level:2},{value:"<code>not</code> expressions",id:"not-expressions",level:2}];function d(e){const n={a:"a",admonition:"admonition",blockquote:"blockquote",code:"code",em:"em",h2:"h2",h3:"h3",li:"li",mermaid:"mermaid",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,r.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.p,{children:"To be able to use Rego for data filtering, the policy needs to be constructed to"}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsx)(n.li,{children:"correctly represent the desired row filtering logic"}),"\n",(0,s.jsxs)(n.li,{children:["properly be ",(0,s.jsx)(n.strong,{children:"translatable into the target representation"})," (such as SQL)"]}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["The subset of Rego that can be used to represent row filtering logic is colloquially referred to as the ",(0,s.jsx)(n.strong,{children:"fragment"}),"."]}),"\n",(0,s.jsxs)(n.admonition,{title:"What you will learn",type:"info",children:[(0,s.jsx)(n.p,{children:"You will develop an intuition for what is valid Rego for data filters depending on the target.\nNot every construct is supported for every target."}),(0,s.jsxs)(n.p,{children:["For a step-by-step walkthrough of evaluating a Rego policy ",(0,s.jsx)(n.em,{children:"partially"}),", see ",(0,s.jsx)(n.a,{href:"./partial-evaluation",children:"Evaluating a data filter policy"}),"."]})]}),"\n",(0,s.jsx)(n.h2,{id:"what-is-partial-evaluation",children:"What is Partial Evaluation?"}),"\n",(0,s.jsxs)(n.p,{children:["The translation of data policies into queries (like SQL WHERE clauses) is driven by ",(0,s.jsx)(n.em,{children:"partial evaluation (PE)"})," of a Rego query."]}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsxs)(n.p,{children:["With partial evaluation, callers specify that certain inputs or pieces of data are ",(0,s.jsx)(n.em,{children:"unknown"}),". OPA evaluates as much of the policy as possible without touching parts that depend on unknown values.",(0,s.jsx)("sup",{children:(0,s.jsx)(n.a,{href:"/blog/partial-evaluation-162750eaf422",children:"1"})})]}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["The ",(0,s.jsx)(n.em,{children:"unknown"})," values that remain during partial evaluation represent the pieces of data that represent your filter."]}),"\n",(0,s.jsx)(n.admonition,{type:"tip",children:(0,s.jsxs)(n.p,{children:["When only ",(0,s.jsx)(n.em,{children:"known"})," values are used, ",(0,s.jsx)(n.strong,{children:"you can use all of Rego."})]})}),"\n",(0,s.jsx)(n.h2,{id:"example-preamble",children:"Example Preamble"}),"\n",(0,s.jsxs)(n.p,{children:["In this running example, assume a table ",(0,s.jsx)(n.code,{children:"fruits"})," exists with columns ",(0,s.jsx)(n.code,{children:"name"}),", ",(0,s.jsx)(n.code,{children:"colour"}),", and ",(0,s.jsx)(n.code,{children:"price"}),"."]}),"\n",(0,s.jsx)(n.mermaid,{value:"erDiagram\n    fruits {\n        str
1ing name\n        string colour\n        int price\n    }"}),"\n",(0,s.jsx)(n.h2,{id:"context-data-for-partial-evaluation",children:"Context data for Partial Evaluation"}),"\n",(0,s.jsx)(n.h3,{id:"unknowns-database-rows",children:"Unknowns: database rows"}),"\n",(0,s.jsxs)(n.p,{children:["Database rows are ",(0,s.jsx)(n.strong,{children:"unknown"})," at policy evaluation time \u2014 OPA does not have access to the database. They are represented in Rego using the convention ",(0,s.jsx)(n.code,{children:"input.<TABLE>.<COLUMN>"}),", e.g. ",(0,s.jsx)(n.code,{children:"input.fruits.name"})," refers to the ",(0,s.jsx)(n.code,{children:"name"})," column of the ",(0,s.jsx)(n.code,{children:"fruits"})," table."]}),"\n",(0,s.jsxs)(n.p,{children:["The ",(0,s.jsx)(n.strong,{children:"METADATA annotation"})," on the policy package declares which ",(0,s.jsx)(n.code,{children:"input"})," paths are unknown. OPA uses this to know which parts of the policy to leave as conditions rather than evaluate:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rego",metastring:'title="policy.rego"',children:'# METADATA\n# scope: package\n# compile:\n#   unknowns: [input.fruits]\npackage filters\n\ninclude if input.fruits.name == "banana"\n'})}),"\n",(0,s.jsxs)(n.p,{children:["With ",(0,s.jsx)(n.code,{children:"input.fruits"})," declared as unknown, OPA will not try to resolve ",(0,s.jsx)(n.code,{children:"input.fruits.name"})," during partial evaluation \u2014 instead it becomes a column reference in the output SQL."]}),"\n",(0,s.jsx)(n.h3,{id:"known-values-request-context",children:"Known values: request context"}),"\n",(0,s.jsxs)(n.p,{children:["Our data filters also depend on user information. These ",(0,s.jsx)(n.strong,{children:"known values"})," are sent to OPA as input at query time and will be substituted during partial evaluation:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-json",children:'{\n  "name": "april",\n  "email": "[email protected]",\n  "fav_colour": "yellow",\n  "budget": 10\n}\n'})}),"\n",(0,s.jsxs)(n.p,{children:["They are referenced in the policy as ",(0,s.jsx)(n.code,{children:"input.user"}),", e.g. ",(0,s.jsx)(n.code,{children:"input.user.budget"}),". Because they are not listed in ",(0,s.jsx)(n.code,{children:"unknowns"}),", OPA resolves them to their concrete values during partial evaluation."]}),"\n",(0,s.jsx)(n.h2,{id:"simple-comparisons",children:"Simple comparisons"}),"\n",(0,s.jsxs)(n.p,{children:["The fragment supports simple comparisons, such as ",(0,s.jsx)(n.code,{children:"=="}),", ",(0,s.jsx)(n.code,{children:"!="}),", ",(0,s.jsx)(n.code,{children:"<"}),", ",(0,s.jsx)(n.code,{children:">"}),", ",(0,s.jsx)(n.code,{children:"<="}),", ",(0,s.jsx)(n.code,{children:">="}),", between ",(0,s.jsx)(n.em,{children:"unknown"})," and ",(0,s.jsx)(n.em,{children:"known"})," values.\nIt is not important if the ",(0,s.jsx)(n.em,{children:"unknown"}),' is on the left-hand side ("LHS") or right-hand side ("RHS"), but it is critical that only one side is ',(0,s.jsx)(n.em,{children:"unknown"}),":"]}),"\n",(0,s.jsxs)(n.admonition,{title:"OK",type:"tip",children:[(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rego",children:'package filters\n\ninclude if {\n    input.fruits.name == "banana"                # LHS unknown, RHS constant\n    input.fruits.price <= input.user.budget      # LHS unknown, RHS known.\n    input.user.fav_colour == input.fruits.colour # LHS known, RHS unknown.\n}\n'})}),(0,s.jsxs)(n.p,{children:["SQL target: ",(0,s.jsx)(n.code,{children:"WHERE name = 'banana' AND price <= 10 AND colour = 'yellow'"})]}),(0,s.jsxs)(n.p,{children:["As you can see the ",(0,s.jsx)(n.em,{children:"known"})," values from ",(0,s.jsx)(n.code,{children:"input.user"})," have been replaced."]})]}),"\n",(0,s.jsx)(n.admonition,{title:"NOT OK",type:"danger",children:(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rego",children:"package filters\n\ninclude if {\n    input.fruits.name != input.fruits.colour # LHS and RHS unknown\n    input.fruits.price                       # plain unknown\n}\n"})})}),"\n",(0,s.jsxs)(n.admonition,{title:"SQL",type:"info",children:[(0,s.jsx)(n.p,{children:"For SQL translation targets, it's possible to have unknowns on both sides of the simple c
1omparisons."}),(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rego",children:"package filters\n\ninclude if input.fruits.name != input.fruits.colour\n"})}),(0,s.jsxs)(n.p,{children:["SQL target: ",(0,s.jsx)(n.code,{children:"WHERE name <> colour"})]})]}),"\n",(0,s.jsxs)(n.admonition,{title:'"Is Anything"',type:"info",children:[(0,s.jsx)(n.p,{children:"For SQL and UCAST/Prisma, it's valid to assert that a field exists by unifying it with a wildcard:"}),(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rego",children:"package filters\n\ninclude if input.fruits.price = _\n"})}),(0,s.jsxs)(n.p,{children:["SQL target: ",(0,s.jsx)(n.code,{children:"WHERE name IS NOT NULL"})]}),(0,s.jsx)(n.p,{children:"A more common way to do this would be function definition shorthands, like"}),(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rego",children:'package filters\n\ninclude if {\n    some pat in data.filter.patterns\n    matches(pat, input.fruit.name)\n}\n\nmatches("*", _) # "*" matches everything\nmatches(x, x)   # exact match\n'})}),(0,s.jsxs)(n.p,{children:["Here, the first ",(0,s.jsx)(n.code,{children:"matches"})," definition would yield an expression like ",(0,s.jsx)(n.code,{children:"_ = input.fruit.name"})," in the partial evaluation results."]})]}),"\n",(0,s.jsx)(n.h2,{id:"built-in-functions",children:"Built-in Functions"}),"\n",(0,s.jsx)(n.p,{children:"Certain built-in functions can be translated with certain restrictions:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.code,{children:"startswith"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.code,{children:"endswith"})}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.code,{children:"contains"})}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"k in ..."})," (not ",(0,s.jsx)(n.code,{children:"k, v in ..."}),")"]}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["These built-in functions can only be used with ",(0,s.jsx)(n.em,{children:"unknowns"})," on the left-hand side."]}),"\n",(0,s.jsxs)(n.admonition,{title:"OK",type:"tip",children:[(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rego",children:'package filters\n\ninclude if {\n    startswith(input.fruits.name, "ba")\n    input.fruits.colour in {"blue", "green"}\n}\n'})}),(0,s.jsxs)(n.p,{children:["SQL target: ",(0,s.jsx)(n.code,{children:"WHERE name LIKE 'ba%' AND colour IN ('blue', 'green')"}),"."]})]}),"\n",(0,s.jsx)(n.admonition,{title:"NOT OK",type:"danger",children:(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rego",children:'package filters\n\ninclude if {\n    endswith("apple", input.fruits.name)        # RHS unknown\n    1, input.fruits.colour in ["blue", "green"] # k, v in ...\n    regexp.match(input.fruits.name, \'^b[an]+$\') # unsupported builtin (for unknown values)\n}\n'})})}),"\n",(0,s.jsxs)(n.p,{children:["Other built-in functions are not supported ",(0,s.jsxs)(n.strong,{children:["for usage with ",(0,s.jsx)(n.em,{children:"unknown"})," values"]}),".\nIf your filtering rules use other built-ins with ",(0,s.jsx)(n.em,{children:"known values"}),", that's OK -- see below for an example."]}),"\n",(0,s.jsx)(n.h2,{id:"rules-and-functions",children:"Rules and functions"}),"\n",(0,s.jsx)(n.p,{children:"Many Rego constructs are available for building filters, with certain restrictions:"}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"every"})," may not be used with ",(0,s.jsx)(n.em,{children:"unknown"})," values"]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.code,{children:"default"})," rules (or functions) may not be used in combination with ",(0,s.jsx)(n.em,{children:"unknown"})," values"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Nonetheless, you can use rules and functions to structure your policy, as long as these restrictions are observed:"}),"\n",(0,s.jsxs)(n.admonition,{title:"OK",type:"tip",children:[(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rego",children:'package filters\n\ninclude if user_in_corp\n\ninclude if {\n    not user_in_corp\n    apple_ish\n}\n\n# apple_ish rule does not use `default` or `every`\napple_ish if input.fruits.name == "pineapple"\napple_ish if input.fruits.name == "apple"\n\n# user_in_corp only uses known values\ndefault user_in_corp := false\nuser_in_corp if endswith(lower(input.user.email), "@corp.com")\n'})}),(0,s.jsxs)(n.p,{children:["SQL target: ",(0,s.jsx)(n.code,{children:"WHERE name = 'pineapple'"})," if the user's email is not ending in ",(0,s.jsx)(n.code,{children:"@corp.com"}),". If it does, the filter would be empty, not restricting the database query."]})]}),"\n",(0,s.jsx)(n.admonition,{title:"NOT OK",type:"danger",children:(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rego",children:'package filters\n\ninclude if only_pineapples\n\ndefault only_pineapples := false                    # default rule\nonly_pineapples if input.fruits.name == "pineapple"\n'})})}),"\n",(0,s.jsxs)(n.h2,{id:"not-expressions",children:[(0,s.jsx)(n.code,{children:"not"})," expressions"]}),"\n",(0,s.jsxs)(n.p,{children:["Expressions using ",(0,s.jsx)(n.code,{children:"not"})," are permitted for ",(0,s.jsx)(n.a,{href:"#simple-comparisons",children:"simple expressions"})," and ",(0,s.jsx)(n.a,{href:"#built-in-functions",children:"built-in functions"}),".\n",(0,s.jsx)(n.code,{children:"not"})," combined with a ",(0,s.jsx)(n.em,{children:"unknown"})," value or a rule reference is not allowed."]}),"\n",(0,s.jsxs)(n.admonition,{title:"OK",type:"tip",children:[(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rego",children:'package filters\n\ninclude if {\n    not input.fruits.name == "apple"\n    not input.fruits.colour in {"blue", "green"}\n}\n'})}),(0,s.jsxs)(n.p,{children:["SQL target: ",(0,s.jsx)(n.code,{children:"WHERE (NOT name = 'apple' AND NOT colour IN ('blue', 'green'))"}),"."]})]}),"\n",(0,s.jsx)(n.admonition,{title:"NOT OK",type:"danger",children:(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-rego",children:'package filters\n\ninclude if not input.fruits.name                    # plain unknown\n\ninclude if not apple_ish                            # not + rule\napple_ish if endswith(input.fruits.name, "apple")\napple_ish if startswith(input.fruits.name, "apple")\n'})})})]})}function u(e={}){const{wrapper:n}={...(0,r.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(d,{...e})}):d(e)}},28453:(e,n,i)=>{i.d(n,{R:()=>a,x:()=>l});var t=i(96540);const s={},r=t.createContext(s);function a(e){const n=t.useContext(r);return t.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function l(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:a(e.components),t.createElement(r.Provider,{value:n},e.children)}}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.