Rows in ThingsDB: Compact & Efficient Data Serialization
In ThingsDB, wrapping is the preferred way to shape and return structured data. By defining wrap-only types, you control exactly which fields and computed properties are exposed to your clients without mutating your underlying data models.
Normally, wrapping returns data as key-value objects. While this is readable and self-documenting, repeating property keys across thousands of items creates unnecessary payload overhead, especially when rendering tables or data grids where column positions are already known.
With the new row mode for types, you can instruct ThingsDB to serialize wrapped types as flat value arrays instead of objects. Let's walk through how it works.
Setting up a Demo Collection
To see row mode in action, let's set up a simple bookstore collection with Author and Book types:
new_type('Author');
new_type('Book');
new_type('BookStore');
set_type('Author', { id: '#', name: 'str', books: '{Book}' });
set_type('Book', { id: '#', title: 'str', pages: 'int', author: 'Author?' });
set_type('BookStore', { books: '{Book}', authors: '{Author}' });
// Establish the two-way relation so Author.books is populated automatically
mod_type('Book', 'rel', 'author', 'books');
// Initialize the collection root with .books and .authors
.to_type('BookStore');
Next, let's populate the collection with a couple of authors and books:
tolkien = Author{ name: 'J.R.R. Tolkien' };
herbert = Author{ name: 'Frank Herbert' };
.authors.add(tolkien, herbert);
.books.add(
Book{ title: 'The Hobbit', pages: 310, author: tolkien },
Book{ title: 'The Lord of the Rings', pages: 1178, author: tolkien },
Book{ title: 'Dune', pages: 412, author: herbert }
);
The Traditional Approach: Wrapping as Objects
Suppose we want to fetch a flat overview of all books containing the book's ID, title, page count, and the author's name. The standard approach is to create a wrap-only type (_Book) with a computed author method:
new_type('_Book', WPO); // WPO = wrap-only type
set_type('_Book', { id: '#', title: 'str', pages: 'int', author: |this| this.author.name });
When we query our books using .map_wrap('_Book'):
.books.map_wrap('_Book');
ThingsDB returns an array of objects:
[
{ "id": 1, "title": "The Hobbit", "pages": 310, "author": "J.R.R. Tolkien" },
{ "id": 2, "title": "The Lord of the Rings", "pages": 1178, "author": "J.R.R. Tolkien" },
{ "id": 3, "title": "Dune", "pages": 412, "author": "Frank Herbert" }
]
The Problem with Manual Array Mapping
If your dataset grows to thousands of books, repeating "id", "title", "pages", and "author" on every single record wastes bandwidth and serialization time. Previously, the workaround was to manually map each item into an array:
.books.map(|book| [book.id(), book.title, book.pages, book.author.name]);
However, this has two major drawbacks:
- Repetition: You have to rewrite the mapping closure in every query that needs this format.
- Memory & CPU overhead: ThingsDB has to allocate and build a brand-new list of arrays in memory before it can even begin serializing the response.
The Solution: Enabling Row Mode
With row mode, you can configure _Book once to define the exact column order:
mod_type('_Book', 'row', ['id', 'title', 'pages', 'author']);
Now, calling the exact same .map_wrap() query:
.books.map_wrap('_Book');
Produces a compact, positional array of values:
[
[1, "The Hobbit", 310, "J.R.R. Tolkien"],
[2, "The Lord of the Rings", 1178, "J.R.R. Tolkien"],
[3, "Dune", 412, "Frank Herbert"]
]
Why This Matters
- Direct Serialization: Unlike
.map(),map_wrap()streams the wrapped output directly during serialization without allocating intermediate arrays in memory. - Smaller Payloads: Stripping out repetitive keys significantly reduces network payload sizes for large result sets.
- Reusable Schema: Your column layout is defined centrally on the type rather than scattered across application queries, while still benefiting from computed type methods like author in this example.
Comments
No comments yet. Start the discussion.