Quick Start
The examples assume a q process listening on localhost:1800, for example one started with q -p 1800. They define their own trade table, so any empty q session works.
Query, call, and send
import narwhals as nw
import pyarrow as pa
import xqdb
conn = xqdb.Q("localhost", 1800) # PyArrow backend; connects on first use
try:
conn.sync("trade:([] sym:`AAPL`MSFT; price:10.5 11.0; size:100 200)")
# A q table arrives as a Narwhals DataFrame.
frame = conn.sync("select from trade where price > 10")
print(nw.to_native(frame)) # pyarrow.Table
# Functional call: q applies the lambda to the argument. str becomes a symbol.
total = conn.sync("{[s] exec sum size from trade where sym=s}", "AAPL")
print(total) # 100
# Send a frame. Dictionary-encoded strings become a q symbol column.
rows = pa.table({
"sym": pa.array(["GOOG"]).dictionary_encode(),
"price": [12.25],
"size": [300],
})
conn.sync("insert", "trade", rows)
# Asynchronous message: returns once written; q sends no reply.
conn.asyn("insert", "trade", rows)
finally:
conn.disconnect()import { Q } from "@xbbg/xqdb";
import { Table } from "apache-arrow";
const q = await Q.connect({ host: "localhost", port: 1800 });
try {
await q.sync("trade:([] sym:`AAPL`MSFT; price:10.5 11.0; size:100 200)");
// A q table arrives as an apache-arrow Table.
const result = await q.sync("select from trade where price > 10");
if (result instanceof Table) {
console.log(result.numRows, result.schema.fields.map((field) => field.name));
}
// Functional call: string becomes a symbol; q longs come back as bigint.
const total = await q.sync("{[s] exec sum size from trade where sym=s}", "AAPL");
console.log(total); // 100n
// Send one row as a mixed list: symbol, float, long.
await q.sync("insert", "trade", ["GOOG", 12.25, 300n]);
// Asynchronous message: resolves once written; q sends no reply.
await q.asyn("insert", "trade", ["GOOG", 12.5, 100n]);
} finally {
await q.disconnect();
}What happened
- 01Connect
Python opens the socket on the first query.
Q.connect()in Node.js connects before it resolves. Both perform the q handshake with an empty username and password. - 02Query
sync(expression)sends a q string for evaluation and waits for the reply. The table becomes a DataFrame in Python and an ArrowTablein Node.js. - 03Call
sync(function, ...args)sends a functional call with up to eight arguments. A{...}lambda string is sent as a q lambda. - 04Send
Arguments are converted by the type mapping.
asynreturns once the message is written, without a reply.
Two conversions surprise most new users:
- Strings are symbols. A Python
stror JavaScriptstringargument becomes a q symbol. To send a q string (a char vector), passbytesin Python or aBuffer/Uint8Arrayin Node.js. - Numbers are floats in JavaScript. A JavaScript
numberbecomes a q float. Use abigintsuch as300nfor a q long.
A plain string column, such as Arrow string, pandas object, or Polars String, becomes a general list of q strings rather than a symbol column. Dictionary-encode or categorize the column to send symbols. Arrow JS already dictionary-encodes strings by default. See DataFrames and Arrow.
Choose a DataFrame backend (Python)
import narwhals as nw
import xqdb
conn = xqdb.Q("localhost", 1800, backend="polars") # or "pandas"; default "pyarrow"
frame = conn.sync("([] x:1 2 3)")
print(type(nw.to_native(frame))) # polars.DataFrame
conn.backend = "pyarrow" # switch for later results
conn.disconnect()Next steps
- Connections and TLS: credentials, TLS, timeouts, and connection lifecycle.
- Queries and Subscriptions:
sync,asyn,receive, and tickerplant feeds. - Values, Nulls, and Text: temporal precision, nulls, and UTF-8 handling.
