Skip to main content

Schema catalog

This page describes the schema catalog: the exact, continuously maintained record of which labels, properties, types and relationship shapes exist in a tenant, and the describe and propertyValues operations that read it. It is for developers building schema-aware tools (form builders, query builders, AI agents that need to know what data exists) and for anyone exploring an unfamiliar dataset. The rdb helper used in the examples is defined in the Quickstart.

An emergent schema​

RushDB has no CREATE TABLE and no schema declaration. Instead, every commit updates a catalog as a side effect of the write:

The catalog recordsFor example
Every label and how many records carry itPerson: 3, Company: 1
Every property, per type, with how many records carry itborn (number) on 3 records; a property only ever set to null has type null
Whether a property holds arrayslanguages is an array property
Properties whose name exists with several typescode as both string and number
Value dictionaries per label and propertydistinct values of status, with counts
Every relationship shape with its count and propertiesPerson -INFLUENCED-> Person: 2

Because the catalog is maintained on every commit, reading it does not scan your records. Counts are exact, never estimates.

List labels​

rdb '{"protocolVersion":1,"operation":{"kind":"describe","describe":{
"kind":"labels","query":{}}}}'
{"protocolVersion":1,"response":{"kind":"labels","labels":["Person","Company","address","department"]}}

Describe the whole schema​

describe.schema returns labels with record counts, properties and relationship shapes in one response:

rdb '{"protocolVersion":1,"operation":{"kind":"describe","describe":{
"kind":"schema","query":{},"detail":"catalog"}}}'
{
"protocolVersion": 1,
"response": {
"kind": "schema",
"schema": {
"labels": [
{ "name": "Person", "recordsCount": 3 },
{ "name": "Company", "recordsCount": 1 }
],
"properties": [
{ "id": "p…", "name": "born", "type": "number", "recordsCount": 3, "isArray": false }
],
"relationships": [
{ "sourceLabel": "Person", "targetLabel": "Person", "type": "INFLUENCED", "count": 2,
"properties": [ { "name": "via", "type": "string", "relationshipsCount": 2 } ] }
]
}
}
}

The request takes:

FieldMeaning
queryA query document (labels, predicate) that scopes the description. {} describes the whole tenant.
detail"full" (default) adds sample values and min/max per property; "catalog" returns only catalog facts and never reads records.
skip, limitPaginate the properties list.

Properties​

describe.properties returns the property list on its own:

rdb '{"protocolVersion":1,"operation":{"kind":"describe","describe":{
"kind":"properties","query":{"labels":["Person"]}}}}'

Each entry has:

FieldMeaning
idA stable property id (p followed by 16 hex digits), usable in place of the name in propertyValues. Ids are local to the tenant.
nameThe normalised property name (see Property names).
typeDisplay type: string, number, boolean or datetime.
recordsCountExact number of records carrying the property.
isArraytrue when the property holds arrays.
siblingTypesPresent when the same name also exists with other display types; lists those types by logical name (int64, float64, utf8, boolean, datetime). Integers and floats are both number, so they are never each other's sibling.
labelThe label the entry was observed on, when the response is broken down per label.
sampleValues, min, maxWith detail: "full": a bounded set of distinct values and the observed range. Unfiltered requests sample up to 10,000 records for these; counts stay exact.

A property name stored with several types appears once per display type (per label when broken down by label); integers and floats are one number entry with their combined count. A client that caches property types should treat a non-empty siblingTypes as a sign that one cached type does not cover every record.

Note: detail: "catalog" cannot be combined with a non-empty predicate; drop the predicate or use the default full detail.

Relationship shapes​

The relationships part of describe.schema aggregates edges by (sourceLabel, type, targetLabel):

FieldMeaning
sourceLabel, targetLabelLabels of the endpoints.
typeRelationship type.
countNumber of relationships with this shape.
propertiesEach property carried by relationships of this shape, with its display type and relationshipsCount (relationships of this shape that carry it).

This is the cheapest way to answer "how are my labels connected?". Use the relationships query only when you need the individual edges; see Relationships.

Narrowing with a predicate​

describe.labels, describe.properties, describe.schema and propertyValues all accept the same query document as a records query. A labels list or predicate narrows every count to the matching records:

rdb '{"protocolVersion":1,"operation":{"kind":"describe","describe":{
"kind":"schema","query":{"labels":["Person"],"predicate":{"born":{"$gte":1900}}}}}}'

Without a predicate the answer comes straight from the catalog, independent of data size. With a predicate the engine first finds the matching records, then counts labels, properties and relationship shapes over that set — a cost that grows with the number of matches and is bounded by the request's memory budget (a request that would exceed it fails with resource_exhausted rather than returning a partial count). A relationship shape is counted when at least one endpoint is in the match set. This is how faceted search UIs are built; see Facets.

Property values​

propertyValues reads the value domain of one property:

rdb '{"protocolVersion":1,"operation":{"kind":"query","query":{
"kind":"propertyValues","property":"languages","mode":"topK","limit":5,
"query":{"labels":["Person"]}}}}'
modeReturns
values (default)Distinct values; limit caps how many are shipped, truncated says whether it did.
countdistinctCount only.
rangemin and max only (numeric order for numbers, lexical order otherwise).
topKThe limit most frequent values, with record counts in counts.

distinctCount is always exact and untruncated. With an empty predicate the answer comes from the per-label value dictionaries.

Remove a property everywhere​

deleteProperty removes a property from the whole tenant: every value of it, in every type, and its catalog entries. Use it to clean up a misspelled or retired field.

rdb '{"protocolVersion":1,"operation":{"kind":"mutate","mutation":{
"kind":"deleteProperty","name":"nickname"}}}'

name is matched by its normalised form. The response reports, as affected, how many owners (records or relationships) carried the property. Naming a property that does not exist is an error.

A property name used both on records and on relationships is two separate properties; one call removes one of them. Repeat the call until it reports that the property does not exist to remove both.

Warning: deleteProperty is tenant-wide and cannot be undone. To remove a property from a single record, replace the record without it.

See also​