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. 1

    Upload

    Drag in a .csv or .xlsx, or paste rows. The first row is treated as headers.
  2. 2

    Check the inferred column types

    A column of 2024-03-01 must 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. 3

    Name it for the model

    monthly_revenue is chosen correctly by agents far more often than sheet1. Lower case, underscores, no spaces.

Why it works this way

Table and column names are part of your prompt. When an agent has several tables attached, the schema is the only thing it has to go on — so naming is a functional improvement, not cosmetics.

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

Any field below can be filled from Secrets instead of being typed in, so rotating a password is one edit rather than a hunt through every connection.

PostgreSQL

FieldRequiredExample / notes
hostYesdb.example.com
portNoDefaults to 5432
databaseYesanalytics
usernameYesCreate a read-only role for this
passwordYes
sslNoSet to require to enable TLS. Needed by most managed hosts (RDS, Cloud SQL, Neon, Supabase).

MySQL / MariaDB

FieldRequiredExample / notes
hostYesdb.example.com
portNoDefaults to 3306
databaseYesanalytics
usernameYes
passwordYes
sslNoSet to require for TLS.

Nine more databases use exactly the fields above

Most “new databases” are not new protocols. Each of these is a first-class entry in the picker with its own name, logo and default port, but it speaks a wire protocol we already support, so it takes the same fields and shares the same proven driver:
  • PostgreSQL fields — CockroachDB, TimescaleDB, AlloyDB, Greenplum, YugabyteDB
  • MySQL fields — MariaDB, SingleStore, StarRocks, Apache Doris, PlanetScale
Pick the one you actually run — the port default and the docs differ even where the driver does not.

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.

FieldRequiredExample / notes
hostYessql.example.com or acme.database.windows.net
portNoDefaults to 1433. Leave blank if using a named instance.
databaseYesanalytics
usernameYes
passwordYes
instance_nameNoA 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_certificateNoSet 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.

FieldRequiredExample / notes
urlYesBase URL of the HTTP interface, e.g. https://abc.clickhouse.cloud:8443
usernameYesdefault
passwordYes
databaseNoScopes table browsing

Snowflake

Uses a programmatic access token (PAT), not a password — generate one in Snowflake under your user's settings.

FieldRequiredExample / notes
accountYesAccount identifier: xy12345.eu-west-1 or myorg-myaccount
tokenYesThe PAT
warehouseYesCOMPUTE_WH — the compute that runs the query
databaseYesANALYTICS
schemaNoPUBLIC — scopes table browsing
roleNoDefaults 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.

FieldRequiredExample / notes
hostYeshttps://dbc-xxxx.cloud.databricks.com
warehouse_idYesFrom Connection Details
tokenYesPersonal access token
catalogNoUnity Catalog catalog name
schemaNoDefault schema

Google BigQuery

FieldRequiredExample / notes
project_idYesmy-gcp-project
service_account_jsonYesThe FULL service-account key JSON, pasted as a string. Grant it BigQuery Data Viewer + Job User.
locationNoUS, EU or a region like us-central1. Used for jobs and region-wide table listing.
datasetNoRestricts 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.

FieldRequiredExample / notes
regionYesus-east-1
access_key_idYes
secret_access_keyYes
databaseYesdev
workgroup_nameServerlessServerless workgroup name
cluster_identifierProvisionedCluster id
db_userProvisionedDatabase user for the cluster

Azure Synapse (dedicated SQL pool)

FieldRequiredExample / notes
serverYesmyworkspace.sql.azuresynapse.net
databaseYesPool name
usernameYes
passwordYes

Trino / Starburst / Presto

FieldRequiredExample / notes
hostYestrino.example.com — hostname only, no scheme
portNoDefaults to 443 with TLS, 8080 plain
usernameYesRequired by the protocol (sent as X-Trino-User)
passwordNoBasic auth; optional on anonymous coordinators
access_tokenNoJWT/OAuth2 bearer — takes precedence over password when set
catalogNoiceberg, hive, delta …
schemaNoDefault schema
sslNoSet to disable for plain HTTP; anything else is HTTPS (the default).

Amazon Athena

FieldRequiredExample / notes
regionYesus-east-1
access_key_idYes
secret_access_keyYes
session_tokenNoFor temporary STS credentials
databaseNoGlue database queried by default; also scopes browsing
catalogNoDefaults to AwsDataCatalog
workgroupNoDefaults to primary
output_locationUsuallys3://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.

FieldRequiredExample / notes
ords_urlYesBase URL from Database Actions, e.g. https://<id>-<db>.adb.<region>.oraclecloudapps.com/ords
usernameYesDB user, HTTP Basic against the REST-enabled schema
passwordYes
schemaNoURL 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

A Parquet file keeps its schema in the FOOTER, so the head-of-file sample that infers columns for CSV cannot see it — Parquet files used to appear in the catalog as a name and a size with no columns at all. The crawler now reads the footer directly, which also gives an exact row count without scanning the file. Nothing is downloaded to do it.

Bucket queries read up to 50,000 rows per file

A bucket query is not pushed down. The referenced files are read up to a per-file cap and the query runs locally, so a filter does not reduce what is fetched. When a file hits that cap the Workbench says which one, because the answer is then over a prefix of the file rather than all of it — narrow the source data, or use a warehouse connection, if you need a query over more than that.

Why bucket SQL does not run against the bucket

The engine that can reach 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

Every connector above will happily accept an admin credential. Don't give it one — the platform only ever issues SELECT, so a read-only role loses you nothing and bounds the blast radius of a leaked secret.

Direct query vs import

ModeFreshnessCostUse for
Direct queryAlways currentA round trip to the source per queryOperational checks; data that changes minute to minute
Import (snapshot)As of the last refreshCheap and fast to re-queryDashboards 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

Grants are resolved fresh on every call, including scheduled refreshes, so nothing keeps working off a cached grant. Equally, a shared connection is a live dependency: revoking it stops the grantee's dashboards refreshing.

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.

AppAuthStreams
Google SheetsService-account JSONOne dataset per worksheet. Share the sheet with the key's client_email, or Google returns 403 however valid the key is.
StripeSecret or restricted keyCharges, customers, invoices, subscriptions, payment intents, products, prices, refunds, payouts, balance transactions
ShopifyAdmin API access tokenOrders, customers, products, draft orders, price rules
HubSpotPrivate app tokenContacts, companies, deals, tickets, line items, products
SalesforceConnected 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

That is the right semantic for a source whose rows are edited and deleted in place — an append would resurrect deleted rows for ever. The previous contents are snapshotted as a restorable version first, so a sync that pulls a truncated source is recoverable. Syncs run on demand or hourly / daily / weekly, and you are notified if one fails or comes back partial.

Staying connected

Three things run underneath every connection without being asked for. All are tunable by whoever runs the instance — see self-hosting.

What happensWhat you see
Health checksEvery 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 ageHow 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.
RetriesA 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?

If your network has no direct route to the internet, the person running the instance sets 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

FeatureWhat it gives you
Column profilesRow counts, null rates, distinct values, min/max per column — the fastest way to spot a column that is 90% empty before charting it.
AI descriptionsGenerated plain-English descriptions for tables and columns. Agents read these too, so a described catalog measurably improves tool choice.
LineageWhat a dataset came from and what depends on it — prep flows, dashboards, metrics. Check before changing or deleting anything.
Business glossaryDefine terms once ("active customer") and attach them to columns so the definition travels with the data.
Change detectionScheduled crawls report new, changed and removed columns, so an altered upstream schema surfaces as a change rather than a broken dashboard.
Owner & statusWho 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.

sql
-- 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

It is the same engine in both places, which is the point. Local queries used to run on a different in-browser engine that lacked window functions, so a question like “what percentage of total sales does each region account for?” could return a different answer in the workspace than on a schedule — and did so silently: a running total came back as zero for every row rather than erroring. Window functions, CTEs and correlated subqueries all work now, and they behave identically wherever they run.

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

A Content-Security-Policy or corporate proxy that blocks WebAssembly or web workers will stop it loading, and the strip says so rather than leaving Run looking broken. Open /engine-check — it reports which stage failed and which build your browser selected, which is normally enough for whoever administers the policy to fix it.

Giving an agent access

  1. 1

    Open the agent → Tools

    Enable sql_query. Also enable calculator — see the warning below.
  2. 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. 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. 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

Only 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

SymptomCauseFix
"Table does not exist"Not in Allowed tables, or the agent guessed a nameAdd it to Allowed tables; rename cryptic tables; add AI descriptions.
It queried the wrong tableTwo similar names, no descriptionsGenerate AI descriptions — they are what the model disambiguates on.
Date filters do nothingColumn imported as textChange the column type on the table to date.
Athena connection failsNo query result locationSet output_location to an S3 prefix, or configure one on the workgroup.
Managed Postgres refuses to connectTLS not enabledSet ssl to require.
Snowflake auth failsUsing a passwordThis connector expects a programmatic access token (PAT), not your account password.
Connector test fails on a private hostOutbound guardRequests to private and link-local addresses are refused. The database must be reachable from wherever the app runs — see Install & deploy.
Agent totals are wrongIt did arithmetic itselfEnable the calculator tool and set temperature to 0.