SQL Databases
QUBIQ connects to relational databases as a consumer — to read data onto screens, log values, run named queries, and store the audit journal. This is separate from the configuration database, which is internal and not user-accessible.
Supported drivers
| Driver | Typical port | Notes |
|---|---|---|
| PostgreSQL | 5432 | Also covers Postgres-compatible engines. |
| MySQL | 3306 | MariaDB included. |
| SQL Server | 1433 | Windows and Linux hosts. |
| SQLite | — | File-based; sandboxed, see below. |
Configuration
| Field | Description |
|---|---|
| Name | Connection identifier. |
| Driver | One of the above. |
| Host / Port | Server address (not used for SQLite). |
| Database | Database or catalog name. |
| Username / Password | Credentials. Encrypted at rest, never returned to the browser. |
| SSL | Require an encrypted connection. |
| Max connections | Pool ceiling. Sized to the database's limits, not to QUBIQ's demand. |
| AI username / password | Optional read-only credential used by the AI assistant instead of the primary one. |
SQLite specifics
| Field | Description |
|---|---|
| Path | File path, confined to the user-databases/ sandbox inside the data directory. |
| Create if missing | Create the file on first open. |
SQLite paths are resolved through a sandbox at both create time and open time. Directory traversal,
absolute paths outside the sandbox, and any attempt to open the internal configuration database are
rejected; only .db, .sqlite and .sqlite3 extensions are permitted.
That is a security boundary, not a convenience limit: a user-defined connection must never be able to name an arbitrary file on the host.
Pooling and sizing
The pool is shared by everything that uses the connection — named queries, pipeline nodes, bound widgets, the AI assistant.
Size max connections against the database's limit and the number of QUBIQ services that will
use it, not against your expected query rate. A pool larger than the server allows produces
connection-refused errors under load; a pool of 5–20 is right for most deployments.
What you can do with a SQL connection
| Use | Where |
|---|---|
| Bind a widget to query results | Query bindings |
| Run a saved, parameterised query | Named queries |
| Read/write from a pipeline | The SQL Query node — Pipeline nodes |
| Read/write from a script | system.db.* — Scripting API |
| Store the security audit journal | Audit journal |
Named queries
A named query is a saved statement with declared parameters and its connection baked in. Prefer them over inline SQL:
- The statement lives in one place; changing it does not mean editing every screen.
- Parameters are bound, not concatenated — the injection surface disappears.
- A screen or script refers to it by name, so it needs no database credentials of its own.
-- named query: production_by_shift
SELECT shift, SUM(units) AS units
FROM production
WHERE ts >= :start AND ts < :end
GROUP BY shift
ORDER BY shift
Called as system.db.runNamedQuery("production_by_shift", {start, end}) or bound directly to a
table widget.
Stored procedures and transactions
| Capability | API |
|---|---|
| Call a procedure, receiving every result set | system.db.callProc(...) |
| Managed transaction — auto-commit, auto-rollback on throw | system.db.transaction(opts, fn) |
| Manual transaction handle | system.db.transaction(opts) |
Transactions accept an isolation level (ReadUncommitted … Serializable, Snapshot), a
readOnly flag and a server-side timeoutMs after which the transaction is rolled back
automatically. That timeout matters: it means a browser disconnect cannot strand an open
transaction holding locks.
For anything transactional, prefer a Gateway script, which runs entirely server-side. → Gateway scripts
Writing data
Two paths, with different guarantees:
| Path | Guarantee |
|---|---|
| Pipeline SQL node | Covered by store-and-forward: buffered on outage, replayed in order, quarantined on permanent failure. |
Script system.db.* | Executes immediately; failure is returned to the caller to handle. |
If a write must not be lost when the database is unavailable, put it in a pipeline.
Security
- Give QUBIQ its own database account with the minimum rights it needs. A dashboard usually
needs
SELECTonly. - Use the AI credential for a read-only account if you enable the assistant on a production database.
- Prefer named queries to inline SQL. Where inline SQL is unavoidable, always use parameters.
- Upstream errors are sanitised before they reach a client — QUBIQ does not leak the database's raw error text or schema detail to the browser.
Troubleshooting
| Symptom | Check |
|---|---|
| Test fails with a timeout | Firewall or the server not listening on that interface. |
| Authentication fails but credentials are right | Host-based auth (pg_hba.conf), or the account is restricted by source host. |
| Works, then "too many connections" under load | Pool larger than the server's limit; reduce max connections. |
| Queries slow only through QUBIQ | Missing index on the filter columns the binding adds; check the plan for the actual statement. |
| SQLite path rejected | Outside the sandbox, or a disallowed extension. Move the file under user-databases/. |
| TLS required by server | Enable SSL on the connection. |