Klarety

Run SQL

Run Spark SQL on Nexus with parameters, estimates and result pages.

For devs

nexus.sql() runs one statement on and waits for the answer. In the app, the same queries run in DataSQL editor. For long scripts or heavy processing, see Spark sessions and Jobs.

Before you start

  • The SDK is installed and you can connect (SDK quickstart).
  • Your key's Access allows what the statement does. A read-only key can only read.
  • The workspace has credits.

Run a query

top_customers.py
from klarety import Nexus, RefusedError, ServiceError

with Nexus.connect() as nexus:
    try:
        result = nexus.sql(
            "SELECT customer, SUM(amount) AS total "
            "FROM orders GROUP BY customer ORDER BY total DESC",
            max_rows=10,
        )
    except RefusedError as refused:
        raise SystemExit(f"Query refused: {refused}")
    except ServiceError as failed:
        raise SystemExit(f"Nexus could not answer, try again: {failed}")

    for row in result.rows:
        print(row["customer"], row["total"])
    print(f"Read {result.bytes_scanned} bytes")

Tables without a name are read from default. Name another schema as schema.table.

What comes back

sql() returns a ResultSet:

FieldMeaning
rowsThe rows, each a dictionary keyed by column name. len(result) is the number of rows.
bytes_scannedHow much data the statement read.
truncatedTrue when the statement produced more rows than came back.
result_idSet when the rest of the result was kept aside for you to read page by page.
pagesHow many pages were kept aside.
result_truncatedTrue when even the kept result passed your plan's size, so only its first part is kept.

Two value types need care:

  • A whole number larger than 253 comes back as text, so no JSON reader rounds it. Convert it with int().
  • A timestamptz value comes back in UTC with no offset written. Add Z before a strict parser reads it.

Pass values with parameters

Never join user input into SQL. Use params: each :name is filled as a string literal, never read as SQL.

result = nexus.sql(
    "SELECT * FROM orders WHERE customer = :customer AND amount > CAST(:minimum AS DOUBLE)",
    params={"customer": "Acme", "minimum": "100"},
)

Every value is a string; CAST numbers and dates in the SQL, as above. A parameter name starts with a letter or underscore and has up to 64 letters, digits or underscores; a statement takes at most 256 parameters.

Control how many rows come back

max_rows caps the rows returned: 1 to 10,000, 1,000 by default, never past your plan's page of rows. If there are more, truncated is True.

Read a large result

When a result is bigger than one answer, Nexus keeps the rest for a day and sets result_id. nexus.result_rows(result) reads every row page by page, without holding it all in memory:

result = nexus.sql("SELECT * FROM events WHERE day = '2026-11-01'")

if result.truncated:
    count = 0
    for row in nexus.result_rows(result):
        count += 1
    print(f"{count} rows in total")

If result_id is None, result_rows() returns the rows you have, so it is always safe to call.

Estimate before you run

nexus.estimate() reports what a statement would read, without running or charging for it:

estimate = nexus.estimate("SELECT * FROM events")
print(estimate["bytes_scanned"], estimate["allowed_bytes"], estimate["within_budget"])

If within_budget is False, it would be refused with OverBudgetError. Add a WHERE on a column the table is organised by, select fewer columns, or aggregate first.

Start Nexus ahead of time

After a quiet period, the first statement can wait while Nexus starts. Before a known burst (for example, at app start), call nexus.warm(); it returns at once and starts Nexus in the background. nexus.spark_status() tells you whether it is off, starting, ready or busy.

Retries and timeouts

You do not need to write retry loops around sql():

  • Every statement carries an SDK-chosen ID. If the connection drops, the SDK resends it with the same ID, and Nexus runs it once.
  • When every slot your plan allows is busy, or Nexus is starting, the SDK waits and tries again for up to five minutes.
  • A statement that has not finished in that time raises AnswerNotReadyError. Run it again: nothing ran twice.

What can go wrong

ExceptionCauseWhat to do
RefusedErrorThe statement is not allowed for this key, or the SQL is invalid. The message says which.Fix the statement or use a key with more access.
OverBudgetErrorThe statement would read more than your plan allows.Narrow it, then check with estimate().
RateLimitedErrorToo many calls per minute.The SDK already waited; slow your loop down.
OverBudgetError (busy)Every slot is taken and the queue is full.Send it again shortly.
InsufficientCreditsErrorThe workspace is out of credits.Add credits.
AnswerNotReadyErrorNo answer within the waiting time.Ask again.

Next steps

On this page