OriginChainDB docs
schema · sql

SQL.

1

Before you start.

This page uses the shop.orders table from the quickstart. Table names are always fully qualified: namespace.table, never a bare table name.

namespace   = "shop"
table       = "orders"
primary_key = ["id"]

[[columns]]
name = "id"
ty   = "str"
required = true

[[columns]]
name = "customer"
ty   = "str"

[[columns]]
name = "amount_cents"
ty   = "i64"

[[columns]]
name = "status"
ty   = "str"

[[columns]]
name = "notes"
ty   = "str"

[[columns]]
name = "placed_ms"
ty   = "u64"

# Turns WHERE status = '...' into a sub-linear lookup.
[[indexes]]
name    = "by_status"
columns = ["status"]

The [[indexes]] block is what turns WHERE status = … from a full scan into a lookup. EXPLAIN tells you which one you got.

2

Type mapping.

What a column declared with each ty looks like coming out of a SELECT, and what you get after decoding in each client. All three clients decode rows into a generic map, so the language column is what JSON decoding yields there.

ty JSON on the wire TypeScript Python Go
i64 / u64 number number int float64 ⚠
f64 number number float float64
bool boolean boolean bool bool
str / text string string str string
decimal string — "19.99" string str string
uuid string string str string
enum string string str string
inet string string str string
bytes hex string — "\\x4f43" string str string
date text — "2026-07-25" string str string
timestamp text — "2026-07-25 09:14:22" string str string
time text — "09:14:22.500000" string str string
interval number — milliseconds number int float64
json object or array unknown dict / list map[string]any / []any
list array unknown[] list []any
point { lat, lng } object object dict map[string]any
decimal is a string, and its arithmetic is exact

A decimal column travels as a JSON string — "19.99" — deliberately, so no binary-float rounding is ever introduced into money. On write you may send either a string or a bare JSON number; both are canonicalised to the same stored form. SUM, AVG, arithmetic, ORDER BY and range comparisons over a decimal column are all exact and numeric, not lexical, and the result comes back in the same canonical string form. A result past the exact-decimal range is an error, never a silently wrong number.

large integers lose precision in TypeScript and Go

An i64 or u64 travels as a JSON number. JavaScript decodes it to a double, and Go decodes into map[string]any as float64 — so values beyond about 9×10¹⁵ are silently rounded in both. Python is unaffected. If you store identifiers or counters that big, declare the column as str, or decode the response yourself with a big-integer-aware parser.

dates and times come back as text

date, time and timestamp columns are stored as integers but rendered into familiar text on the way out — you read "2026-07-25", not an epoch count. That reformatting applies only to output columns the plan can prove are a straight pass-through of a temporal column, so a computed expression over one still comes back as a number. On write, both spellings are accepted and address the same row.

Related.

  • Row CRUD — point reads and the bulk-write path, and how its upsert differs from SQL INSERT.
  • Transactions — making several statements land together, and what isolation you actually get.
  • Schema reference — column types, indexes, foreign keys and CHECK constraints.
  • Queries in the dashboard — running the same statements from the console workbench.
  • Error reference — the shared error envelope every endpoint returns.