OMGDB DOCS
// Querying

Schema Validation

Declarative per-collection rules — required fields, field types, foreign-id references, and unique constraints — enforced on writes and stored in the op-log.


OMGDB supports lightweight, declarative schema validation per collection. A collection can carry a spec that lists which fields are required, the expected type of named fields, foreign-_id references to other collections, and unique fields (single or compound). Rules are enforced when documents are written, and they live in the op-log as a define operation — so a packed or replayed store carries its own validation rules and remains self-describing.

Validation is deliberately narrow: the vocabulary is required / fields / refs / unique, nothing more. There is no JSON-Schema-style constraint language (no patterns, ranges, enums, or nested sub-schemas). See data model for the value types these rules reference.

Defining a collection spec

A spec is a JSON (or YAML) object describing one or more collections:

{
  "collections": {
    "users": {
      "required": ["name"],
      "fields": { "age": "long", "bio": "markdown" },
      "unique": ["email", ["tenant", "email"]]
    },
    "posts": {
      "fields": { "author": { "type": "objectId", "ref": "users" } },
      "refs": { "reviewer": "users" }
    }
  }
}
  • required — an array of field names that must be present on every document. Each entry must be a string.
  • fields — an object mapping a field name to its expected type: either a type-name string (see the type table) or an object { "type": ..., "ref": ... } combining a type with a reference.
  • refs — an object mapping a field name to the collection whose _id it must reference (a collection-name string, or { "collection": "users", "field": "_id" } — only _id is supported as the target field).
  • unique — an array of uniqueness constraints: a field-name string for a single-field constraint, or an array of field names for a compound one.

All keys are optional. A collection with none of them imposes no constraints.

You attach a spec to a store at creation time with --from:

omgdb create app.omgdb --from spec.json

The CLI reads the spec file, defines each entry in the top-level collections object on the new store, and creates a unique index for every unique entry. On success it prints what it did:

created store at app.omgdb (2 collection(s) defined, 2 unique index(es))

Note: The spec file may be JSON or YAML — a .yaml/.yml extension selects a minimal YAML subset (mappings, sequences, inline arrays, # comments; tabs and multi-line block scalars are not supported). The top level must contain a collections object; otherwise omgdb create --from fails with spec must contain a `collections` object.

Defining a collection is idempotent with respect to replay: each define is recorded as a define op in the log, and replaying the log rebuilds the validation catalog. Re-defining a collection replaces its rules. Because the rules are persisted in the log, reopening, packing, or compacting the store preserves them — the database describes its own constraints. omgdb describe renders each collection’s spec (and its indexes) as part of the live manual — see introspection.

Supported field type names

A field’s expected type is given as one of the type-name strings below. The vocabulary is checked when the spec is defined: an unknown or misspelled type fails at define time with field `age` has unknown type `lng` (expected one of: array, binData, bool, date, double, long, markdown, null, number, object, objectId, string) — instead of silently producing a spec no insert could ever satisfy.

Type nameValue kindExample value
nullJSON nullnull
boolBooleantrue
long64-bit signed integer42
double64-bit float3.14
numberAny numeric value — matches both long and double42 or 3.14
stringUTF-8 string"ada"
markdownMarkdown text — validated as a string"# Title\n\nBody"
binDataBinary data{"$binary":"..."}
arrayArray[1, 2, 3]
objectEmbedded document{"k": 1}
objectId12-byte ObjectId{"$oid":"..."}
dateTimestamp (ms since epoch){"$date": 0}

Note: A bare JSON integer such as 36 is a long, while 36.0 is a double — they are distinct types, and a field declared long rejects a value written with a decimal point. Declare number when either numeric kind is acceptable. markdown declares intent — the field holds Markdown text, as produced by Markdown import — and the type check enforces that the value is a string.

How rules are enforced

Validation runs on write, at the moment a document enters a collection that has a spec:

  • Store::insert_one validates the document before it is appended to the log. A violation returns a validation error and writes nothing.
  • Inserts inside a transaction (Txn::insert_one) are validated the same way; a violation makes the closure return an error, which aborts the whole transaction.
  • Store::replace_one (and Txn::replace_one) validate the replacement document, so an update cannot move a document into an invalid state.

The check covers:

  1. Required fields — every name in required must be present on the document. A missing one fails with missing required field `name` .
  2. Field types — for each field present on the document that also appears in fields, the value’s actual type must match the declared type. A mismatch fails with field `age` should be `long` but is `string` .
  3. References — for each field with a ref, the value must be the _id of an existing document in the referenced collection. A null value passes (an optional reference), an array is checked element by element, and a dangling reference fails with field `author` references missing `users._id` ....
  4. Uniqueness — each unique entry is enforced by a unique secondary index (single-field or compound), so a write that duplicates an existing key is rejected. See indexes for unique-index semantics.

Limitation: Type rules apply only to fields that are present. A field listed in fields but not in required is unconstrained when absent — declaring a type does not make a field mandatory. Use required for presence and fields for typing. There is no constraint on fields that appear in a document but not in the spec; extra fields are always allowed.

A passing and a failing insert

Given the users spec above (required: ["name"], fields: {"age": "long"}):

# Passes: name is present, age is a long.
omgdb insert app.omgdb users '{"name":"ada","age":36}'

# Fails: missing the required field `name`.
omgdb insert app.omgdb users '{"age":5}'

# Fails: `age` is declared `long` but the value is a string.
omgdb insert app.omgdb users '{"name":"x","age":"old"}'

A passing insert prints the assigned _id (an ObjectId, e.g. {"$oid":"..."}). A failing insert is rejected with a validation error and leaves the store unchanged.

Auditing existing documents

Specs are enforced going forward, but a collection may already hold documents — for example a spec defined after data was inserted, or documents introduced by an external producer that appended to the log. The validate command scans a collection and reports every document that does not satisfy the current rules (required fields, types, and references):

omgdb validate app.omgdb users

When every document is valid (or the collection has no rules) it prints a single success line:

valid: no violations in `users`

When there are offenders it prints one line per violating document — the document’s _id as canonical JSON, followed by the reason — and exits non-zero:

{"$oid":"6630c0..."}: missing required field `name`
{"$oid":"6630c1..."}: field `age` should be `long` but is `string`
2 document(s) violate validation rules

Each document is reported with the first rule it violates. validate is read-only: it never modifies documents or the log, so it is safe to run as an audit step in CI or before compaction.

End-to-end example

# spec.json: {"collections":{"users":{"required":["name"],"fields":{"age":"long"},"unique":["email"]}}}
omgdb create app.omgdb --from spec.json
omgdb insert app.omgdb users '{"name":"ana","age":30,"email":"[email protected]"}'
omgdb insert app.omgdb users '{"name":"bob","email":"[email protected]"}'  # rejected: duplicate email
omgdb validate app.omgdb users   # -> valid: no violations in `users`

The rules survive reopening the store: because the define op is in the log, a fresh omgdb process replays it and enforces the same constraints. To see the inferred shape of a collection alongside its declared rules and indexes, use introspection via omgdb describe.

Limitations

Limitation: Validation is intentionally narrow and covers only what is listed here.

  • The vocabulary is required (presence), fields (types), refs (foreign _id references), and unique (single/compound uniqueness). No ranges, regex/patterns, enums, length limits, or conditional rules — this is not JSON Schema.
  • Type checks apply to the top-level field’s value type. There is no coercion (a double is not accepted where a long is declared — declare number for either) and no validation of array element types or nested sub-document fields.
  • References target only the _id of the referenced collection, and referential integrity is checked on write — deleting a referenced document is not blocked retroactively.
  • Rules apply to insert and replace. They are not re-checked retroactively when a spec changes — use omgdb validate to find pre-existing offenders.

For more on the value types referenced by fields, see data model. For a hands-on walkthrough of creating a store and inserting documents, see the quickstart.

View this page as raw Markdown →