> ## Documentation Index
> Fetch the complete documentation index at: https://www.osohq.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Polar Operators

Polar supports several operators used to construct rule bodies. These operators define how expressions are evaluated during rule execution.

## Unification (`=`)

Unification is Polar's core matching operation. Two values **unify** if they are equal or if variables can be consistently bound to make them equal. Lists unify if all corresponding elements unify.

The unification operator (`=`) checks if its left and right operands unify; for example, `"a" = "a"`, `x = "a"`, or `["a", "b"] = [x, "b"]` where the variable `x` is either bound to `"a"` or unbound.

Note that in the case of `String` values, unification is case sensitive, e.g.:

* `"x"` does not unify with `"X"`
* `User{"Alice"}` does not unify with `User{"alice"}`

## Conjunction (`and`)

`and` requires both conditions in a rule body to be true.

```polar theme={null}
oso_employee(first, last) if
  is_user(first, last) and
  is_employee("Oso", first, last);
```

## Disjunction (`or`)

`or` succeeds if either the left or right operand is true. Any `or` expression can be rewritten as multiple rules with the same head but different bodies.

```polar theme={null}
is_user(first, last) if
  oso_employee(first, last) or
  is_guest(first, last);

# The `or` can be rewritten as a pair of rules:
is_user(first, last) if oso_employee(first, last);
is_user(first, last) if is_guest(first, last);
```

## Negation (`not`)

`not` checks that a specific fact does **not** exist.

This would look like below:

For example, you might use it to say that a user should only be allowed to perform an action if they are *not* banned, and that the policy grants them that permission:

```polar theme={null}
allow(user, action, resource) if
  not is_banned(user) and
  has_permission(user, action, resource);
```

Negation rules:

* You can negate only a **single fact**, not a compound expression (like one using `and` or `or`), or a fact that refers to another policy rule.
* Variables inside a negated fact must also appear in a non-negated fact in the same rule. This ensures Polar knows the set of possible values before evaluating the negation.

These restrictions allow Polar to compile negation into efficient `NOT EXISTS` SQL subqueries.

Polar supports a wide range of patterns using negation, including common use cases like:

* Checking that a user meets **all** required conditions (e.g., has every required role)
* Allowing access from a parent resource unless explicitly overridden

## List Membership (`in`)

`in` iterates over a list of strings.

```polar theme={null}
x in ["a", "b", "c"]
```

`x` is unified with each list element in sequence. The right operand must be a list of strings.

In the following example, the variable `x` will be bound to `"a"`, `"b"`, and `"c"`, in turn, and then the `x = "a"` check will evaluate. This expression will only succeed for the first item in the list, `"a"`.

```polar theme={null}
x in ["a", "b", "c"] and x = "a"
```

`x` is unified with each list element in sequence. The right operand must be a list of strings.

This succeeds only for the first element, `"a"`.

The left operand can be a value instead of a variable:

```polar theme={null}
"a" in ["a", "b", "c", "a"]
```

This succeeds twice, once for the first element and once for the fourth.

## Integer Comparisons

`<`, `<=`, `>`, and `>=` compare integer values.

If a fact stores a Unix timestamp:

```polar theme={null}
expires_at(File{"foo"}, 1670280790)
```

You can write:

```polar theme={null}
expires_after_y2k38(resource) if  
  expires_at(resource, time) and 
  time > 2147483647;
```

## Inequality (`!=`)

The inequality operator (`!=`) checks that two values are not equal.

```polar theme={null}
has_permission(user: User, "edit", resource: Resource) if
  has_role(user, "editor", resource) and
  owner matches User and
  has_role(owner, "owner", resource) and
  user != owner;
```

This rule grants edit permission to editors, but only when they are not the owner of the resource.

<Note>
  The `!=` operator is not supported in [local authorization](/develop/facts/local-authorization). Use centralized authorization for policies that require inequality comparisons.
</Note>

## `matches` Operator

`matches` asserts that a variable is of a specific type.

```polar theme={null}
<variable> matches <Type>
```

Typically used when introducing a new variable, such as when joining two rules.

```polar theme={null}
has_role(user: User, role: String, resource: Resource) if
  group matches Group and
  has_group(user, group) and
  has_role(group, role, resource);
```

This binds `group` to a `Group` type and requires both `has_group` and `has_role` to succeed with the same `Group` value.

## Operator Precedence

Polar operators are evaluated from highest precedence to lowest as follows:

1. `in, matches`
2. `=, <, <=, >, >=`
3. `not`
4. `and`
5. `or`

For example, the following assertion passes:

```polar theme={null}
actor User{}

e(user: User) if
  a(user) or
  b(user) and
  c(user) or
  d(user);

test "parent-child permissions" {
  setup {
    a(User{"alice"});
  }

  assert e(User{"alice"});
}
```

Oso evaluates the conditions as follows:

```polar theme={null}
actor User{}

e(user: User) if
  a(user) or # <-- true
  b(user) and c(user) or # <-- false
  d(user);            # <-- false

test "parent-child permissions" {
  setup {
    a(User{"alice"});
  }

  assert e(User{"alice"});
}
```

That is, `b(user) and c(user)` is evaluated first. It returns `false`, but because `a(user)` is `true`, the entire condition evaluates to `true or false or false`, which returns `true`.

Use `()` to explicitly control evaluation order.

For instance, in the example above, if you instead wanted to state that `e` is true only if at least one of `a` and `b` is `true`, and at least one of `c` and `d` is true, you could rewrite the rule as follows:

```polar theme={null}
e(user: User) if  
  (a(user) or b(user))  # <-- true  
  and  
  (c(user) or d(user)); # <-- false  
```

This evaluates the `or` expressions before applying `and`.
