Querying & DML
KalamClient.query(...) executes SQL over the KalamDB HTTP API and returns a QueryResponse.
final response = await client.query('SELECT 1');
print(response.success);Parameterized queries
Use $1, $2, … placeholders in SQL and pass params as a Dart list.
final res = await client.query(
r'SELECT * FROM app.messages WHERE conversation_id = $1 AND is_deleted = $2',
params: ['conv_42', false],
);params is encoded to JSON and sent to the server, so values should be JSON-compatible (strings, numbers, booleans, null, lists, and maps).
If you need richer server-side typing (timestamps, decimals, JSON columns), keep your SQL explicit (e.g. CAST(...)) and validate the returned values.
Namespaces
KalamDB supports per-tenant namespaces. The SDK exposes this as an optional namespace argument.
final res = await client.query(
'SELECT * FROM messages ORDER BY created_at DESC LIMIT 10',
namespace: 'alice',
);This is most useful when your SQL references unqualified tables (e.g. messages instead of alice.messages).
For background, see: SQL Reference: Namespaces.
Reading results
A QueryResponse can contain multiple QueryResult values (one per statement). The most common pattern is to read the first one.
Primary row access (QueryResult.rows)
if (!res.success) {
print(res.error);
return;
}
final first = res.results.first;
print(first.columns.map((c) => c.name).toList());
for (final row in first.rows) {
final id = row['id']?.asInt();
final name = row['name']?.asString();
final active = row['active']?.asBool();
print('id=$id name=$name active=$active');
}rows is List<Map<String, KalamCellValue>> (named columns), not List<List<dynamic>>.
Multiple statements
final res = await client.query('SELECT 1; SELECT 2;');
for (final (i, r) in res.results.indexed) {
print('statement[$i] columns=${r.columns.length} rows=${r.rowCount}');
}DML: INSERT / UPDATE / DELETE
All SQL goes through query(...):
await client.query(
r"INSERT INTO app.messages (conversation_id, role, content) VALUES ($1, $2, $3)",
params: [42, 'user', 'hello'],
);
await client.query(
r"UPDATE app.messages SET content = $1 WHERE message_id = $2",
params: ['edited', 'msg_123'],
);
await client.query(
r"DELETE FROM app.messages WHERE message_id = $1",
params: ['msg_123'],
);FILE refs: insert and read
Insert a FILE reference value
If you already have file metadata (for example from a previous read), insert it as JSON:
final ref = KalamFileRef(
id: '1234567890123456789',
sub: 'f0001',
name: 'avatar.png',
size: 20480,
mime: 'image/png',
sha256: 'abc123...',
);
await client.query(
r'INSERT INTO app.users (id, avatar) VALUES ($1, $2)',
params: ['user_1', ref.toMap()],
);Read a FILE reference
final res = await client.query('SELECT id, avatar FROM app.users WHERE id = $1', params: ['user_1']);
final row = res.rows.first;
final fileRef = row['avatar']?.asFile();
if (fileRef != null) {
final url = fileRef.getDownloadUrl('http://localhost:18080', 'app', 'users');
print('${fileRef.name} ${fileRef.formatSize()} $url');
}Error handling
When success is false, the server returns an error code/message:
final res = await client.query('SELECT * FROM table_that_does_not_exist');
if (!res.success) {
throw StateError('${res.error}');
}For user-facing apps, prefer mapping error.code to a stable UI message and logging error.details for debugging.