Databases & SQL
Connect a SQL database and query it directly — the foundation for dashboards and data-driven pages. Likeable's query engine is DuckDB: it talks to Postgres and MySQL, federates across them, and reads cloud files (Parquet/CSV/JSON) — all from one place.
Add a database connection
Open the Data panel → Add → Database, paste a connection string, and choose an access mode:
postgres://user:password@host:5432/dbname
mysql://user:password@host:3306/dbname
duckdb::memory: # an in-process analytics / federation engine
Likeable detects the engine, stores the connection string encrypted at rest, and shows only a credential-free summary afterwards.
The connection string is a secret — enter it in the Data panel, never in chat. Rotate it with "Update connection string" on the connection.
Access mode — an explicit choice
Every database connection is read-only by default: queries run inside a READ ONLY transaction (and only
SELECT / WITH / SHOW / EXPLAIN are allowed), with a statement timeout and a row cap. Values bind
positionally ($1 / ?) — never string-interpolated — so inputs are data, not code.
When you create a connection you can switch it to read-write — a deliberate choice that lets that connection's queries run mutations and DDL, and commit them. Every other guard (single statement, parameter binding, timeout, row cap) still applies. Choose read-write only when a dashboard genuinely needs to write back.
Whose credentials — shared or per-viewer
When you create a connection you also choose whose credentials run its queries:
- Shared (default) — one org-wide connection string; every viewer queries through it. Simplest; enforce any row limits in the SQL/view.
- Per-viewer — each signed-in user connects their own credentials (their own DSN), stored encrypted per user, and their queries run as them — so the source's own row-level security applies. A user who hasn't connected their credentials is asked to before they can query; there's no fallback to a shared credential. Set yours from the Data panel ("🔑 connect your credentials" on the connection).
Query a database
Use the SqlData brick: set the connection, a SELECT, and a target key for the rows, then bind a chart or
table to that key. Bind params live to filter inputs so changing a filter re-queries. See
Datasets & filters and Build a dashboard. The agent can test a
query for you — confirming it works and learning the column names — before wiring it up.
Federate across sources
A DuckDB connection can attach your other database connections by reference and query across them in one
statement — join your Postgres orders to your MySQL customers, or to a Parquet file in object storage. The
attached database's credentials are resolved and injected server-side (always READ ONLY); they never
appear in the SQL. Reading files over https / s3 is gated by the same SSRF guard the proxy uses, plus an
allow-list of hosts. Object-store credentials (S3 / R2 / MinIO) are stored encrypted on the connection and
injected server-side as a DuckDB secret — never in the SQL.
Iceberg (lakehouse). A DuckDB connection can attach an Iceberg REST catalog (its endpoint + the
object-store credentials for the data files); DuckDB then reads your Iceberg tables as <catalog>.<namespace>.<table>.
The catalog is attached READ ONLY, server-side. (Verified end-to-end against a containerized REST catalog +
MinIO — see the integration tests.)
-- with a Postgres connection attached as `pg`, joined to a Parquet file in object storage
SELECT o.region, sum(o.amount) AS total
FROM pg.public.orders o
JOIN read_parquet('https://data.example.com/targets.parquet') t USING (region)
GROUP BY o.region;
Supported engines
Postgres, MySQL, and DuckDB today, through a pluggable driver registry. DuckDB is also the federation +
analytics engine (its postgres / mysql scanners and httpfs), and powers in-browser data manipulation —
see Mortar.
Local-first files
The LocalFileSource brick reads a CSV / Parquet / JSON the user picks from their own machine in the browser (DuckDB-Wasm — no upload, no server) into a dataset. It's offline-capable, and charts, tables, and SQL transforms bind to it like any other dataset.
Next steps
- Build a dashboard
- Datasets & filters
- Mortar — transforms & SQL
- AI chat & agents — let an assistant answer from this data (text-to-SQL / RAG)