Pular para o conteúdo principal

Your own databases: Qubix.db

Qubix.db is the command through which a user script and a website page handler work with your own PostgreSQL, MySQL or MariaDB database: they read rows and write changes. No HTTP middleman is needed for this — the script connects to the database itself with a connection string.

This is not the Qubix database. The statistics Qubix records are read by the sql command — read-only and within the rights of your role (see Tables for sql queries). Qubix.db works with your database with the rights of the database user named in the connection string — it both reads and writes.

Where the command is available:

  • in scripts of the Scripts section — both on a schedule and via the Executar button;
  • in website page handlers — on every visit and via the Executar button in the Executor de teste panel (see Site backend).

Britva rules do not have Qubix.db.

An allowed host first

You can connect only to a host that an administrator has entered in the Database hosts for scripts list (Configurações do Qubix → JavaScript). While the list is empty, connections to databases are off. More — in the Where you can connect section.

What it looks like​

db-quick-start.jsJavaScript
function main() {
const db = Qubix.db.connect('postgres://report:[email protected]:5432/crm')

const rows = db.query('SELECT id, email FROM customers WHERE country = $1 LIMIT 10', ['DE'])
for (const row of rows) console.log(row.id, row.email)

const changed = db.exec('UPDATE customers SET checked_at = now() WHERE country = $1', ['DE'])
console.log('rows changed:', changed)

db.close()
}

All calls are synchronous: the result comes right away, as with sql and ctx.fetch, so await is not needed. With MySQL everything is the same, except that the string starts with mysql:// and a value's place in the query text is marked with ?.

Connection string​

Qubix.db.connect(...) takes a string of this form:

postgres://user:password@host:5432/database?sslmode=require
mysql://user:password@host:3306/database?tls=true
PartWhat it means
schemepostgres:// or postgresql:// — PostgreSQL; mysql:// or mariadb:// — MySQL and MariaDB
user:password@the database user and their password. What the queries may do is decided by this user's rights in your database
hostone host — a name or an address. Several hosts separated by commas are not accepted, and neither is a path to a socket file instead of a host: the connection goes over the network only
:portoptional; without a port — 5432 for PostgreSQL and 3306 for MySQL
/databasethe database name
?…parameters — only from the table below
Special characters in the password

The connection string is read by the rules of an address, so characters like #, ?, /, % or a space in the user name and the password must be written in percent-encoding. The easiest way is to build the string with encodeURIComponent:

JavaScript
const url = 'postgres://report:' + encodeURIComponent(password) + '@db.example.com:5432/crm'

String parameters:

ParameterDatabaseValuesIf not given
sslmodePostgreSQLdisable, allow, prefer, require, verify-ca, verify-fullprefer: an encrypted channel if the database supports it, otherwise an open one
channel_bindingPostgreSQLdisable, prefer, requireprefer
require_authPostgreSQLscram-sha-256 — the password goes only by SCRAM: a server that asks for it in any other way gets a refusalwhatever the server asks for
application_namePostgreSQLany name — the connection shows up under it in the database's list of connections—
tlsMySQL, MariaDBtrue — encryption with certificate verification; skip-verify — encryption without certificate verification; preferred — encryption without verification if the server supports it, otherwise an open channel; false — no encryptionno encryption
When the server reaches the internet through Tor

If an administrator set the server to reach the internet through Tor (the Outbound requests tab), a database on the internet is connected only when the connection checks the database server by its name: add sslmode=verify-full for PostgreSQL and tls=true for MySQL and MariaDB. A Tor exit is somebody else's server: without the check it would receive the database password. sslmode=verify-ca is not enough — without a root certificate of your own it accepts any public certificate without checking the name — and neither is channel_binding=require with require_auth=scram-sha-256: the password does not travel open then, but an exit that accepted the encryption itself gets enough to guess a weak password offline. A database on an internal network from the host list is reached directly, and the rule does not apply to it.

Other parameters are not accepted — the refusal lists the supported ones. Parameters that would make the connection read files on the Qubix server (passfile, sslkey, sslcert, sslrootcert, service, servicefile for PostgreSQL and allowAllFiles for MySQL) are always rejected.

A string without :// is treated as the name of a saved connection, and there are no saved connections: such a call gets a refusal. Pass a connection string.

Queries: query and exec​

Qubix.db.connect(...) returns a connection object:

MethodWhat it doesWhat it returns
query(text, params)runs a query that returns rowsan array of rows; each row is an object whose keys are the column names
exec(text, params)runs a changing statement: INSERT, UPDATE, DELETE and othersthe number of rows the statement changed
close()closes the connectionnothing

Values are passed separately from the query text — as the second argument, in an array, while the text has $1, $2… in their place for PostgreSQL and ? for MySQL. A value is not pasted into the query text, so you do not need to escape it by hand, and the query cannot be tampered with through it. The second argument can be left out if there are no values.

Value in JavaScriptHow it goes to the database
a string, a number, true / false, nullas is
Datea date and time
ArrayBufferbinary data
an array, an objectnot accepted — a refusal. For a JSON column, pass JSON.stringify(value)

What comes back in the answer rows:

Column in the databaseValue in the answer row
integera number. An integer beyond JavaScript's exact integer range (Number.MAX_SAFE_INTEGER) comes as a string, so that no digits are lost
floating-point (real, double precision, FLOAT, DOUBLE)a number
decimal (numeric, DECIMAL)a string — this way no precision is lost
JSONa string — parse it with JSON.parse
date (DATE)a string like 2026-09-27
date and timea string in ISO format in UTC, for example 2026-09-27T10:00:00Z
binary (bytea, BLOB)ArrayBuffer
boolean (boolean in PostgreSQL)true / false. In MySQL BOOLEAN is the integer TINYINT, so it comes as the number 0 or 1
NULLnull
anything elsea string

Date-and-time columns without a time zone (timestamp in PostgreSQL, DATETIME in MySQL) carry no zone in the value itself: the time comes as it was written, with a Z appended.

Columns with the same name

Columns with the same name — for example, id from two tables — give one property in the row, and the last one stays. Give them different names with AS.

Closing the connection​

close() closes the connection. Calling it again breaks nothing, and a query over a closed connection gets a refusal. A connection you did not close is closed on its own: for a script — right after the run, for a page handler — before the answer to the visitor. Close the connection as soon as you no longer need it — most conveniently in a finally block.

All queries of one connection go in one database session: the connection is not swapped for another one in the middle of a run. Closing does not give back a place in the connection limit — every attempt to connect to a host during a run counts.

Example: a summary from Qubix statistics into your own database​

The script counts the deposits of the past day by country with the sql command and writes the result into your PostgreSQL table with a single insert.

daily-deps-to-db.jsJavaScript
function main() {
const rows = sql`
SELECT geo, countIf(event = 'dep') AS deps
FROM qubix_events
WHERE event_time > now() - INTERVAL 1 DAY
AND geo != ''
GROUP BY geo`
if (!rows.length) return

// One insert for all rows: every query and exec call counts toward the query limit.
const values = []
const params = []
for (const r of rows) {
values.push('(now(), $' + (params.length + 1) + ', $' + (params.length + 2) + ')')
params.push(r.geo, r.deps)
}

const db = Qubix.db.connect('postgres://writer:[email protected]:5432/reports')
try {
const inserted = db.exec('INSERT INTO daily_deps (taken_at, country, deps) VALUES ' + values.join(', '), params)
console.log('rows written:', inserted)
} finally {
db.close()
}
}

For MySQL, replace $1, $2… with ?. Split a large volume into several inserts, but not into an insert per row: you will run into the query limit.

Where you can connect​

You can connect only to a host from the Database hosts for scripts list. An administrator maintains the list on the JavaScript tab of the Configurações do Qubix window — the tab itself is described in the System article. There is one list for scripts and website page handlers; while it is empty, connections to databases are off.

Entries are written one per line:

EntryWhat it opens
a name, for example db.example.comthis host — but only if the name leads to an address on the internet
an address, for example 10.0.0.5, or a network, for example 10.0.0.0/24this address or this network, including on an internal network; after that the database can also be reached by a name that leads to this address
*any address on the internet, but not the internal network

Enter a database on an internal network — yours or the Qubix server's own — as an address or a network: a name alone is not enough for it. An entry that could not be read (for example, a network with a wrong mask) is skipped, and a line about it goes to the server log. A change to the list takes effect from the next run, no restart needed.

Limits​

An administrator sets the limits on the same JavaScript tab. For scripts they sit under the host list and are counted per run. Website page handlers have limits of their own — in the Handlers de páginas de sites (Locations) block: connections and queries there are counted per page request, while the rows, the answer size and the timeout are labeled the same way as for scripts.

LimitWhat it restrictsWhat happens beyond it
Databases: max connections per run (for handlers — Databases: max connections per page request)the number of connections: every attempt to connect to a host counts, including a failed one; a call rejected earlier — with an empty host list or a list without a single readable entry, with a wrong connection string, a name instead of one, or a value that is not a string — does not counta refusal
Databases: max queries per run (for handlers — Databases: max queries per page request)the number of query and exec calls across all connections togethera refusal
Databases: max rows per querythe number of rows in the answer to one querythe extra rows are not read: the database stops the query, query returns the first rows, and a warning goes to the run log
Databases: max response size, bytesthe size of the database's answer to one query. Every byte received counts, the service ones included, so the limit is reached sooner than the sum of the values themselves would reach ita refusal, and the connection is closed
Databases: query timeout, msthe time of one query and of one connection attempta refusal. A PostgreSQL connection stays usable after it, a MySQL connection is closed

Besides these limits, queries are stopped by the overall deadline: the script's run time, or Máx. tempo de execução do handler, ms for a page handler. A change to the limits takes effect from the next run.

Refusals and what to do about them​

A refusal is an ordinary JavaScript error. You can catch it with try…catch and carry on. An uncaught error stops the script's run with an error, and a page handler answers the visitor with an empty 500 response; to see the reason, repeat the request in the Executor de teste panel (see Site backend).

The refusal texts are in English. The tables below leave out the common prefix Qubix.db: that most of them start with. The host list and the limits are named in the texts the way they are labeled in the English interface; N in a text stands for a number.

Connecting and the host list​

RefusalWhat happenedWhat to do
connections to databases are off: the "Database hosts for scripts" list (System → JavaScript) is emptythe host list is emptyask an administrator to enter the database host
connections to databases are off: … has no entry that can be read (see the server log)the list has no entry that could be readfor an administrator — fix the entries; the unread ones are named in the server log
host "db.example.com" is not in the "Database hosts for scripts" list (System → JavaScript)the host is not on the listenter the host: a name — for a database on the internet, an address or a network — for a database on an internal network
host "db.example.com" is on an internal network: its address or network must be in the "Database hosts for scripts" list (System → JavaScript)the name is on the list but leads to an internal addressenter the database's address or network in the list
host "db.example.com" has no address, host "db.example.com" could not be resolvedno address was found for a name from the listcheck the host name
Qubix.db.connect("crm"): saved connections are not available yet; …a name was passed, not a connection stringpass a connection string
database type "redis" is not supported: use postgres:// or mysql://an unknown schemestart the string with postgres://, postgresql://, mysql:// or mariadb://
one host per connection, the connection string names no host, port "…" is not a number from 1 to 65535, cannot read the connection string: …the connection string is put together wrongcheck the string; write special characters in the password in percent-encoding
parameter "foo" is not supported (supported: …), sslmode="bogus" is not supported (supported: …), parameter "…" is given more than oncea parameter not from the list, a wrong value or a repeatkeep the parameters from the table above, each one only once
this server reaches the internet through Tor, and a Tor exit reads a connection that does not check the database server: add …the server reaches the internet through Tor, and the connection string does not check the database serveradd sslmode=verify-full for PostgreSQL, tls=true for MySQL — see above
parameter "sslrootcert" is not allowed: it makes the driver read files on the serverthe parameter would make the connection read files on the Qubix serverremove the parameter
host "/var/run/postgresql" is a unix socket; only TCP connections are allowed, parameter "host" is not allowed: …instead of a host, the address has a socket path, or the host is given in the parametersgive the host and the port in the address itself

Limits and timeout​

For a page handler, the text says per page request instead of per run.

TextWhat happenedWhat to do
connection cap exceeded: N per run (limit "Databases: max connections per run")the connection limit is used upopen one connection and run all queries through it
query cap exceeded: N per run (limit "Databases: max queries per run")the query limit is used upcombine queries: many rows — in one insert, many reads — in one query
the answer is larger than N bytes (limit "Databases: max response size, bytes")the answer is larger than the limit; the connection is closedselect fewer columns and rows; open a new connection for the next queries
the database sent more than N bytes while connecting (limit "Databases: max response size, bytes")the database sent too much already while logging incheck that it is really a database that answers at this address and port, and that the answer size limit is not too small
query stopped after N ms (limit "Databases: query timeout, ms")the query or the connection attempt did not fit into the time limitspeed up the query or ask an administrator to raise the limit
this connection was closed after an answer exceeded the response cap, this connection was closed after a query ran out of timethe connection was closed after one of the two refusals aboveopen a new connection
the run is out of time; the query was stoppedthe run time of the script or the handler is overcut down the work that one run does
result truncated to N rows (limit "Databases: max rows per query")this is not a refusal but a warning in the run log: there are more rows than the limit, the first ones came backnarrow the selection: a WHERE condition, LIMIT, aggregation in the query

Values and calls​

TextWhat happened
this connection is closeda query over a connection already closed with close()
parameter #2 is an array; pass a string, number, boolean, null, Date or ArrayBufferthe value is an array
parameter #2 is an object; pass JSON.stringify(value) for a JSON columnthe value is an object
query(text, params): params must be an array, e.g. [42, "text"]the values were not passed as an array
Qubix.db.connect(connection): pass a connection string such as postgres://user:password@host:5432/databasesomething other than a string was passed to Qubix.db.connect

Answers from the database itself​

Everything else is your database's answer: a wrong password, no such table, an error in the query text. It follows Qubix.db: together with the explanations the connection adds: for example, with a wrong password in PostgreSQL the text contains failed SASL auth: FATAL: password authentication failed for user "report". The password from the connection string does not get into the text, and the addresses the host leads to are replaced with its name.

Script and website page handler: the differences​

ScriptWebsite page handler
When it runson a schedule and via the Executar buttonon every visit and via the Executar button in the Executor de teste panel
Limitscounted per run; the fields under the host listcounted per page request; the Handlers de páginas de sites (Locations) block
Refusal on a limit… per run… per page request
Warning about truncated rowsin the run consoleonly in the test run console: the log of a live visit is not shown anywhere
Overall deadlinethe script's run timeMáx. tempo de execução do handler, ms
Unclosed connectionsare closed right after the runare closed before the answer to the visitor
Connection reuseno: every run opens its own connectionsno: every visit opens its own connections, and the database login repeats on every page view
Uncaught refusalthe run ends with an errorthe visitor gets an empty 500 response
The visitor left without waiting for the answer—the handler is interrupted, and the running database query is canceled: the change it was making may or may not get applied
Your own Qubix name at the top level of the codethe save is refused: the name is taken by the platformthe handler works, but Qubix.db is unavailable to it

If the handler has not returned even after its deadline, the visitor gets a 503 response, and the connections of this run are closed when the handler finally finishes.

Every page view logs in to the database anew, so keep handler queries short and do not open more than one connection where one is enough.

A test run writes for real

Both the Executar button of a script and the Executar button of a handler run real queries against your database: changes made through exec are applied at once. Test writes on a test table or a test database.

What's next​