Database
The Database step runs one SQL statement against your own PostgreSQL or MySQL and hands the rows to the rest of the workflow. It is how a workflow enriches a webhook payload from your users table, looks up an order before calling an API, or writes a result back where your application can see it. It is not a database KnoxCall hosts. The server is yours; KnoxCall connects to it, runs the statement you wrote, and forgets the connection.Setting one up
1
Store the connection as a Secret
Create a Secret whose value is a connection URI:
postgres://, postgresql://, mysql:// and mariadb:// are all understood — a
JDBC-style jdbc:postgresql://… prefix is not; strip it. The
Secret is envelope-encrypted like every other Secret and is resolved only while the
step runs — it is never written into the workflow.2
Add a Database step and pick it
Choose PostgreSQL or MySQL to match the Secret, then select the Secret in
Connection Secret.
3
Write the SQL, and put the values in Parameters
Values go in Parameters, never in the SQL
The SQL box is not templated.{{variables}} in it are refused when you save, and
refused again if the step somehow runs.
That is deliberate, and it is the single most important thing on this page. A value
pasted into a statement stops being a value — ' OR '1'='1 in the middle of a WHERE
clause is syntax, and it is how databases get emptied. A value in Parameters is
sent to your database separately from the statement and can never be parsed as SQL,
whatever it contains.
Write the placeholders your database uses:
Parameters is a JSON array, in placeholder order, and is templated:
true stays a boolean, an object
becomes JSON.
One statement per step
A Database step runs exactly one statement.SELECT 1; DROP TABLE users is refused when
you save, and your database refuses it again at run time: KnoxCall always uses the
prepared-statement protocol, which cannot carry two commands.
If you need several statements, use several steps — or put them in a function or a view
and call that.
Read-only by default
Read-only is on when you add the step, and it is your database that enforces it, not a check on the text of your SQL. The statement runs inside a read-only transaction (BEGIN READ ONLY on PostgreSQL, START TRANSACTION READ ONLY on MySQL), so the server
refuses any write — including one hidden inside a function your statement calls.
Turn Read-only off to INSERT, UPDATE or DELETE. When you do:
- the step’s output records
readOnly: false, so a run that wrote is visible as one; - the statement still runs inside a transaction, so a failure rolls back;
affectedRowson the output tells you how many rows changed.
TLS
TLS is on and fully verified by default.
A server whose CA is not in the public trust store — Amazon RDS and Google Cloud SQL both
use their own roots — will fail to connect on the default rather than quietly
connecting insecurely. That is intentional: an unverified TLS session is not a weaker
guarantee than a verified one, it is a different one, and an attacker on the path can
present any certificate and read your database password.
Whichever you choose is recorded on every run’s output as
tls.
Where KnoxCall will and will not connect
The Database step opens a raw TCP connection, so it goes through a chokepoint of its own. Before any socket is opened, KnoxCall resolves your host once and refuses to connect if the address is:- a private, loopback or link-local address (
10.x,192.168.x,127.0.0.1,::1); - a cloud metadata address (
169.254.169.254); - a KnoxCall host — the platform never sends your traffic to itself;
- one of KnoxCall’s own outbound addresses.
Your database must be reachable from the public internet for this step to connect. If it
is inside a private network, expose a read replica, put it behind a bastion you control,
or use the HTTP Request step against an API in front of it.
Bounds
Timeout is one budget for the whole step. Resolving your database’s host, opening
the connection and running the statement all draw on the same number — a step set to
10,000 ms will not spend 10,000 ms connecting and then another 10,000 ms querying. When
the budget runs out the connection is closed and the step fails.
Row limit bounds the output, not the work. Your database still produces every row a
statement asks for; the limit decides how many are kept in the step’s output. Put a
LIMIT in your SQL to bound what the database does. When more rows came back than were
kept, the output says so: truncated: true, with rowCount reporting the real total.
Output
{{steps.lookup.output.rows[0].email}}.