Ulabase

Your data

Constraints

Rules on the whole collection no write can break: no negative balance, no double booking. Written as a pipeline, checked in a transaction on every write.

A constraint is a rule about your data that a single document cannot express: no account balance below zero, no two orders holding the same seat, never more stock committed than exists. A schema validates one document at a time. A constraint is checked against the whole collection, and a write that would break it is refused.

The Constraints page: what a constraint costs

How a constraint is written

You write an aggregation pipeline that searches for trouble, then say whether finding something means the rule holds or breaks.

Field Meaning

Name

Identifies the rule, unique within the collection. It comes back in the error, so make it readable: noNegativeBalance.

Message

Returned to whoever made the write. Write it for them: an account balance cannot be negative.

The rule holds when the pipeline returns

nothing: the pipeline looks for what must not exist. something: the pipeline looks for what must exist.

Pipeline stages

The pipeline itself, a JSON array. It runs against the collection after the write, inside the same transaction.

Most rules are the nothing kind: look for the forbidden state, and the rule holds as long as the search comes back empty.

[
  { "$match": { "balance": { "$lt": 0 } } }
]

Save that as noNegativeBalance with holds when set to nothing, and no write can leave a negative balance behind.

A constraint’s pipeline takes no parameters. The rule is checked on every write, whoever made it, so there is no request to take a value from and $var and $ifvar have no effect. The editor hides Add pagination and the variables guide for the same reason.

Constraints are stored in the collection’s metadata under constraints, the same way aggregations are stored under aggrs.

Add a constraint

  1. Expand the collection.

  2. Click Add constraint.

  3. Fill in name, message, when the rule holds, and the pipeline.

  4. Click Test to try the pipeline against the data you have now (see below).

  5. Click Create.

An empty pipeline is refused: it would match everything, which is never what a rule means.

Check a rule before you save it

Test runs the rule’s pipeline against the collection as it stands and gives the answer in the rule’s terms:

  • The data you have satisfies this rule: nothing to fix.

  • These documents already break the rule: a write would now be refused because of exactly these documents.

  • The rule is not satisfied: the pipeline found nothing: for a something rule, the thing that must exist is missing.

Do this before saving. A rule saved against data that already breaks it will refuse writes that have nothing to do with the offending documents.

Turn a rule off

Each rule has a Disable button. While you investigate, disable the rule: the definition is kept, and Enable turns it back on.

What it costs, and what your app must do

A collection with at least one enabled constraint is written inside a transaction. Every write to it is checked against every enabled rule and rolled back if one is violated.

Your client code has to handle one consequence: two concurrent writes to the same collection can conflict. The one that loses is refused with 409 Conflict and is not applied. Send it again as it was.

A violated rule is also a 409, and retrying it only repeats the answer. The body tells the two apart:

Body Meaning

"retryable": true

A write conflict. Nothing was applied. Send the same request again.

"constraint": "<name>"

A rule was violated, with the offending documents in violations. Retrying will not help.

Nothing else sets retryable, so the whole check is one line:

const res = await fetch(url, { method: 'POST', body, headers });

if (res.status === 409) {
  const error = await res.json();

  if (error.retryable) {
    // lost a race: send it again
  } else {
    // error.constraint names the rule, error.violations lists the documents
  }
}

Retry with a short backoff and a few attempts. A conflict that keeps repeating means contention: look at what is writing to the collection.

  • Aggregations: the same pipeline editor, for pipelines you expose as endpoints

  • Schemas: validates one document’s shape, which a constraint does not do

  • Indexes: a unique index is the cheap rule across documents, when uniqueness is all you need