Statement control
Query and session tags
Section titled “Query and session tags”Tags are JSON objects Hardcharger™ records with every session and statement in its
instrumentation tables: who ran it, for which job,
for which customer. Set them in SQL, from a driver, or per request (Warp-Query-Tag):
SET hardcharger.session_tag = '{"app": "nightly-etl", "team": "data-eng"}';SET hardcharger.query_tag = '{"job": "load-orders", "run": 42}';CREATE TABLE orders AS SELECT range AS id FROM range(1000);SET hardcharger.query_tag = '';$ export HARDCHARGER_PASSWORD='change-me'$ for i in $(seq 50); do [ "$(hardcharger --host localhost --tls=false --username hardcharger --quiet --csv \ --command "SELECT count(*) FROM _hardcharger_instr.sql_statements WHERE query_tag IS NOT NULL" | tail -1)" = 2 ] && break; sleep 0.1; done$ hardcharger --host localhost --tls=false --username hardcharger --csv --command \ "SELECT s.query_tag->>'job' AS job, s.sql_text FROM _hardcharger_instr.sql_statements s WHERE s.query_tag IS NOT NULL"job,sql_textload-orders,CREATE TABLE orders AS SELECT range AS id FROM range(1000)load-orders,SET hardcharger.query_tag = ''The statement that clears the tag still carries it. From Python, connect(session_tag=…, query_tag=…) and conn.set_query_tag(tag=…); from the
CLI, --session-tag and --query-tag. A tag that isn’t a JSON object is refused. Over
plain HTTP, the Warp-Query-Tag header tags one statement or upload; it must be
a JSON object written in ASCII (other characters as \u escapes), at most 4,096 bytes, or the
request gets 400 bad_request:
$ H='Content-Type: application/json'$ TOKEN=$(curl -s localhost:31337/warp/v1/auth/token -H "$H" \ -d '{"grant_type": "password", "username": "hardcharger", "password": "change-me"}' | jq -r .access_token)$ URL=localhost:31337/warp/v1/sessions/$(curl -s localhost:31337/warp/v1/sessions -H "Authorization: Bearer $TOKEN" -H "$H" -d '{}' | jq -r .session_id)$ curl -s $URL/statements -H "Authorization: Bearer $TOKEN" -H "$H" -H 'Warp-Query-Tag: {"job": "caf\u00e9"}' \ -d '{"sql": "SELECT 1 AS one"}' | jq -r .statesucceeded$ curl -s $URL/statements -H "Authorization: Bearer $TOKEN" -H "$H" -H 'Warp-Query-Tag: {"job": "café"}' \ -d '{"sql": "SELECT 1 AS one"}' | jq -c '{status, code, detail}'{"status":400,"code":"bad_request","detail":"Warp-Query-Tag must be ASCII (write other characters as \\u escapes)"}Timeouts
Section titled “Timeouts”Three places set a statement’s time limit; the most specific wins:
| Where | Scope |
|---|---|
query_timeout_seconds |
The server’s default (0: none) |
SET [SESSION | GLOBAL] hardcharger.query_timeout = <seconds> |
A session, or every session |
Warp-Statement-Timeout: <ms> (JDBC setQueryTimeout, ADBC adbc.warp.statement.timeout_seconds) |
One statement |
from adbc_driver_hardcharger import dbapi as hardcharger
with hardcharger.connect(uri="hardcharger://localhost?tls=false", username="hardcharger", password="change-me", settings={"query_timeout": 1}) as conn: with conn.cursor() as cur: try: cur.execute(operation="SELECT count(*) FROM range(1000000000000) a") cur.fetchall() except hardcharger.Error as e: print("statement_timeout" in str(e))TrueA statement that times out ends cancelled with error code statement_timeout (status 409, with
cancel_reason: timeout). The server stops it in DuckDB; it doesn’t just stop waiting.
Cancel
Section titled “Cancel”Every client cancels on the server: Ctrl-C in the CLI and in Python (cur.adbc_cancel() from
another thread), Statement.cancel() in JDBC, a Go context ending. A client that hangs up in the
middle of an unretained result cancels it too. A statement that changes data and has started
writing runs to the end rather than being undone because its reader left.
Kill a session
Section titled “Kill a session”An administrator can end any other session at once. hardcharger_sessions() lists them (users
see only their own):
from adbc_driver_hardcharger import dbapi as hardcharger
def connect(): return hardcharger.connect(uri="hardcharger://localhost?tls=false", username="hardcharger", password="change-me")
with connect() as victim, connect() as admin: session = victim.current_session() with admin.cursor() as cur: cur.execute(operation=f"KILL SESSION '{session}'") print(cur.fetchall()) try: with victim.cursor() as cur: cur.execute(operation="SELECT 1") except Exception as e: print("session_ended" in str(e))…TrueThe killed session’s next request gets 401 session_ended with reason killed, and its running
statement stops. KILL SESSION needs the administrator; you can’t kill your own session.
Limit sessions
Section titled “Limit sessions”max_sessions caps non-administrator sessions;
past it, opening a session fails with 429 too_many_sessions. Administrators are never capped,
so you can always get in to look. session_idle_timeout
(15 minutes) evicts sessions with no request and nothing running.
The statement queue
Section titled “The statement queue”DuckDB runs each statement on many threads; a hundred heavy statements at once just fight over the cores and memory. The queue lets a fixed number run and holds the rest:
| Option | Default | Meaning |
|---|---|---|
max_concurrent_statements |
0 |
Statements that run at once; more wait in the queue. 0: no queue. |
max_queued_statements |
-1 |
Statements that may wait. -1: 8 × the concurrency; 0: unbounded. |
max_queue_wait |
-1 |
Seconds a statement may wait. -1: 300; 0: forever. |
admin_bypass_queue_default |
true |
Administrators skip the queue unless they SET hardcharger.bypass_queue = false. |
A statement that finds the queue full, or waits longer than max_queue_wait, fails with
429 rate_limited and a message that says which, so a client can retry. Queued statements show
queued in their statement document and in instrumentation, which records how long each
waited. An administrator can change the limits live:
SET GLOBAL hardcharger.max_concurrent_statements = 4;SET GLOBAL hardcharger.max_queue_wait = 60;$ HARDCHARGER_PASSWORD='change-me' hardcharger --host localhost --tls=false --username hardcharger --csv --command \ "SELECT name, value FROM hardcharger_settings() WHERE name IN ('hardcharger.max_concurrent_statements', 'hardcharger.max_queue_wait') ORDER BY name"name,valuehardcharger.max_concurrent_statements,4hardcharger.max_queue_wait,60RESET GLOBAL returns a limit to the value the server started with.
Graceful shutdown
Section titled “Graceful shutdown”On the first SIGTERM or SIGINT, Hardcharger stops accepting new sessions and statements
(503 server_draining, with Retry-After), reports not_serving on its health endpoint so load
balancers move traffic away, and lets running statements finish for up to
shutdown_grace_period_seconds (300).
Then it cancels what is left. A second signal stops it at once. With
graceful_shutdown=false, the first signal stops it at once. Both can be changed live with
SET GLOBAL.
Stopping at once closes every session: running statements are cancelled, result streams are cut (clients see a failed stream, never a short one that looks complete), and a bulk upload whose body is still arriving is aborted and rolls back, partitioned uploads included. Nothing is half loaded, and the server exits with status 0.
In Kubernetes, set terminationGracePeriodSeconds a little above the grace period
(guide).