Skip to Content
Querying & DML

Querying & DML

Raw SQL execution

ts snippetTS
const res = await client.query('SELECT * FROM app.messages LIMIT 10');console.log(res.status, res.results?.[0]?.named_rows);

With parameters:

ts snippetTS
const res = await client.query(  'SELECT * FROM app.messages WHERE conversation_id = $1',  ['conv_42'],);

From client.ts, params are sent through queryWithParams(sql, JSON.stringify(params)).

Vector search queries

Vector search uses the same query() and helper APIs as any other SQL workflow.

ts snippetTS
const matches = await client.queryAll(  `SELECT d.id, d.title   FROM rag.documents AS d   JOIN rag.documents_vectors AS v ON v.id = d.id   ORDER BY COSINE_DISTANCE(v.doc_embedding, $1)   LIMIT 5`,  [JSON.stringify(queryEmbedding)],);

Typical SQL setup:

sql snippetSQL
CREATE TABLE rag.documents_vectors (  id BIGINT PRIMARY KEY,  doc_embedding EMBEDDING(384)) WITH (TYPE = 'USER'); ALTER TABLE rag.documents_vectors  CREATE INDEX doc_embedding USING COSINE;

See Vector Search for the full schema and retrieval pattern.

Row helpers (named_rows)

query() returns a raw QueryResponse. Results are exposed via results?.[0]?.named_rows as plain named-column maps from the server.

For ergonomic access, use:

  • queryOne(sql, params?)RowData | null
  • queryAll(sql, params?)RowData[]
  • queryRows<T>(sql, tableName, params?)KalamRow<T>[]

queryOne() and queryAll() are intentionally not generic. They return RowData with KalamCellValue wrappers. Use queryRows<T>() when you want typed application objects plus FILE-aware row helpers.

ts snippetTS
const one = await client.queryOne('SELECT id, name FROM app.users WHERE id = $1', ['u1']);console.log(one?.id.asString(), one?.name.asString()); const many = await client.queryAll('SELECT id, active FROM app.users');for (const row of many) {  console.log(row.id.asString(), row.active.asBool());}

For FILE-aware row wrappers (.file() / .downloadUrl()):

ts snippetTS
interface MessageRow {  id: string;  sender: string;  content: string;} const rows = await client.queryRows<MessageRow>(  'SELECT id, sender, content, avatar FROM app.messages',  'app.messages',);console.log(rows[0]?.data.sender);

The helper methods above wrap each value as KalamCellValue (.asString(), .asInt(), .asFile(), etc.). See Cell Values for the full API.

executeAsUser

ts snippetTS
await client.executeAsUser(  'SELECT * FROM app.messages LIMIT 1',  'alice',);

Source behavior:

  • wraps SQL as EXECUTE AS '<user_id>' ( <statement> )
  • strips trailing semicolons in inner SQL
  • escapes ' in the target user id
  • requires one non-empty statement and a non-empty user id
  • is valid for USER and STREAM tables; SHARED tables use their table policy directly
  • KalamDB authorizes cross-user targets with its role matrix: system can target any role, DBA can target DBA/service/user, service can target service/user, and regular users can only target themselves

Convenience DML methods

ts snippetTS
await client.insert('app.messages', {  id: 1,  sender: 'alice',  content: 'hello',}); await client.update('app.messages', 1, { content: 'updated' });await client.delete('app.messages', 1);

Important caveat

update() builds SQL string values directly (with quote escaping for strings). For complex/untrusted inputs, prefer query() with parameter placeholders where possible.

Type shape notes from generated declarations

Generated SDK types mark some fields optional:

ts snippetTS
interface QueryResponse {  status: 'success' | 'error';  results?: QueryResult[];  took?: number;  error?: ErrorDetail;} interface QueryResult {  schema?: SchemaField[];  rows?: unknown[][];  named_rows?: Record<string, unknown>[];  row_count: number;  message?: string;}

Handle missing results/schema defensively in generic tooling; prefer named_rows when present.

Last updated on