Relations are tables
Each link in the chain of authority is a record of its own: a place lies within another, a body acts_for a place and appoints members of another body, and an official holds a seat. A relation table names the tables it connects, so SurrealDB refuses a link between the wrong kinds of record.
DEFINE TABLE acts_for TYPE RELATION IN body OUT place SCHEMAFULL;
DEFINE TABLE appoints TYPE RELATION IN body OUT body SCHEMAFULL;
Relations carry data
An edge holds fields like any other record. decides holds the weight a body has over an issue and the reason for that weight, and voted holds the position, so the evidence for each figure is one record away from it.
RELATE body:smmag->decides->issue:public_transport CONTENT {
weight: 0.35,
basis: 'Runs public transport around Grenoble, ...',
};
Recursive graph walks
A district lies in a city, which lies in a metropolitan area, a region, a country and the EU. One expression follows within to any depth and collects every place on the way, so the dataset sets the number of levels and the query does not change.
$p.{..+collect}(->within->place)
Geometry in the same query
Outlines are GeoJSON polygons stored on each place. Finding the places that contain the address is one WHERE clause, and its result goes directly into the graph walk.
SELECT VALUE id FROM place
WHERE area != NONE AND $at INSIDE area;
One relation, two kinds of voter
Some councils record each member's vote, and others only each group's. voted accepts an official or a group as its source, and fn::vote uses the group's vote when a member has none of their own. A literal type limits the position to three values.
DEFINE TABLE voted TYPE RELATION IN official | faction OUT measure SCHEMAFULL;
DEFINE FIELD position ON voted TYPE 'for' | 'against' | 'abstain';
Closures and record links
Array methods take closures, and a record link reads like a field: $p.measure.issue follows a position to its measure and then to the issue. The weighting is a few lines of map, filter and math::sum, with no joins.
LET $known = $members.filter(|$m| $m.alignment != NONE);
LET $agree = math::sum($known.map(|$m| $m.weight * $m.alignment));
SELECT over any array
SELECT takes an array as its source, including one the function built a line earlier. The group tallies for a large council are a GROUP BY over the votes just collected.
SELECT party, they, count() AS members FROM $cast
GROUP BY party, they ORDER BY party, they
The logic is in the database
fn::agreement, fn::elections and fn::representatives are defined in the schema with typed parameters. This page and the command-line scripts call the same functions and get the same figures.
DEFINE FUNCTION fn::agreement(
$at: geometry<point>, $positions: array<object>
) -> array<object> { ... };
The same engine in the browser
This page runs SurrealDB compiled to WebAssembly, in memory, and loads the schema file unchanged. Every query on this page runs in your browser, and the page has no server of its own.
const db = new Surreal({ engines: createWasmEngines() });
await db.connect("mem://");
A new city is new data
Paris and Grenoble are two data files over one schema, and the schema names no country, city or issue. A new city, issue or body is a set of new records, and the schema and functions stay as they are. The French version of this page is one example: a file of UPDATE statements on the text fields, loaded into a second database.
CREATE issue:public_transport CONTENT { ... };
RELATE body:smmag->decides->issue:public_transport CONTENT { ... };