SQLite Database
XCX 4.4.1 Database
XCX 4.4.1 has native relational database support via the database: type and a set of built-in methods. Version 4.4.1 supports SQLite only.
Table of Contents
1. Connection Declaration (database:)
- 5.1 DDL
- 5.2 Write Operations
- 5.3 Read Operations
- 5.4 Delete Operations
- 5.5 Transactions
- 5.6 Other
8. Full Example
1. Connection Declaration (database:)
Fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
engine | s | yes | - | Database engine. Currently only "sqlite". |
path | s | yes | - | Path to the .db file (relative to project root). |
timeout | i | no | 5000 | Operation timeout in ms. |
readonly | b | no | false | Read-only mode. |
Behavior
- Connections are lazy - opened on first use.
- On connection failure (e.g. missing permissions)
halt.erroris triggered automatically. - SQLite creates the database file if it does not exist (unless
readonly = true). - Multiple simultaneous connections are allowed - each has its own name.
I/O Model
All DB operations that touch disk or the driver are I/O operations:
| Context | Behavior |
|---|---|
| Inside a fiber | require yield |
| Outside a fiber | block synchronously |
This applies to read (fetch, query, queryRaw), write (push, save, insert, exec), delete (remove, exec), and DDL (sync, drop) operations.
Exception - methods without yield. Methods that only operate on transaction state or metadata do not require yield in any context:
db.has(),db.close(),db.isOpen()db.begin(),db.commit(),db.rollback()
begin(), commit() and rollback() do not touch data - they only manage transaction state in the driver.
2. Table Column Attributes
These attributes extend the table: block declaration and are used by the database module.
Attributes
| Attribute | Description | SQL Equivalent |
|---|---|---|
@pk | Primary key. Can be combined with @auto. | PRIMARY KEY |
@unique | Value must be unique within the column. | UNIQUE |
@optional | Column may be NULL in SQL. XCX returns the type's default value on read. | NULL |
@default(v) | Default column value in SQL. | DEFAULT v |
@fk(t.col) | Foreign key pointing to column col of table t. | REFERENCES t(col) |
Example
@optional Behavior
XCX has no null as a standalone type. @optional columns in SQL may store NULL, but when read into XCX the value is automatically replaced with the type's default:
| XCX Type | Value when SQL NULL |
|---|---|
i | 0 |
f | 0.0 |
s | "" |
b | false |
Note: @optional only signals NULL acceptability on the SQL side. XCX does not preserve the distinction between NULL and the default value - that information is lost on read. If your application logic requires distinguishing these cases, store a flag in a separate column.
Column Omission Rules for add() and insert()
| Column Attribute | Can be omitted? | What goes into SQL |
|---|---|---|
@auto | yes (always) | auto-generated value |
@default(v) | yes | value v from DEFAULT |
@optional | yes | NULL |
@optional @default(v) | yes | value v from DEFAULT |
| no attributes | no | compile error |
@auto columns can never be provided explicitly - neither positionally nor via named argument. Attempting to do so is a compile error.
3. Named Arguments
Named arguments allow passing values to table.add(), table.insert(), and db.insert() by column name rather than by position. They are an optional, additive feature - all existing positional calls remain fully valid.
Named arguments work only for table insert operations. They do not apply to user-defined functions, fibers, or other built-in methods. Column names are statically known to the compiler from the table: schema.
Syntax
Namespace separation. The left side of = is always the column name. The right side is an expression from the local scope. These are two independent namespaces - no conflict:
Evaluation order. Right-hand expressions are evaluated left to right in the order written:
Mixing Positional and Named
Positional and named arguments can be mixed. Positional arguments must precede named arguments.
Assignment rule. Positional arguments are assigned to columns left-to-right in declaration order, skipping @auto, until they run out. Named arguments fill the remaining columns by name.
Positional arguments assign sequentially without skipping. Skipping a middle column is only possible via a named argument or by omitting it entirely (if optional):
Providing the same column both positionally and by name is a compile error:
Compile-Time Rules
| Rule | Behavior |
|---|---|
@auto column provided | compile error |
| Unknown column name | compile error |
| Duplicate column name | compile error |
| Required column omitted | compile error |
| Named arg before positional arg | compile error |
Completeness table:
| Column Attribute | Can be omitted? | Value when omitted |
|---|---|---|
@auto | yes (always) | auto-generated value |
@default(v) | yes | value v from DEFAULT |
@optional | yes | NULL (→ XCX default on read) |
@optional @default(v) | yes | value v from DEFAULT |
| no attributes | no | compile error |
4. Result Capture (as)
as is a general XCX mechanism for capturing the result of a block operation:
Write/Delete Result Object
Operations db.insert(), db.save(), db.push(), and db.exec() return an object with two fields:
| Field | Type | Description |
|---|---|---|
affected | i | Number of rows changed / inserted by the operation |
insertId | i | ID of the last inserted record (@auto @pk); 0 if no insert |
Using as is optional - if the result is not needed it can be omitted:
affected and insertId Semantics
| Operation | affected | insertId |
|---|---|---|
insert() - single row | 1 | ID of inserted row |
push() - multiple rows | number of inserted rows | ID of last inserted row |
save() - upsert (insert) | 1 | ID of inserted row |
save() - upsert (update) | 1 | 0 |
exec(DELETE ...) | number of deleted rows | 0 |
exec(UPDATE ...) | number of updated rows | 0 |
On operation failure (constraint violation, no connection, readonly) halt.error is triggered and execution does not reach the code using the as result. The result object is always valid if the code can see it.
5. API Reference
5.1 DDL
5.2 Write Operations
On error (violation of @unique, @fk, no connection) the operation triggers halt.error.
5.3 Read Operations
#### .first() on queryRaw Result
.first() returns the first element of the JSON array as a single json object. It triggers halt.error if the query returned no rows. Typical use is for aggregate queries (COUNT, SUM, MAX) and lookups that return at most one row.
If you are not certain the result will be non-empty, check the size before using .first():
#### Supported Operators in .where()
The .where() filter compiles to a SQL WHERE clause. A limited subset of XCX expressions is supported:
| Category | Supported |
|---|---|
| Comparisons | ==, !=, <, <=, >, >= |
| Logical | AND, OR, NOT |
| Values | literals, local variables |
| Left operands | column names only |
Function calls, string methods, and nested expressions are not supported in .where() with db.fetch(). Using an unsupported expression is a compile error.
Chaining .where() joins conditions with AND:
#### db.fetch() and Local rows
db.fetch() reads only data from the SQL database - local rows declared in the table: block are ignored. The local table definition serves only as a schema hint.
To insert local rows into the database, use db.push(users) before db.fetch().
5.4 Delete Operations
db.remove() without .where() is a compile error (D401). To delete all rows, use db.truncate(users) explicitly.
5.5 Transactions
If any operation inside the transaction triggers halt.error, the transaction is automatically rolled back before the frame is aborted. The connection returns to the state before begin().
rollback() is for explicitly cancelling a transaction in conditional logic before an error occurs:
5.6 Other
6. Type Mapping (XCX ↔ SQL)
| XCX Type | SQL Type (SQLite) |
|---|---|
i | INTEGER |
f | REAL |
s | TEXT |
b | INTEGER (0 / 1) |
date | TEXT (YYYY-MM-DD HH:mm:ss) |
7. Security Constraints
| Constraint | Behavior |
|---|---|
Path containing .. | halt.fatal - path traversal |
| Absolute path | halt.fatal |
| Raw SQL with unvalidated input | Developer is responsible for sanitization - use ? params |
readonly = true + DML operation | halt.error |
save() on table without @pk | compile error |
remove() without .where() | compile error (D401) |
Always use prepared statements with ?: