Data & analytics
Data Catalog & SQL
Everything tabular: uploaded files, 22 databases and warehouses, 5 app sources, the catalog that describes them, and the workbench that queries them.
Open Data → Data Catalog.
Local tables
- 1
Upload
Drag in a.csvor.xlsx, or paste rows. The first row is treated as headers. - 2
Check the inferred column types
A column of2024-03-01must be date, not text, or date filters and time-series charts will not work on it later. Fix it now — it is far more annoying after dashboards are built. - 3
Name it for the model
monthly_revenueis chosen correctly by agents far more often thansheet1. Lower case, underscores, no spaces.
Why it works this way
External connections — every field
Configure these under Integrations → Data Sources → Add connection. Every connector has a Name (your label) and a Test connection button; the last test result and its error are kept on the connection so you can see when it started failing. All secrets are encrypted at rest and never returned to the browser.
Note
PostgreSQL
| Field | Required | Example / notes |
|---|---|---|
host | Yes | db.example.com |
port | No | Defaults to 5432 |
database | Yes | analytics |
username | Yes | Create a read-only role for this |
password | Yes | — |
ssl | No | Set to require to enable TLS. Needed by most managed hosts (RDS, Cloud SQL, Neon, Supabase). |
MySQL / MariaDB
| Field | Required | Example / notes |
|---|---|---|
host | Yes | db.example.com |
port | No | Defaults to 3306 |
database | Yes | analytics |
username | Yes | — |
password | Yes | — |
ssl | No | Set to require for TLS. |
Nine more databases use exactly the fields above
- PostgreSQL fields — CockroachDB, TimescaleDB, AlloyDB, Greenplum, YugabyteDB
- MySQL fields — MariaDB, SingleStore, StarRocks, Apache Doris, PlanetScale
Microsoft SQL Server / Azure SQL
Speaks TDS through the tedious driver, so it needs a Node or Docker deployment — there is no REST SQL API to fall back on.
| Field | Required | Example / notes |
|---|---|---|
host | Yes | sql.example.com or acme.database.windows.net |
port | No | Defaults to 1433. Leave blank if using a named instance. |
database | Yes | analytics |
username | Yes | — |
password | Yes | — |
instance_name | No | A named instance such as SQLEXPRESS. Mutually exclusive with a port — send both and the instance is ignored, which silently connects you to the wrong server. |
trust_server_certificate | No | Set true for an on-prem server with a self-signed certificate. Leave off for Azure SQL, which never needs it. |
ClickHouse
Uses the HTTP interface, so it works on any deployment.
| Field | Required | Example / notes |
|---|---|---|
url | Yes | Base URL of the HTTP interface, e.g. https://abc.clickhouse.cloud:8443 |
username | Yes | default |
password | Yes | — |
database | No | Scopes table browsing |
Snowflake
Uses a programmatic access token (PAT), not a password — generate one in Snowflake under your user's settings.
| Field | Required | Example / notes |
|---|---|---|
account | Yes | Account identifier: xy12345.eu-west-1 or myorg-myaccount |
token | Yes | The PAT |
warehouse | Yes | COMPUTE_WH — the compute that runs the query |
database | Yes | ANALYTICS |
schema | No | PUBLIC — scopes table browsing |
role | No | Defaults to the user's default role. Set it explicitly for least privilege. |
Databricks SQL
The warehouse_id comes from the SQL warehouse's Connection Details tab.
| Field | Required | Example / notes |
|---|---|---|
host | Yes | https://dbc-xxxx.cloud.databricks.com |
warehouse_id | Yes | From Connection Details |
token | Yes | Personal access token |
catalog | No | Unity Catalog catalog name |
schema | No | Default schema |
Google BigQuery
| Field | Required | Example / notes |
|---|---|---|
project_id | Yes | my-gcp-project |
service_account_json | Yes | The FULL service-account key JSON, pasted as a string. Grant it BigQuery Data Viewer + Job User. |
location | No | US, EU or a region like us-central1. Used for jobs and region-wide table listing. |
dataset | No | Restricts browsing to one dataset |
Amazon Redshift
Two shapes: Serverless (set workgroup_name) or provisioned (set cluster_identifier and db_user). Fill one pair, not both.
| Field | Required | Example / notes |
|---|---|---|
region | Yes | us-east-1 |
access_key_id | Yes | — |
secret_access_key | Yes | — |
database | Yes | dev |
workgroup_name | Serverless | Serverless workgroup name |
cluster_identifier | Provisioned | Cluster id |
db_user | Provisioned | Database user for the cluster |
Azure Synapse (dedicated SQL pool)
| Field | Required | Example / notes |
|---|---|---|
server | Yes | myworkspace.sql.azuresynapse.net |
database | Yes | Pool name |
username | Yes | — |
password | Yes | — |
Trino / Starburst / Presto
| Field | Required | Example / notes |
|---|---|---|
host | Yes | trino.example.com — hostname only, no scheme |
port | No | Defaults to 443 with TLS, 8080 plain |
username | Yes | Required by the protocol (sent as X-Trino-User) |
password | No | Basic auth; optional on anonymous coordinators |
access_token | No | JWT/OAuth2 bearer — takes precedence over password when set |
catalog | No | iceberg, hive, delta … |
schema | No | Default schema |
ssl | No | Set to disable for plain HTTP; anything else is HTTPS (the default). |
Amazon Athena
| Field | Required | Example / notes |
|---|---|---|
region | Yes | us-east-1 |
access_key_id | Yes | — |
secret_access_key | Yes | — |
session_token | No | For temporary STS credentials |
database | No | Glue database queried by default; also scopes browsing |
catalog | No | Defaults to AwsDataCatalog |
workgroup | No | Defaults to primary |
output_location | Usually | s3://bucket/prefix/ for query results. Required unless the workgroup already sets one — the most common cause of a failing Athena connection. |
Oracle Database / Autonomous DB
Connects over ORDS — plain HTTPS, so no wallet and no Instant Client. Autonomous Database ships ORDS enabled.
| Field | Required | Example / notes |
|---|---|---|
ords_url | Yes | Base URL from Database Actions, e.g. https://<id>-<db>.adb.<region>.oraclecloudapps.com/ords |
username | Yes | DB user, HTTP Basic against the REST-enabled schema |
password | Yes | — |
schema | No | URL schema-alias segment from ORDS.ENABLE_SCHEMA. Defaults to the lower-cased username, which is ORDS's own default. |
Object stores and lakehouse catalogs
S3, Cloudflare R2, MinIO, DigitalOcean Spaces, Backblaze B2, GCS over its S3 API and any other S3-compatible endpoint are added as catalog sources through Data Catalog → Add source. A crawl lists the bucket, groups a folder of same-format files into one dataset, and records each file’s columns. Iceberg REST and Unity Catalog are connected for metadata only.
Parquet, CSV, JSON and NDJSON files are queryable. Press Query data on a file in the catalog and it opens in the Workbench with the bucket selected as the engine. The SQL name is the file’s basename without its extension, so data/orders.parquet is orders; a partitioned folder sales/*.parquet is sales. Files in the same bucket can be joined, including across formats — a Parquet fact table against a CSV lookup is an ordinary query.
Where a Parquet schema comes from
Bucket queries read up to 50,000 rows per file
Why bucket SQL does not run against the bucket
s3:// needs network access, and DuckDB offers no setting that grants that while denying the local filesystem — so an engine able to read your bucket could also read the server’s own files. Your SQL therefore runs in the sandboxed engine over rows fetched for it, and only queries the platform composes itself ever reach the networked one. That is also why a query naming a file the catalog has not crawled is refused by name rather than attempted.Create a read-only user
SELECT, so a read-only role loses you nothing and bounds the blast radius of a leaked secret.Direct query vs import
| Mode | Freshness | Cost | Use for |
|---|---|---|---|
| Direct query | Always current | A round trip to the source per query | Operational checks; data that changes minute to minute |
| Import (snapshot) | As of the last refresh | Cheap and fast to re-query | Dashboards many people open; anything charted repeatedly |
Sharing a connection with your team
A connection belongs to whoever created it. Rather than every analyst creating their own — several copies of one credential, each rotated separately, each a place it can leak — a superadmin can share it under Admin → IAM → Access, to a user or a group, as either a Database / warehouse connection or an App source.
A shared connection runs as its owner. The credential is the connection — a grantee has none of their own — so the owner's credential is decrypted server-side and the query runs against the owner's warehouse. A grantee can query it, test it and see its health; they cannot see the credential, edit it or delete it. {{secret:NAME}} references resolve as the owner, never against the grantee's own vault.
Revocation takes effect on the next use
A shared app source syncs as its owner, into the owner's datasets — so a teammate who notices stale data can re-run it and refresh the real datasets rather than building a parallel copy under their own account. The audit entry records both who triggered it and whose data moved. Sharing the source lets someone keep it healthy; to let them read the resulting data, share those datasets too.
Apps — SaaS sources
Databases are queried in place. Apps have no query language, so they are pulled into datasets instead: Integrations → Apps → connect, discover what is in there, choose what to sync. Each stream becomes its own dataset and is then indistinguishable from an uploaded CSV — same type inference, same version history, same use in BI, prep flows and the semantic layer.
| App | Auth | Streams |
|---|---|---|
| Google Sheets | Service-account JSON | One dataset per worksheet. Share the sheet with the key's client_email, or Google returns 403 however valid the key is. |
| Stripe | Secret or restricted key | Charges, customers, invoices, subscriptions, payment intents, products, prices, refunds, payouts, balance transactions |
| Shopify | Admin API access token | Orders, customers, products, draft orders, price rules |
| HubSpot | Private app token | Contacts, companies, deals, tickets, line items, products |
| Salesforce | Connected app (client credentials) | Accounts, contacts, leads, opportunities, cases, campaigns, users |
Auth is a pasted credential, never OAuth: a redirect flow needs a public callback URL that a self-hosted deployment behind a firewall may not have, so each connector uses the vendor's server-to-server credential instead.
A sync replaces its dataset
Staying connected
Three things run underneath every connection without being asked for. All are tunable by whoever runs the instance — see self-hosting.
| What happens | What you see | |
|---|---|---|
| Health checks | Every connection is re-tested on a schedule with the same probe the Test button uses. A warehouse password that expires on your company’s rotation policy is found by us, not by a dashboard erroring in front of a customer. | A Failing badge in Integrations, one notification when it breaks and one when it recovers — not one per check. |
| Credential age | How long ago the stored secret was entered. Re-saving a connection resets it; a health check does not. | An “N d old” badge once it passes the policy age (90 days by default). Advisory — nothing expires or stops working. |
| Retries | A rate limit or a brief outage from a provider is retried with backoff rather than failed. Retries are always reads, so nothing can be double-written. | Nothing — that is the point. A tile that would have errored simply loads. |
Behind a corporate proxy?
HTTPS_PROXY and NO_PROXY and every connector follows them. Without it, reaching Snowflake or Stripe fails as a connection timeout rather than anything that names the real cause — so it is worth checking first if a connector that should work does not.The catalog
| Feature | What it gives you |
|---|---|
| Column profiles | Row counts, null rates, distinct values, min/max per column — the fastest way to spot a column that is 90% empty before charting it. |
| AI descriptions | Generated plain-English descriptions for tables and columns. Agents read these too, so a described catalog measurably improves tool choice. |
| Lineage | What a dataset came from and what depends on it — prep flows, dashboards, metrics. Check before changing or deleting anything. |
| Business glossary | Define terms once ("active customer") and attach them to columns so the definition travels with the data. |
| Change detection | Scheduled crawls report new, changed and removed columns, so an altered upstream schema surfaces as a change rather than a broken dashboard. |
| Owner & status | Who owns it and when it was last crawled. |
Query data on any dataset opens it in the workbench with the table loaded.
SQL workbench
Write SQL against anything connected. Results can be charted, added to a dashboard, or exported to CSV/Excel.
-- Monthly revenue and order count for the last 12 months
SELECT date_trunc('month', o.created_at) AS month,
COUNT(*) AS orders,
SUM(o.amount) AS revenue
FROM orders o
WHERE o.status = 'settled'
AND o.created_at >= current_date - INTERVAL '12 months'
GROUP BY 1
ORDER BY 1;- Ask in English — describe the question and the assistant writes the SQL. Read it before running: it shows you exactly which join and filter the answer depends on.
- Results are capped for display, with the true match count shown, so a runaway query cannot hang the page.
Which engine runs your SQL
Queries against a connected warehouse run on that warehouse. Queries against uploaded tables and prepared datasets run on DuckDB — in your browser for the workbench and Ask AI, and on the server for scheduled refreshes, prep flows and agent tools.
Why it works this way
The first query in a session is slower
The engine is WebAssembly — roughly 8 MB, fetched once and then cached by your browser for every later visit. You will see “Starting the SQL engine…” with a progress bar while that happens.
- It starts as soon as you open a data page, not when you press Run, so it usually finishes while you are still choosing a table.
- It downloads once per browser, not per query, per dataset or per tab.
- It is served from your own deployment, never a CDN — so an air-gapped install works, and no third party sees that you loaded it.
If it never finishes
Giving an agent access
- 1
Open the agent → Tools
Enablesql_query. Also enablecalculator— see the warning below. - 2
Set Allowed tables
Only tables in this list are visible to the agent. Leaving it empty means sql_query has nothing to query. - 3
Mention the data in the system prompt
e.g. "Use sql_query for anything involving counts, totals or dates. Never estimate a number you could compute." - 4
Test with a counting question
Ask for a total. Check the sources under the answer name the table — if they name a document instead, it answered from prose.
At run time the agent sees each table's name, columns and a small sample of rows, writes a SELECT, and gets real rows back.
Read-only, and scoped to you
SELECT is accepted — writes and DDL are rejected before execution. Queries run under your identity and row-level security, so an agent cannot read a table you cannot. When an agent runs for an anonymous embed visitor it is explicitly scoped to the owner's data.Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| "Table does not exist" | Not in Allowed tables, or the agent guessed a name | Add it to Allowed tables; rename cryptic tables; add AI descriptions. |
| It queried the wrong table | Two similar names, no descriptions | Generate AI descriptions — they are what the model disambiguates on. |
| Date filters do nothing | Column imported as text | Change the column type on the table to date. |
| Athena connection fails | No query result location | Set output_location to an S3 prefix, or configure one on the workgroup. |
| Managed Postgres refuses to connect | TLS not enabled | Set ssl to require. |
| Snowflake auth fails | Using a password | This connector expects a programmatic access token (PAT), not your account password. |
| Connector test fails on a private host | Outbound guard | Requests to private and link-local addresses are refused. The database must be reachable from wherever the app runs — see Install & deploy. |
| Agent totals are wrong | It did arithmetic itself | Enable the calculator tool and set temperature to 0. |