What it is
One store, the tenancy that holds it, four ways of asking about a record — and the one index that is allowed to change an answer.
A TessariDB node is a single process over a single directory — or over no directory at all, which is a store in memory and the fastest way to try anything. Inside it, data is arranged in a tenancy of three levels.
DEFINE NAMESPACE prod;
USE NAMESPACE prod;
DEFINE DATABASE orders;
USE DATABASE orders;
DEFINE TABLE users (name string, email string);
A table declares its columns and refuses a record carrying one it does not know.
Where that is not what you want — records whose shape you have not fixed — the
word is DEFINE COLLECTION, and Definitions is
where the two are set beside each other.
A namespace holds databases, a database holds tables, and a table holds
records. USE selects where the statements after it apply, and it stays in force
for the rest of the connection — a connection is a session, not a single
round trip.
A record has an identity
Records are addressed by a table and an id, written together.
CREATE users = { name: 'ada', email: 'ada@example.com' };
SELECT * FROM users:1;
The write names no id, so the table's counter supplies one and the answer says
which — 1, here, making the record's address users:1. The id is part of that
address rather than a column inside the record, which is why the read above does
not need a WHERE and does not need an index. Write the address in place of the
table when you want to choose it yourself. See
Records.
Four ways of asking about a record
| Ask about | Written as |
|---|---|
| a value | WHERE email = 'grace@example.com' |
| a connection | SELECT * FROM users:1->follows->users |
| a word inside text | WHERE body MATCHES 'lovelace' |
| a nearby vector | ORDER BY vector::euclidean(at, [0.1, 0.0]) LIMIT 2 |
Each is an ordinary expression, so they compose. A condition may hold a full-text match and an equality at once, and a traversal may end in a filter.
Four rather than one because a store that answered only the first would send you elsewhere for the other three. They are not the whole set — shapes on the sphere, a key-value space, a file bucket, a time-series, a queue, a topic and a vault are the rest, and The engines names all eleven with the question each one answers.
An index changes the cost, not the answer
DEFINE TABLE notes (body string);
DEFINE INDEX by_body ON notes FIELDS body SEARCH;
Adding that index does not change which records MATCHES 'lovelace' returns. It
changes what the question costs. That property holds for every index in the store
with exactly one exception — the vector index, which cannot promise it, and so
requires the statement to ask for approximation out loud:
DEFINE TABLE points (at array);
DEFINE INDEX by_at ON points FIELDS at VECTOR euclidean;
SELECT * FROM points ORDER BY vector::euclidean(at, [0.1, 0.0]) LIMIT 2 APPROXIMATE;
Without the word, the read is the exact scan it always was.
Definitions are statements
There is no separate schema language. DEFINE NAMESPACE, DEFINE TABLE,
DEFINE INDEX, DEFINE ANALYZER and DEFINE USER are statements like any
other, and they take IF NOT EXISTS:
DEFINE ANALYZER IF NOT EXISTS simple FILTERS lowercase;
Without it, a name that is already taken is refused rather than quietly replaced — so a migration that runs twice tells you, unless you said it was allowed to.