TessariDBDocs
  • 0.34.2-betalatest
  • 0.34.1-beta
  • 0.34.0-beta
  • 0.33.4-beta
  • 0.33.3-beta
  • 0.33.2-beta
  • 0.33.1-beta
  • 0.33.0-beta
  • 0.32.0-beta

Documents

A record payload is a tree, and a route is how you name something inside it.

A record's payload is a tree. Objects nest, arrays hold elements, and records in one table need not share a shape — which is the point of holding documents at all.

DEFINE COLLECTION people;
CREATE people = { name: 'ada', address: { city: 'Paris', zip: '75001' }, tags: ['urgent', 'old'] };
CREATE people = { name: 'grace', address: { city: 'Lyon' } };
CREATE people = { name: 'alan', address: 'Paris' };
CREATE people = { name: 'edsger' };

None of those writes names an id, so the collection's counter supplies one per record in the order they were written — people:1 through people:4 — and each write answers with the number it used.

Note people:3, whose address is a string where the others hold an object. Nothing prevents that, and the rules below say exactly what happens when a question reaches it.

Routes

A route names something inside the tree.

SELECT * FROM people WHERE address.city = 'Paris';
SELECT * FROM people WHERE tags[0] = 'urgent';

A position is a position, not a membership test — tags[1] = 'old' asks about the second element and nothing else.

A route that reaches nothing is not an error

Four ways to reach nothing, all of which answer with no rows rather than failing:

SELECT * FROM people WHERE postcode = 'x';              -- no such root
SELECT * FROM people WHERE address.city.first = 'x';    -- through a non-object
SELECT * FROM people WHERE address[0] = 'x';            -- an object by position
SELECT * FROM people WHERE tags[9] = 'old';             -- past the end

This matters more than it looks. In a store where shapes differ by design, a question that failed whenever one record disagreed would be unanswerable in practice.

A position is a whole number, though — tags[-1] is UnexpectedToken, refused where it is written rather than quietly meaning "the last one".

Routes can be indexed

DEFINE INDEX by_home_city ON people FIELDS address.city;
SELECT * FROM people WHERE address.city = 'Paris';

The same record the scan answered. A prefix pattern becomes a range over the same index:

SELECT * FROM people WHERE address.city LIKE 'Par%';

An index on a route is not an index on what the route starts from — WHERE address = 'Paris' still finds people:3, whose address is a string, and does it by scan.

A composite index may mix a route and a plain field:

DEFINE INDEX by_city_and_name ON people FIELDS address.city, name;

Projecting

A read may ask for part of a record:

SELECT name FROM people;
SELECT address.city FROM people;

A projected route answers under the last step of its name — city, above.

A position has no such name, so it needs one:

SELECT address.city AS home, tags[0] AS first_tag FROM people:1;

Without it, SELECT tags[0] FROM people is refused — UnnamedProjection. And two projections answering under one name are refused too:

SELECT address.city, work.city FROM people;

DuplicateProjection. Both would arrive as city and one would win silently.

A projection does not change the access path: SELECT name FROM people WHERE address.city = 'Paris' still uses the index.

[*] — every element, not one of them

A route holding [*] denotes the values it reaches rather than a value. Two contexts have a rule for several.

A comparison holds when any element satisfies it:

SELECT * FROM people WHERE tags[*] = 'urgent';
SELECT * FROM people WHERE tags[*] = 'old';

people:1 both times — its first element answers the first question and its second answers the second, which is the whole of what any means. No element matching is no match rather than a failure. And negating any means none:

SELECT * FROM people WHERE NOT tags[*] = 'urgent';

Three records — including people:4, which has no tags at all.

A projection answers with all of them:

SELECT tags[*] AS all_tags FROM people;

Every record answers, because zero elements is an empty array rather than a missing row.

Where several has no meaning, it is refused

SELECT array::len(tags[*]) AS n FROM people;

Refused — SeveralOutsideAComparison. It stands as the whole projected value or not at all; nested inside a call there is no rule for what the call receives.

SELECT * FROM people ORDER BY tags[*];

Also refused. There is no rule for ordering by several values, and inventing one would sort records by a rule nobody could predict.

Multikey indexes

One entry per element rather than one per record — which is what lets an index answer a question about elements without answering it with a fact about arrays.

DEFINE INDEX by_tag ON people FIELDS tags[*];
SELECT * FROM people WHERE tags[*] = 'old';

The same record the scan answered, and the node now says via index.

Three combinations are refused, each because it has more than one honest reading:

DEFINE INDEX by_tag_unique ON people FIELDS tags[*] UNIQUE;

SeveralInAUniqueIndex — unique across elements, or unique per record?

DEFINE INDEX by_tag_search ON people FIELDS tags[*] SEARCH;

SeveralInAnAnalysedIndex — a search index decides its own multiplicity.

DEFINE INDEX by_two ON people FIELDS tags[*], aliases[*];

SeveralRoutesInOneIndex — the cross product of two arrays is not what anybody means.

Documents — TessariDB