Tables
Shape uses Table<T> for structured row data.
The examples below assume:
from std::core::intrinsics use { Table }from std::core::queryable use { Queryable }Defining Row Types
Section titled “Defining Row Types”type Event { id: int, value: number, tag: string}Table Row Literals
Section titled “Table Row Literals”You can create tables inline using row literal syntax — comma-separated bracket groups after a Table<T> type annotation:
let events: Table<Event> = [1, 50.0, "alpha"], [2, 75.5, "beta"], [3, 120.0, "gamma"]Each [...] group is one row, with values in field order. The compiler checks that the number of elements matches the struct’s field count and resolves field types at compile time.
The LSP shows field names as parameter hints before each value, and the formatter auto-aligns columns across rows for readability.
Loading Tables
Section titled “Loading Tables”from app::events use { load_events }
let events: Table<Event> = load_events("/tmp/events.csv")?
let filtered = events .filter(|row| row.value > 10) .limit(100)
print(filtered)Query Syntax
Section titled “Query Syntax”Shape query syntax desugars into typed table operations:
let ids = from row in events where row.value > 10 select row.idUse whichever form is clearer for the code you are writing.
Table Methods
Section titled “Table Methods”Table<T> carries its row type through method chains. Type-preserving methods (filter, orderBy, head, tail, limit) return Table<T>. Transform methods (map, select) return Table<U> where U is the new row shape.
| Method | Signature | Description |
|---|---|---|
filter | fn(T) -> bool → Table<T> | Keep matching rows |
map | fn(T) -> U → Table<U> | Transform each row |
orderBy | fn(T) -> any, string → Table<T> | Sort by key |
groupBy | fn(T) -> K → Vec<{key: K, group: Table<T>}> | Group rows |
select | fn(T) -> U → Table<U> | Project columns |
head / tail | number → Table<T> | First/last N rows |
limit | number → Table<T> | Cap row count |
count | () → number | Row count |
forEach | fn(T) -> void → void | Side-effect iteration |
describe | () → Table<any> | Summary statistics |
Chaining
Section titled “Chaining”Methods chain naturally. The row type T flows through the entire pipeline:
let top_events = events .filter(|row| row.value > 10) .orderBy(|row| row.value, "desc") .limit(5)// top_events is Table<Event> — same row type as eventsThe Queryable Trait
Section titled “The Queryable Trait”Table<T> implements the Queryable<T> trait, which provides a uniform query interface shared by database extensions. Code written against Queryable<T> works on both in-memory tables and database queries:
trait Queryable<T> { filter(predicate): any, map(transform): any, orderBy(column, direction): any, limit(n): any, execute(): any}Database connectors (PostgreSQL, DuckDB) are available as Shape packages that return lazy query objects also implementing Queryable. The same .filter(...).orderBy(...).limit(n).execute() chain works across all data sources — in-memory tables execute immediately while database queries build SQL and execute on .execute(). These packages use extern C fn to call the underlying C libraries. For HTTP/REST APIs, use the built-in http stdlib module.
See Packages & Building and Native C Interop for how database packages are structured.
Rendering Tables as Charts
Section titled “Rendering Tables as Charts”Combine table row literals with content string chart format specifiers to visualize data inline:
type Revenue { month: int, revenue: number, cost: number}
let data: Table<Revenue> = [1, 120.0, 80.0], [2, 135.0, 85.0], [3, 150.0, 90.0], [4, 142.0, 88.0], [5, 168.0, 95.0], [6, 190.0, 100.0]
print(c"{data: chart(line), x(month), y(revenue, cost)}")The chart(line) format specifier renders the table as a line chart, with x(month) selecting the x-axis column and y(revenue, cost) selecting the series. See Content Strings for all chart types and options.
Table<T>keeps row shapes explicit and type-checked.- Prefer explicit row types for stable APIs and better LSP hints.
- External source schemas should be locked via the project/script lockflow.
Table<T>.execute()returns itself — it’s a no-op for in-memory tables but required for lazy database queries.