The influxdb3 type-id: read-only browsing and querying of InfluxDB 3 in SQL, over /api/v3/query_sql, tested on InfluxDB 3 Core 3.12.0.
Source: src/lib/db/providers/timeseries/influxdb/, the sql-* modules and the shared connection layer.
Tests: tests/unit/db/influxdb/ and tests/integration/db/influxdb3-provider.test.ts.
| Write reach | none: the SQL policy, evaluateInfluxSql, refuses every statement that does not lead with a read keyword before any request, and the route table reaches no write endpoint; there is no object edit, maintenance or transaction route |
The connection type is labelled "InfluxDB 3 (SQL)" in the dialog, and it reads InfluxDB 3 through the SQL its server runs, Apache DataFusion. It lists the tables of one database, describes their columns, previews their newest rows, and runs one read statement per request. It writes nothing: no route Studio can send writes, the read policy refuses every writing statement before the request, and InfluxDB 3 Core's planner refuses them as well. To query InfluxDB 1.x, 2.x or 3.x in InfluxQL, use the InfluxDB (InfluxQL) type, influxdb.md.
| InfluxDB 3 | Studio |
|---|---|
| One server | One connection, which reads one database of it, the session database |
| A database | The session database: the connection's Database field, or the only one the token lists; no tree node |
| A table (a measurement) | A row of Tables at the top of the tree |
A tag, a field, time |
A column, typed tag, the field's type, or time |
| A row | A row of the grid |
| A token | The Token field, sent as a bearer token |
Tested against InfluxDB 3 Core 3.12.0, image influxdb:3.12.0-core@sha256:624d69bca6bf6fb174aca5a974e1d96e5c5486994e6ae35bc9b4fe02b9520076, captured 2026-10-04.
InfluxDB 3 Enterprise speaks the same API and is not tested; its database tokens are handled from InfluxData's documentation (D201).
InfluxDB Cloud Serverless and Cloud Dedicated serve SQL only over Arrow Flight, which Studio does not use, so this type cannot reach them: connect to them with InfluxDB (InfluxQL) (D202).
One directory, src/lib/db/providers/timeseries/influxdb/, serves two type-ids with two independent classes over one shared connection layer: influxdb with InfluxDBProvider, documented in influxdb.md, and influxdb3 with InfluxDB3Provider, documented here.
InfluxDB3Provider extends SQLBaseProvider, so the shared SQL limiter, statement splitter and generators serve it under the DataFusion grammar row.
sql-provider.ts is the one file of the directory that imports a module under src/lib/db/providers/ outside it, @/lib/db/providers/sql/sql-base, and the seam guard (tests/unit/db/influxdb/seam-guard.test.ts) allows that one import and no other.
No sql-* module imports an influxql-* module, and no influxql-* module imports an sql-* one.
| File | What it owns |
|---|---|
sql-provider.ts |
InfluxDB3Provider: the declarations, the connect sequence, each call's permit, deadline and error context |
sql-policy.ts |
The read policy: one statement, six leading keywords, no writing word, the text cap |
sql-results.ts |
A jsonl answer to fields and rows, with exact integers, the row cut and the cell budget |
sql-objects.ts |
The database listing, the table listing, the column read, the one-segment path rule |
connection-options.ts |
Shared: the endpoint, the bearer header, the plaintext rule and its consent, TLS, the bounds |
routes.ts, client.ts |
Shared: the closed route tables, and the only client that builds a request from them |
versions.ts, errors.ts |
Shared: the version from /ping and /health, and the one error table of both types |
run-database.ts, monitoring.ts, labels.ts |
Shared: the session database, the overview and health, the labels of both types |
index.ts |
The two provider classes the factory imports, and nothing else |
The constructor validates nothing and opens nothing, so a provider built from any connection answers its capabilities and labels.
connect() builds the options, then reads GET /ping (and GET /health when /ping names no version), then the database listing, then settles the session database; each read shares one surface deadline, and a failure closes the client.
disconnect() closes the client, which stops every request in flight.
The shared transport closes a pooled socket left idle for 4 s itself, below a server keep-alive such as Qdrant's 5 s, so a request is never written on a socket the server is closing (#1419); a request in flight is not cut.
The transport never goes through an http_proxy or https_proxy variable and never follows a redirect: use an SSH tunnel to reach a private endpoint.
Studio sends no write to InfluxDB 3: no line protocol write, no database, table, token, cache, plugin or trigger route exists in the route table, and no maintenance operation is offered.
The read-only mode is declared (READ_ONLY_ENFORCED, and a writeReach of none), and every statement passes the read policy before any request whatever the connection's readOnly says.
Three layers stand between a statement and a write.
The closed route table holds four routes, of which POST /api/v3/query_sql is the only one that carries a statement, so no request can reach a write endpoint.
The read policy (section 5.3) admits one statement that leads with a read keyword and holds no writing word, and refuses everything else before the request.
InfluxDB 3 Core's planner refuses every SQL write form on the query route, captured on 3.12.0 (section 3.5); the policy does not rest on that staying true.
The client is Studio's own, over the shared node:http(s) transport, and it adds no package.
@influxdata/influxdb3-client reads over Arrow Flight, which rounds 64-bit integers and nanosecond times to what JavaScript numbers hold, offers no cancel and no per-connection CA, and opens gRPC sockets outside the HTTP egress guard; @influxdata/influxdb-client queries Flux only.
Reading query_sql as jsonl keeps every integer exact and every request inside the egress guard, at the cost of exact column types and a server-side cancel, filed as D197.
MCP is offered (MCP_EXPOSABLE): list_connections and inspect_schema see the session database's tables and columns, as the tree does, and never _internal or a system.* table.
Agent mode executes statements on this type under the agent read-only profile (section 3.5), and MCP's run_read_query runs them under the same profile (MCP_STATEMENT_READ.influxdb3 is true), described in "MCP read" in section 3.5.
Plan mode drafts statements in the language section 5.1 names.
Agent mode executes statements on PostgreSQL, SQLite, DuckDB, SQL Server, MySQL, Kubernetes, MongoDB, ClickHouse, libSQL, InfluxDB 3 (SQL), Elasticsearch and OpenSearch; here the provider implements queryReadOnly in sql-provider.ts, with its own rules in sql-agent-read.ts.
The boundary is engine-enforced, by the planner.
POST /api/v3/query_sql carries free statement text, so the closed route table alone proves nothing about it, and Core mints only admin tokens, so no credential can carry the boundary either.
What holds it is InfluxDB 3.12 Core's planner, which refuses every write, DDL, COPY, SET, PREPARE and transaction statement on that route, whatever the token.
The read policy (section 5.3) and the system schema fence below are parser-only defence in front of it: they refuse a text before it is sent, and the boundary does not rest on them.
bun tests/live/influxdb3-write-refusals.ts sent 43 statements raw to query_sql as the admin token, with no read policy in front, on 2026-10-29, and wrote each answer to tests/fixtures/influxdb/3.12.0-core/write-refusal/.
Every one is refused with a 400 Error during planning: DML not supported: ..., DDL not supported: ... or Statement not supported: ..., or a 405 This feature is not implemented: ..., and the server read back unchanged after each: its databases, every table and column, the seeded row counts, the token table, and no file at a path a COPY or an external table named.
They cover INSERT (VALUES, SELECT, into system.queries), CREATE TABLE (plain, AS, SELECT ... INTO), CREATE EXTERNAL TABLE, VIEW, SCHEMA, DATABASE, FUNCTION and INDEX, DROP TABLE, VIEW, SCHEMA, DATABASE and FUNCTION, DELETE, UPDATE, TRUNCATE, MERGE, COPY of a table and of a query, ALTER TABLE, SET, SET TIME ZONE, RESET, PREPARE, EXECUTE, DEALLOCATE, BEGIN, START TRANSACTION, COMMIT, ROLLBACK, VACUUM and ANALYZE TABLE, and the forms behind a read keyword: EXPLAIN INSERT, EXPLAIN ANALYZE INSERT, EXPLAIN ANALYZE COPY, WITH ... INSERT and a second statement after a ;.
Two more captures record the token: the token table of _internal lists one token, with the permission *:*:*, and the Enterprise route that mints a database-scoped token answers 404 on Core.
tests/integration/db/influxdb3-provider.test.ts replays every capture: the request is the one Studio's client builds for that text, the read policy refuses the text, and an agent read the planner answers that way is refused in the server's own words.
The check runs in connect() only when the execution context sets both readOnly and sendsStatements, the agent and hand-over profiles.
The server's /ping must name InfluxDB 3 Core on a line in INFLUXDB3_CHECKED_RELEASES, today 3.12, the line the captures were taken on; any other version, an Enterprise server, and a server that names no version (a resource token's /ping 403) are refused PROFILE_UNSUPPORTED_TARGET until their own refusals are captured.
The operations profile and plan mode send no statement of the model's, so they open unchecked, and a plan run grounds under the operations profile and never asks for the read-only one.
queryReadOnly refuses on any handle opened without that context, an editor handle included, before it sends anything.
The agent credential takes the token form: the agent token alone, sent as the bearer credential in place of the connection's own, with an agent user beside it refused, since this type takes no user. This type has no connection string, so an agent token is never beside one. On Core every token is an admin token, so an agent token changes who the server logs, never what the planner refuses.
Each call is one statement in one query_sql request, never through query(): that path cuts an answer to its fixed bounds and reports the cut, which a check of the returned rows against the agent's budget could not see.
In order: the budget is checked, the estimate-plan session mode is refused (the estimating plan is the EXPLAIN prefix the agent composes, sent in the ordinary execute mode), the read policy runs, and the system schema fence runs.
The fence refuses any name system followed by a . unless influxdb_schema follows it, whatever its case, its quoting or what stands before it (public.system.queries): system.queries holds the text of every query the server ran, system.parquet_files the store's file paths and system.processing_engine_logs the plugins' output, and the engine serves them to any Core token.
The server also reads a string before a . as a name ('system'.queries answers the rows of system.queries), so the fence refuses any string followed by a ., whatever it holds.
What the read policy or the fence refuses is a RefusedBeforeSendError, since nothing was sent, so the agent rail shows it as refused before the database, in Studio's words, never as the server's error.
The request's deadline is the budget's, and its response cap is three times the byte budget plus 64 KiB, since a jsonl line is at most three times the bytes of the row it becomes (a character outside ASCII written as a \uXXXX escape); an answer past the cap is refused as over the byte budget.
The answer is shaped in the strict mode of shapeJsonlBody: a row past the row budget, or a row that takes the answer past the 250,000-cell budget, throws the shared row refusal of read-only-budget.ts, naming every row the answer holds and the rows the bound admits, and the shaped rows are held to the byte budget, all before anything is returned.
A statement past its deadline is refused as over the time budget; the server's query is not cancelled (section 5.10).
bun tests/live/influxdb3-agent-boundary.ts, run on 2026-10-29 against the influxdb3 and influxdb3-filelimit services of section 12, passes 63 checks through the production path:
- the agent profile admits the 3.12.0 Core server, and the operations profile opens it;
- 24 writes, DDL,
COPY,SET, transaction and second-statement texts are each refused byqueryReadOnlybefore sending and, sent raw through the route-table client with the same token, refused by the planner with a 400 or a 405; - the databases, tables, columns, rows and tokens read back the same afterwards;
- the positive control: the same token writes a point through
/api/v3/write_lp, a route the provider's table does not hold, into a scratch database the agent then reads, and which the script deletes; system.queriesis refused andsystem.influxdb_schemaread;- a result past the row budget, one past the cell budget (
50000 rows > 41666 allowedfor six columns), one past the byte budget and a statement past its deadline are each refused, never truncated; - a read, an
EXPLAINand the catalog read answer their rows; - on the file-limit server, a read over more files than the limit is refused in the server's words.
MCP's run_read_query serves InfluxDB 3 (SQL) through the agent's own path: it takes the connection under the agent read-only profile, so the open checks the server is InfluxDB 3 Core on the 3.12 line, and sends the statement through queryReadOnly, after the read policy and the system schema fence, alone in one query_sql request.
It shares the agent's profiled handle, so the server check runs at the open of that handle, as in agent mode, not before each statement.
A seed may carry an agent token of its own as agentPassword (an agent credential), but on Core every token is an admin token, so whichever token is sent, the seed's or the agent's, the boundary is the planner's, never the token's, and an Enterprise server, a Core server outside the 3.12 line and a server that names no version are each refused at open, in the tool's sentence, before any statement runs.
MCP's statement fence refuses a write-shaped statement before anything is acquired, as defence in depth; it also refuses SHOW, DESCRIBE and EXPLAIN ANALYZE, which the read policy admits.
A read of a system table other than system.influxdb_schema, also with its schema written as a string, passes that fence and is refused by the provider's system fence before anything is sent.
Values are not withheld: rows reach the MCP client as they reach the agent.
It runs a SELECT (a WITH is fine), VALUES, or EXPLAIN without ANALYZE: TABLE is a parser error on 3.12.0 Core and the read policy refuses it, and EXPLAIN ANALYZE runs the statement; it pages with LIMIT n OFFSET m, and an offset on VALUES, EXPLAIN or a query with its own LIMIT is refused.
A SELECT without its own LIMIT is paged at max_rows (100 by default, at most 500), and pagination.hasMore and pagination.nextOffset say where the next page starts; a query whose own LIMIT is past 1000 rows, or a result past 1 MiB, is refused, never cut.
A read past timeout_ms is answered as the timeout: the provider closes the request at the deadline, and the server frees the statement's work (section 5.10).
A client that closes its request stops the wait only: the statement runs on until the provider closes its request at the time left of timeout_ms.
Measured on 2026-11-01 by watching the container: after a two-second timeout_ms the server was idle within two seconds, and after a cancel one second into a 15-second timeout_ms a generate_series count held more than one core until that deadline.
tests/live/influxdb3-mcp-boundary.ts replays the boundary record above through the tool's handler with its statement fence removed, against the influxdb3 and influxdb3-filelimit services of section 12 and an influxdb:3.11.0-core server started the same way, and passed on 2026-11-01 (153 checks).
The 3.12.0 Core server was served under agent-read-only, and the 3.11.0 Core server was refused at open through MCP.
The MCP fence refused all 43 captured statements of section 3.5 before anything was acquired; with it removed, the read policy refused each before it was sent, and each sent raw with the same token was refused by the planner with the status and the words its capture records.
system.queries, system.parquet_files, "system"."queries" and five forms that write the schema as a string ('system'.queries, 'system'.'queries', public.'system'.queries, 'public'.'system'.'queries' and 'system'."queries") passed the MCP fence and were refused by the system fence, while the server answered each sent raw; system.influxdb_schema was read.
The token wrote a point through /api/v3/write_lp into a scratch database that MCP read back and the script deleted, and a hash of the databases, tables, columns, row counts and tokens read back the same before and after.
VALUES and EXPLAIN answered, TABLE was refused by the read policy, a read paged with offset answered its page; a read with its own LIMIT past 1000 rows, one past 1 MiB and one past a one-second timeout_ms were each refused, the server answered the next call, and on the file-limit server a read over the limit was answered as the database's refusal.
LIBREDB_LIVE_INFLUXDB3_UNCHECKED_PORT=8183 bun tests/live/influxdb3-mcp-boundary.tsStatement-language family: sql.
Overlay: src/lib/agent/languages/sql/engines/influxdb3.ts, with its client row in src/lib/agent/languages/sql/engines/client.ts.
The agent reads it through runServerPack and agentEnginePack, and the dialect through engineServerDialect.
| Member | Kind | Value |
|---|---|---|
catalogRead |
Dialect-bound | Supplied |
planEstimate |
Dialect-bound | Supplied |
profileComposition |
Dialect-bound | "unsupported" |
statistics |
Dialect-bound | "unsupported" |
catalogCapture |
Dialect-bound | "unsupported" |
This overlay overrides no Shared member, so every Shared member is the sql statement-language family's own.
Every statement it composes is DataFusion's, run on 3.12.0 Core through the agent path: the catalog reads off information_schema in the iox schema, with DataFusion's 0-based column positions moved to 1-based, and the estimating plan is EXPLAIN, which plans without running.
The engine keeps no foreign key, no index and no row estimate outside the system schema, so those three catalog kinds answer no rows.
profile_table is offered and answers UNSUPPORTED_DIALECT, and compare_plans reads every plan as unknown, because the provider declares no explain strategy with an agent reading (B143).
A pack does not open agent mode: execution is gated by AGENT_EXECUTION_ENGINES and the execution-profile check.
MCP's run_read_query runs a statement on InfluxDB 3 (MCP_STATEMENT_READ.influxdb3 is true) under the same boundary, described in "MCP read" above.
| Field | Rule |
|---|---|
| Host | A name or address, or a pasted http:// or https:// address, which is split into Host and Port. InfluxDB Cloud endpoints are https on port 443. |
| Port | 8181 by default, the InfluxDB 3 HTTP port |
| Token | Empty only for a server started with --without-auth. On InfluxDB 3 Core every token is an admin token. |
| Database | The one InfluxDB 3 database this connection reads. Empty: the only database the token can list; with more than one, set it here. |
| Send the password without TLS | Ticked, the password or token crosses the network in cleartext to this host. On InfluxDB 3 Core every token is an admin token that reaches server-side code. Prefer TLS or an SSH tunnel; SSL mode require sends the token to a server whose certificate is not checked. |
| Read-only | Studio has no write path on this engine, so every connection to it is read-only. |
There is no User field: InfluxDB 3 has no user name.
The connection-string box is not offered, because it reads http:// and https:// as ClickHouse; paste the address into Host.
The token travels as one header, Authorization: Bearer <token>, on every request, and never in a URL, a body, an error or a log line.
A Token left empty sends no header, which only a server started with --without-auth accepts.
A user name from a seed file or the API is refused when the connection opens:
InfluxDB 3 has no user name: clear User and put the token in Token.
A token holding a character an HTTP header cannot carry is refused before any request, naming the field and never the value:
Token holds a character outside printable ASCII, which an HTTP header cannot carry; re-enter it.
A read-only seed with no token is refused when the file loads, because a server started with --without-auth keeps no boundary:
Credential warning: An InfluxDB 3 server started with --without-auth accepts any token or none, so a read-only seed without a token promises a boundary the server does not keep.
On InfluxDB 3 GET /ping needs the token and answers the product and version, "InfluxDB 3 Core" and "3.12.0" on the tested server.
InfluxDB 1.x and 2.x answer /ping with no body, so the provider then reads GET /health, whose version names the line, and refuses a 1.x or 2.x server before any /api/v3 call:
This server is InfluxDB 2.9.1, which has no SQL endpoint: connect with InfluxDB (InfluxQL).
A server with no SQL route answers the query or listing route with 404:
This server has no InfluxDB 3 SQL endpoint. InfluxDB Cloud Serverless and Dedicated serve SQL only over Flight, which Studio does not use: connect with InfluxDB (InfluxQL).
A server that answers the SQL route with something other than JSON, such as an HTML page, gets:
InfluxDB at [endpoint] answered the SQL request with [content type], not JSON, so it is not an InfluxDB 3 SQL endpoint; check Host, Port and the connection type, or connect with InfluxDB (InfluxQL).
A /ping answered 403 means a resource token, such as an InfluxDB 3 Enterprise database token: the connection goes on with no version and needs the Database field.
With Database empty it is refused:
This token cannot read the server's version, which an InfluxDB 3 Enterprise database token cannot; set Database on the connection.
A connection reads exactly one database, its session database, settled once when it connects.
It is the Database field when that is set; else the only database the token lists through GET /api/v3/configure/database?format=json, with _internal, _monitoring and _tasks never counted.
Otherwise the connection is refused, naming up to ten names, then "and N more", when the token lists several:
This InfluxDB 3 server has more than one database (bench, home), and a connection reads one: set Database on the connection.
This InfluxDB 3 token can list no database; create one on the server or set Database on the connection.
This token cannot list the server's databases, which an InfluxDB 3 Enterprise database token cannot; set Database on the connection.
A Database the readable listing does not hold, matched exactly and case-sensitively, is refused when the connection opens; a token that cannot read the listing connects as before, after a bounded SELECT 1 against the Database it names:
InfluxDB 3 has no database named [database], or this token cannot see it.
_internal holds the server's token table on InfluxDB 3, so it is never a session database:
Studio does not read the _internal database on this server; on InfluxDB 3 it holds the server's token table.
The tree shows the session database's tables at the top level with no database node, and the monitoring overview names the database (section 7). A server with several databases needs one connection per database; a session that reads several is U81.
A token over no TLS is refused before any socket unless the host is a loopback address or localhost, or an SSH tunnel carries the connection, because on InfluxDB 3 Core every token is an admin token that reaches server-side code:
This connection would send its password or token to InfluxDB without TLS, to a host that is not this machine, where anyone on the path can read it. Choose an SSL mode under SSL / TLS (docs/providers/influxdb.md has a TLS recipe for each version), connect through an SSH tunnel, or tick Send the password without TLS to accept that risk for this connection. Inside a container, localhost is the container itself, not the host. Nothing was sent.
With SSL mode disable the dialog offers "Send the password without TLS"; ticked, the refusal is lifted for that connection only.
A consent given for one host survives an edit of Host or Port (D204), so untick it when the connection moves.
A server with no token opens anywhere.
Start the server with a certificate and its key: influxdb3 serve --tls-cert <cert.pem> --tls-key <key.pem> with the server's other options.
A self-signed certificate for a test server comes from one line:
openssl req -x509 -newkey rsa:2048 -nodes -days 365 -keyout key.pem -out cert.pem -subj "/CN=<host>" -addext "subjectAltName=DNS:<host>"Then choose SSL mode verify-ca and paste cert.pem as the CA, which checks the server; or require, which encrypts and does not check the certificate, so it sends the admin token to whatever answers.
verify-system uses the runtime's roots and verify-full also checks the host name; the SSL panel maps one way and is never retried weaker.
The TLS recipes of InfluxDB 1.x and 2.x are in influxdb.md.
A tunnel is offered; through it, the plaintext rule judges and the TLS identity is the tunnel's far end, never its local forward.
influxdb3 has no connection string scheme, no readTls, no paste and no seed load check: every field of its row in CONNECTION_STRING is null (influxdb3-connection-string.ts).
The rolled-back notice names COMMIT as the keyword that keeps an open transaction, and influxdb3 has no consent deadline, so the agent's consent card shows no deadline chip or sentence for it.
Its row in EXECUTION_UI is declared in influxdb3-execution-ui.ts.
The language plan mode writes in, and what the editor runs:
Apache DataFusion SQL as InfluxDB 3 runs it, not InfluxQL and not Flux: one SELECT, WITH, VALUES, SHOW, EXPLAIN or DESCRIBE statement; unquoted names fold to lower case; the time column is "time"; filter time with now() - INTERVAL '1 hour'.
It is SQL, not InfluxQL and not Flux; Flux is not run by either InfluxDB type (D196).
| Request line | URL query keys | JSON body keys |
|---|---|---|
GET /ping |
none | none |
GET /health |
none | none |
POST /api/v3/query_sql |
none | db (required), q (required), format = jsonl |
GET /api/v3/configure/database |
format = json |
none |
The body of a statement is JSON.stringify of exactly db, q and format, with db the session database; no other route, key or path can be built.
/health is read only for a server whose /ping names no version, and configure/database only when the connection opens.
The first keyword, behind comments and opening parentheses, is one of SELECT, WITH, VALUES, SHOW, EXPLAIN and DESCRIBE.
The text is one statement: a ; followed only by whitespace and comments still makes one.
The statement holds no word only a writing statement uses (INSERT, UPDATE, DELETE, MERGE, COPY, CREATE, DROP, ALTER, TRUNCATE, GRANT, REVOKE, INTO) outside quotes, no $ outside quotes and no character outside ASCII outside quotes.
A text is at most 1,048,576 bytes of UTF-8.
Everything the policy refuses is refused before the request, with these sentences:
| Statement | Refusal |
|---|---|
SET x TO 1 |
Only SELECT, WITH, VALUES, SHOW, EXPLAIN and DESCRIBE run on an InfluxDB 3 connection in Studio: this statement begins with SET. |
DROP TABLE home |
This statement holds the word DROP, which only a writing statement uses, and an InfluxDB 3 connection in Studio is read-only; if DROP is a column or table name, write it in double quotes. |
WITH x AS (SELECT 1) DELETE FROM home |
This statement holds the word DELETE, which only a writing statement uses, and an InfluxDB 3 connection in Studio is read-only; if DELETE is a column or table name, write it in double quotes. |
SELECT * INTO copy FROM home |
This statement holds the word INTO, which only a writing statement uses, and an InfluxDB 3 connection in Studio is read-only; if INTO is a column or table name, write it in double quotes. |
SELECT 1; SELECT 2 |
InfluxDB 3 runs one SQL statement per request; remove the text after the first ;. |
SELECT 'x |
The statement has a string, a quoted name or a comment that never closes. |
SELECT $1 |
This statement holds a $ outside quotes (line 1, column 8): Studio sends no bound parameters and reads no dollar-quoted string on an InfluxDB 3 connection; write the value as a '...' string, and a name holding $ in double quotes. |
SELECT * FROM hömé |
This statement holds a character outside ASCII outside quotes (line 1, column 16); write names that need it in double quotes. |
An empty text, or one of comments only:
There is no statement to run.
A text over the cap, here one byte over:
This statement is 1,048,577 bytes; an InfluxDB 3 connection in Studio sends at most 1,048,576.
The engine accepts $1 and ? placeholders, but the route body has no parameter key, so a run with bound parameters is refused:
Studio does not send bound parameters; write the value in the statement.
The statement splitter, the row limiter, the confirmation gate and the read policy read text under one measured grammar row, DATAFUSION_GRAMMAR in src/lib/db/providers/timeseries/influxdb/influxdb3-sql-grammar.ts, so they cannot disagree about where a comment or a literal ends.
| Field | Value | Measured on 3.12.0 Core |
|---|---|---|
hash |
"code" |
SELECT 1 AS x # c is a parser error, so # starts no comment |
bracket |
"subscript" |
SELECT [1,2][1] AS x answers 1; brackets build and index arrays and never quote a name |
blockComment |
"nesting" |
SELECT 1 /* a /* b */ c */ AS x answers 1 |
alternateQuoting |
false |
SELECT q'[x]' AS x is a parser error |
doubleSlashComment |
false |
SELECT 1 AS x // c is a parser error |
backslashAlwaysEscapes |
false |
a backslash in '...' is data, and Studio never writes the E'...' form |
script |
{"blocks":"none","separatorLine":null,"unit":"statement"} |
the default (#1312): DataFusion has no procedural bodies and no separator line, so every code ; ends a statement |
trailingLimitClauses |
[] |
the default (#1398): no clause of this dialect must follow the row bound, so the limiter appends it at the end of the statement |
A string literal doubles its quote ('it''s') and a backslash in it is data; Studio writes every literal that way and never the E'...' form.
A name is double-quoted with "" doubling, and an unquoted name folds to lower case.
The answer is read as jsonl, one JSON object per line.
The columns are the ordered union of the keys the lines name: with SELECT * the engine's alphabetical order, an explicit projection's own order, and a key that first appears on a later line appended.
An integer beyond 2^53 reaches the grid as its exact digits, and NaN and the infinities arrive as null.
A timestamp is the engine's text, UTC with nanoseconds and no zone suffix; write time AT TIME ZONE 'UTC' in the statement to see a Z.
An all-null column and the columns of an empty result cannot be known, because JSON leaves out a null cell and an empty answer has no line; no column type is reported.
A line that is not a JSON object, or one nested deeper than 64 levels, is refused as unreadable:
InfluxDB 3 answered with a line Studio cannot read as a row.
An unaliased expression is named by the engine, but an empty alias is accepted: measured on 3.12 Core, SELECT 1 AS "" answers the line {"":1}, and Studio names that column (No column name) through uniqueFieldNames.
The engine refuses a projection of two columns of one name, measured on 3.12 Core: SELECT 1 AS a, 2 AS a, SELECT usage, usage FROM cpu and SELECT 1, 1 answer 400 "Projections require unique expression names".
A join still answers one key twice: SELECT c1.usage, c2.usage FROM cpu c1 CROSS JOIN cpu c2 answers the line {"usage":1.5,"usage":1.5}, and SELECT * over that join repeats every key.
JSON keeps one value per key and a line leaves out the key of a null cell, so which value belongs to which column cannot be recovered, and Studio refuses the result rather than show it a column short:
InfluxDB 3 answered with more than one column named usage, and its answer cannot say which value belongs to which column, so nothing was shown; give one of them an alias with AS.
A repeated name is caught only on a line that carries it twice, so the all-null limit above applies to it too: when one of the columns is null on every line, no line names it twice and the result comes back as one column.
Measured on 3.12 Core, SELECT c2.usage, c1.usage FROM cpu c1 LEFT JOIN cpu c2 ON c2.host = 'zzz' answers the line {"usage":1.5}, so the grid shows one usage column holding c1.usage.
The answer is read line by line up to the row cut, so a body of many short lines never holds more than the cut's worth of rows.
| Bound | Value |
|---|---|
| Statement text | 1,048,576 bytes of UTF-8, counted before the text is read |
| Response | 16 MiB (16,777,216 bytes) per answer; past it the socket is closed and no row is kept |
| Rows | 10,000 rows per result, the backstop under the limiter's own page; the cut is reported as limited |
| Cells | 250,000 cells (rows times columns) per result |
| In flight | 2 calls per connection and 2 per server type in this process, with a queue of 64 |
| Run deadline | the connection's query timeout |
| Tree, connect and monitoring | 10 seconds, or the query timeout when it is shorter |
| Table listing | 2,000 tables; past it the count is a floor |
An answer over the response cap fails with:
The result is larger than 16 MiB, the most Studio reads for one answer; add a LIMIT or a narrower time range.
The cap and the in-flight bound were measured together in the image runtime under its heap flag and the chart's 512Mi memory limit, with both InfluxDB types kept full at the cap: the process peaked at 230 to 271 MiB at 16 MiB and two calls per type, and above 512 MiB at 32 MiB. A 16 MiB answer still holds about 290,000 narrow rows, far over the row cut, so the cap refuses only an answer that already needs a narrower statement.
The shared limiter appends LIMIT and OFFSET to a statement (supportsResultPagination: true), so a preview shows its first page and Load More reads the next.
A statement that leads with (, or already carries a large LIMIT, is not limited by the shared limiter and is bounded by the row cut and the response cap only.
Load More pages over ORDER BY "time" DESC, and rows of different series share a timestamp, so a page boundary inside a tie can repeat or skip a row between pages.
Add the tag columns to ORDER BY for stable pages, for example ORDER BY "time" DESC, "room".
Every statement runs against the session database; DataFusion cannot name another database in a statement, and one that tries gets a sentence that says so:
InfluxDB 3 found no table edge.iox.numbers: this connection reads one database, home, and a statement cannot name another database; set Database on the connection to read another.
A describe or preview asked by MCP or plan mode with a path longer than the table alone is refused before any request:
This InfluxDB 3 connection reads one database, home, whose tables have no database prefix; set Database on the connection to read another.
A tree click runs the newest rows of the last hour, newest first, with no LIMIT in the text: the preview's page size travels as the limiter's bound, appended after ORDER BY "time" DESC, so Load More works.
The first comment line says what an empty result means: the table has no row newer than the window, which the line says how to widen.
For the table home:
-- Newest rows of the last hour. No row means no row is newer: widen INTERVAL '1 hour' below.
SELECT * FROM "home" WHERE "time" >= now() - INTERVAL '1 hour' ORDER BY "time" DESCGenerate Query writes the same statement with three commented examples below it, naming the table's first integer or float field:
-- Newest rows of the last hour. No row means no row is newer: widen INTERVAL '1 hour' below.
SELECT * FROM "home" WHERE "time" >= now() - INTERVAL '1 hour' ORDER BY "time" DESC
-- A wider window: WHERE "time" >= now() - INTERVAL '1 day'
-- One row per minute: SELECT date_bin(INTERVAL '1 minute', "time") AS minute, avg("co") FROM "home" WHERE "time" >= now() - INTERVAL '1 hour' GROUP BY 1 ORDER BY 1
-- Timestamps are UTC with no zone suffix; time AT TIME ZONE 'UTC' shows a Z.Every name Studio writes is double-quoted, time included, and no generated text ends with ;.
The window is one hour because InfluxDB 3 Core refuses a query that would read more than its file limit, and a wider window met that limit on the measured file-limit fixture.
Count and Profile are offered and run with no time window, because a windowed count would answer a different question under the same button; past Core's file limit either one gets the file-limit sentence:
InfluxDB 3 Core refuses a query that would read more than its file limit (432 Parquet files, about 72 hours, by default). Add a time range such as WHERE time >= now() - INTERVAL '1 day', or raise --query-file-limit on the server.
Stop aborts the run in Studio and closes its socket; the server frees the query's work within about two seconds, measured on 3.12.0, but receives no cancel request, because the HTTP route has none (D197).
A disconnect stops every request in flight or waiting.
The text is SQL, so the editor's SQL confirmation gate reads it: a typed DELETE or DROP asks for confirmation, and the provider then refuses it with the read policy's sentence, a documented cost of being SQL.
The Explain button is not offered (D198); a typed EXPLAIN returns its plan as rows.
A timestamp through this type carries no zone, and the same row read through InfluxDB (InfluxQL) ends in Z, so a timestamp copied from one type into a statement of the other needs its Z added or removed.
For the Charts tab, order by time ascending and choose time as the x axis: the default x axis is the first tag (U79), and "Group by hour" reads a Z time in local time (U80).
How this engine's statement text is read is declared once, in src/lib/db/providers/timeseries/influxdb/influxdb3-sql-grammar.ts, the influxdb3 row of the SQL grammar registry in src/lib/db/sql-grammar/.
Its grammar is DATAFUSION_GRAMMAR, the one reading the statement splitter, the row limiter, the confirmation gate and query-event redaction share, so they cannot disagree about where a literal or a comment ends.
Every fact is Apache DataFusion as InfluxDB 3.12.0 Core parses it, each a statement the server answered through POST /api/v3/query_sql with the body the provider sends, re-run 2026-10-04.
# opens nothing: SELECT 1 AS x # c is HTTP 400, ParserError("Expected: end of statement, found: # at Line: 1, Column: 15").
[ is a subscript and an array literal that nests, never a name quote: SELECT [1,2][1] AS x answers 1, SELECT [[1,2],[3,4]][2][1] AS x answers 3, SELECT ['a]b'][1] AS x answers a]b, and SELECT [room] FROM home answers an array column named make_array(home.room).
Block comments nest: SELECT 1 /* a /* b */ c */ AS x answers {"x":1}.
SELECT q'[x]' AS x and SELECT 1 AS x // c are each HTTP 400 ParserError, so there is no alternate quoting and no // comment.
readsSqlText is true, for the search pair's reason: its text is SQL and its provider extends SQLBaseProvider, so the SQL gate reads it and will prompt on a DELETE or DROP the provider then refuses, a documented cost.
Redaction is sql with no name quote: a redacted statement keeps its words, and every literal and quoted run becomes ?.
There is no container level, as with Qdrant: the session database's tables are the top-level objects, and an object path is one segment, the table.
configure/database is read only when the connection opens, for the session database.
Tables come from information_schema.tables where table_schema = 'iox', in name order, at most 2,000.
A table's columns come from system.influxdb_schema: time, then the tags, then the fields, each in the server's order, typed time, tag or the field's type.
No row count, size, index or foreign key is shown, because the engine cannot state them honestly.
_internal is neither listed nor a session database, because it holds system.tokens.
No system.* table is listed, because the listing reads table_schema = 'iox' only.
An admin token can still read two of them by name in a statement: system.processing_engine_trigger_arguments, which holds the arguments, secrets included, of the server's processing-engine plugins, and system.queries, which holds the text of every query the server ran, other users' included.
No kind declares acceptsRowWrites or acceptsSourceEdits: the type reads only (section 3.1).
The absence is influxdb3's entry in EXPECTED_EDIT_ABSTAINERS (tests/helpers/object-edit-expectation.ts), and tests/isolated/object-edit-declarations.test.ts holds that entry and this section together.
Health is GET /ping, reachability only, never evidence of what the token may read.
The overview's version names the server and the session database, InfluxDB 3 Core 3.12.0, database home on the tested server, because no tree node names the database.
Its table count is the session database's tables; past the listing cap it is a floor, counted from "the first 2,000 tables the session database's table listing returned".
Uptime, size and the connection limit read "N/A" or 0, because no route Studio reads reports them.
Slow queries are not read:
InfluxDB 3 records queries in a server-wide table that shows other users' statements and marks a streamed query finished early, so Studio does not read it.
Sessions are not read either:
InfluxDB has no session list: every request stands alone.
Performance, storage, table and index statistics are empty, and /metrics is never read (D200).
None: no Admin > Operations card and no tree control is offered, because InfluxDB 3 compacts its own storage and the connection is read-only.
| Capability | Value |
|---|---|
queryLanguage |
"sql" |
defaultPort |
8181 |
writeReach |
the editor absent with its proof, evaluateInfluxSql, and object edit, maintenance and transactions absent |
supportsResultPagination |
true |
supportsExternalQueryLimiting |
true |
supportsExplain |
false |
createTable |
absent |
supportsInlineRowEdit |
false |
supportsTestDataGeneration |
false |
supportsTransactions |
false |
supportsMaintenance |
false |
supportsConnectionString |
false |
identifierQuoting |
"double-always" |
statementTerminator |
"none" |
READ_ONLY_ENFORCED |
true |
MCP_EXPOSABLE |
true |
previewTimeWindow declares the one-hour window of section 5.9, and containerLevels is absent.
| Label | Value |
|---|---|
entityName |
Table |
entityNamePlural |
Tables |
rowName |
row |
selectAction |
Preview Newest Rows |
generateAction |
Generate Query |
statementLanguage is the text of section 5.1, which names Apache DataFusion SQL and says it is not InfluxQL or Flux: Apache DataFusion SQL as InfluxDB 3 runs it, not InfluxQL and not Flux: one SELECT, WITH, VALUES, SHOW, EXPLAIN or DESCRIBE statement; unquoted names fold to lower case; the time column is "time"; filter time with now() - INTERVAL '1 hour'.
influxdb/influxdb3-sql-writing.ts declares how a generated statement spells a literal, a name and a bind for this engine, and it is this engine's row of SQL_WRITING (src/lib/db/sql-writing/index.ts), which the readers in src/lib/sql/values.ts and src/lib/sql/identifier.ts look up.
A literal is written by literalStandard and read back by unquoteStandard: the quote is doubled and a backslash is data.
A name is quoted by identifierDoubleQuoted, the SQL standard form: a double-quoted name with an inner double quote doubled.
There is no placeholder (placeholder: null), so a generator quotes the value with quoteLiteral instead of emitting a bind.
Measured on InfluxDB 3.12.0: SELECT 'it''s' answers it's, and SELECT 'a\b' AS a, length('a\b') AS n answers a\b and 3, so a backslash is data.
DataFusion also has PostgreSQL-style E'...' strings (measured: SELECT E'a\nb' decodes the escape), which quoteLiteral never emits.
The engine has placeholders (measured on 3.12.0: SELECT $1 AS x and SELECT ? AS x both answer Error during planning: No value found for placeholder ...), but the route body is exactly db, q and format, so the provider's query() refuses a non-empty params array, and emitting a placeholder would build a statement nothing fills.
Inline row editing reads a key column's kind through the shared INSTANT_TYPE_NAMES, FLOAT64_TYPE_NAMES and EXACT_DECIMAL_TYPE_NAMES of the module's inlineEdit, and binds every edit as typed.
The module's export field writes a result export's CREATE TABLE and INSERT from the shared building blocks alone: STANDARD_DDL_TYPES for the column types, NOTHING_STANDS_ALONE and NO_BARE_SPELLING, so every bare declared name is re-spelled from its family, NULL_NON_FINITE for NaN and the infinities, and binaryStandardHex for a binary cell (X'0102').
InfluxDB 3 parses SQL, but its 3.12 planner refuses DDL and DML, so a file written from it is meant to run somewhere else, and every bare name in it is re-spelled from its family, portably.
| Answer | What Studio says |
|---|---|
400 ParserError |
InfluxDB 3 could not parse the SQL statement: [server text] |
| 400 a table of another database | InfluxDB 3 found no table [table]: this connection reads one database, [database], and a statement cannot name another database; set Database on the connection to read another. |
400 Error during planning |
InfluxDB 3 could not plan the statement: [server text] |
| 400 a malformed Authorization header | InfluxDB 3 could not read the Authorization header; re-enter the token. |
| 401 | InfluxDB 3 refused the token. |
| 403 on a read | This token may not read database [database]. |
404 database not found |
InfluxDB 3 has no database named [database], or this token cannot see it. |
| 404 on a route | This server has no InfluxDB 3 SQL endpoint. InfluxDB Cloud Serverless and Dedicated serve SQL only over Flight, which Studio does not use: connect with InfluxDB (InfluxQL). |
| 405 | InfluxDB 3 does not implement this statement: [server text] |
| 500 the file limit | InfluxDB 3 Core refuses a query that would read more than its file limit (432 Parquet files, about 72 hours, by default). Add a time range such as WHERE time >= now() - INTERVAL '1 day', or raise --query-file-limit on the server. |
500 Resources exhausted |
InfluxDB 3 ran out of the memory it allows one query: [server text] |
| 500 with any other text | InfluxDB 3 refused the statement: [server text] |
| The server ended the answer after accepting it | InfluxDB failed while running this query after accepting it, so its reason did not reach Studio; it is in the server log. Common causes: a division by zero, a failed cast. |
| A line that is not a row | InfluxDB 3 answered with a line Studio cannot read as a row. |
| A line that names a column twice | InfluxDB 3 answered with more than one column named [column], and its answer cannot say which value belongs to which column, so nothing was shown; give one of them an alias with AS. |
| Over the response cap | The result is larger than 16 MiB, the most Studio reads for one answer; add a LIMIT or a narrower time range. |
| The deadline | InfluxDB did not answer within [milliseconds] ms, so Studio stopped waiting and closed the connection, which stops the query on the server. |
| No connect within the deadline | No answer from [host]:[port] after [seconds] seconds: check the address, the port and the network path. |
| Stop | The query was cancelled. |
| TLS | The TLS connection to InfluxDB at [endpoint] failed: check the SSL mode and the CA under SSL / TLS. [code] |
| Unreachable | No complete answer arrived from InfluxDB at [endpoint], and the request was not sent again. [code] |
| A redirect | InfluxDB at [endpoint] answered with a redirect Studio does not follow. |
| A content encoding | InfluxDB at [endpoint] answered with an encoding Studio does not follow. |
| Anything else | The request to InfluxDB failed in a way Studio does not recognise. |
A 500 with a text body is the statement's refusal, never a server-down error: a schema error answers 500 on 3.12.0, for example SELECT "nope" FROM "home" reads "InfluxDB 3 refused the statement: Schema error: No field named nope. ...".
The server's text is shown after Studio's sentence, cut to 500 characters, and a text that holds the token in any form Studio sends is withheld whole.
The token never reaches an error, a result, a log line or a capture.
The unit tests in tests/unit/db/influxdb/ drive each module on its own: sql-policy.test.ts, sql-results.test.ts, sql-objects.test.ts and sql-provider.test.ts for this type, and the shared layer's tests beside them.
tests/integration/db/influxdb3-provider.test.ts drives the provider over the real client with answers captured from InfluxDB 3 Core 3.12.0 (tests/fixtures/influxdb/3.12.0-core/), handed in through the constructor's client factory and a recording transport, with no module mock.
This file's quotes are read back from the code by tests/unit/db/influxdb/provider-doc-influxdb3.test.ts.
bun tests/run-tests.ts tests/integration/db/influxdb3-provider.test.ts
bun run testThe captures are written by bun tests/live/influxdb-evidence.ts against the compose services of section 12: every statement of its plan passes the read policy before the first request, and docker/influxdb/seed.sh is the only writer.
The write-refusal captures are the one exception, written by bun tests/live/influxdb3-write-refusals.ts, which sends each write raw and stops unless the server refuses it and reads back unchanged (section 3.5); bun tests/live/influxdb3-agent-boundary.ts is the live boundary check.
tests/fixtures/influxdb/README.md says what each capture holds.
database-compose.yml holds influxdb3 (127.0.0.1:8181, InfluxDB 3 Core 3.12.0, database home, token apiv3_libredb-influxdb3-admin-token) and, behind the profile influxdb-filelimit, influxdb3-filelimit (127.0.0.1:8182, the same server started with --query-file-limit 1); docker/influxdb/README.md says how to start and seed them.
docker compose -p libredb-studio -f database-compose.yml up -d --wait influxdb3On a server of your own, create a database with influxdb3 create database <name> and a token with influxdb3 create token --admin, and give Studio the token and the database.
Serve over TLS (section 4.6) whenever Studio is not on the same machine.
A seed connection takes the same fields, as SEED_CONNECTIONS.md shows:
- id: "metrics-influx3"
name: "InfluxDB 3 Metrics"
type: influxdb3
host: "${INFLUXDB3_HOST}"
port: 8181
database: telegraf
password: "${INFLUXDB3_TOKEN}"
ssl:
mode: verify-full
roles: ["*"]
managed: true
readOnly: trueThe token goes in password, there is no user, and allowInsecureAuth: true is the seed's form of the consent of section 4.5.
- On InfluxDB 3 Core every token is an admin token, so read-only is a property of what Studio sends, never of the token: the same token reaches every write, token and plugin route outside Studio.
- SSL mode
requiresends the token to a server whose certificate is not checked. - Studio reads
query_sqlas jsonl, with no Arrow Flight: column types are what JSON carries, and a stopped query gets no server-side cancel (D197). - An all-null column and the columns of an empty result cannot be known, and no column type is reported.
- A result that names a column twice, such as a self join's two
usagecolumns, is refused: alias one of them withAS. When one of the two is null on every line, no line names it twice, so the result comes back as one column instead (§5.5). - One connection reads one database; a server with several needs one connection each (U81).
- A page boundary inside a tie of
timecan repeat or skip a row; add the tag columns toORDER BYfor stable pages. - Count and Profile read the whole table and meet Core's file limit on a large one.
- An admin token reads plugin secrets and other users' query text from
system.*tables by name in the editor; the agent'ssystemfence refuses every one butsystem.influxdb_schema(section 3.5). - InfluxDB 3 Enterprise is not tested, and Cloud Serverless and Dedicated cannot be reached by this type (D201, D202).
- MCP's
run_read_queryadmits only the 3.12 Core line, as agent mode does, and its seed's token is an admin token like every Core token (section 3.5). - Agent mode offers
profile_tableandcompare_planswith nothing to read them by (B143), and admits only the 3.12 Core line its write refusals were captured on.
INFLUXDB3_MIGRATION_DDL is what the schema-diff migration generator writes for influxdb3, read through migrationDdlFor in src/lib/db/migration-ddl/.
It declines the whole diff: refusal is the one line the migration prints after its header, in place of any DDL, and transactionWrapper is null.
influxdb3 text is SQL, but the 3.x planner refuses DDL and DML, so there is no table DDL to wrap (#284).
The refusal reason it prints, verbatim:
- "A table's tags and fields are created by the line protocol written to it, and InfluxDB 3's SQL takes no DDL, so there is no column definition to change."
- The InfluxDB 3 Core HTTP API reference:
/api/v3/query_sql,/api/v3/configure/database,/pingand/health. - The InfluxDB 3 Core
serveoptions:--tls-cert,--tls-key,--without-auth,--query-file-limit. - Apache DataFusion's SQL reference, the dialect InfluxDB 3 runs.
docker/influxdb/README.mdandtests/fixtures/influxdb/README.md.