Skip to content

Reading a table

Preview rows with peek, query them with SQL, use download for the artifacts or list the Parquet parts.

Terminal window
mr-data peek RUN_ID [--rows N] [--no-activity] [--json]

Print the columns, the types and the first rows of the preview a run wrote. The preview is an artifact of the run and arrives decoded, so no worker wakes for it.

Run it after a --sample run, before deciding the recipe is right. To ask the data a question, use query.

Flag Argument Required Meaning Default
RUN_ID id yes The hosted run, by the identifier the submission printed. none
--rows N no How many preview rows to print. It narrows and never widens. The run fixed the preview’s size when it finished. 5
--no-activity none no Do not tell the dataset page what this command is doing. off
--json none no One JSON object instead of human lines. off

Nine flags do nothing here, because the hosted preview arrives decoded. They are --format, --reader, --reader-options, --allow-redirect-host, --json-records-pointer, --json-expand, --json-column, --json-optional-column and --json-document-format. The payload reports the ones you passed under flags_without_effect instead of failing the command.

mr-data peek RUN_ID --json
{
"schema_version": "mostlyright-thin-client-v4-peek.v1",
"status": "peeked",
"lane": "hosted",
"run_id": "7c9e1d34-8b52-4f61-9a03-2e5d6f7a8b90",
"dashboard_url": "https://mostlyright.md/datasets",
"artifact_id": "5e2a9c74-1d6b-4308-b9c5-8f0a3d7e2b41",
"row_count": 4100000,
"columns": ["city", "date", "max_temp_c"],
"schema": {
"column 1": { "name": "city", "type": "string" },
"column 2": { "name": "date", "type": "date" },
"column 3": { "name": "max_temp_c", "type": "double" }
},
"sample": { "row 1": { "city": "Boston", "date": "2026-09-11", "max_temp_c": 24.4 } },
"input_sha256": "9f3c…",
"flags_without_effect": {},
"note": "Showing 5 of the 50 rows this run's preview holds, out of 4100000 in the table."
}

Values are illustrative. row_count is the table’s total, not the preview’s. A column’s type stays null when the run wrote no column profile, and the command fetches that profile only when a type is missing. note appears when the print is shorter than the preview holds. It appears too when the preview calls itself truncated. The run then wrote fewer rows than the table has, even with every preview row printed.

Errors: THIN_NO_PREVIEW_SEALED and THIN_NO_MATCHING_ARTIFACT when the run wrote no preview. Also THIN_ARTIFACT_TOO_LARGE and THIN_NOT_FOUND.

Terminal window
mr-data peek "$RUN_ID" --rows 20 --json | jq '.columns, .sample'
Terminal window
mr-data query RUN_ID SQL [--max-rows N] [--field FIELD] [--from VALUE] [--to VALUE]
[--partition NAME=VALUE …] [--no-session] [--no-activity] [--json]

Ask the table this run produced one bounded read-only question.

Studio queues the question. The command reads the answer back at most 40 times and for at most 120 seconds, whichever comes first. It never holds a long poll. A wait that ends first leaves the question running and prints its identifier. Run the same command again and it reads that question back. Studio derives the identifier from the statement, the row limit and the prune block.

The SQL is read-only, one statement, and begins with SELECT, WITH, EXPLAIN or DESCRIBE. Address the run’s table as run_table, whatever the recipe named it. The whole submission (statement, row limit and prune block, as canonical JSON) is at most 64 KiB.

Studio’s statement gate blanks comments and quoted text, then scans the rest on word boundaries. It refuses a column literally named system, copy, set, load, call, glob, import or export. Writing that identifier in "double quotes" puts it outside the scan.

The client holds no SQL allow-list. The warm worker’s read-only DuckDB connection, with external access off, is the boundary.

A table version is many Parquet parts, and a bounded read opens only so many of them. Studio evaluates --field with --from/--to, and --partition, against the part list before it wakes a worker. They are never SQL. The bounds travel as the text you typed. A part records its bounds in the manifest’s canonical text encoding, and this command compares your text against that encoding.

A prune belongs to the question, so the same SQL over a different slice is a new query rather than a replay.

Flag Argument Required Meaning Default
RUN_ID id yes The run whose table to ask. none
SQL statement yes One read-only statement beginning with SELECT, WITH, EXPLAIN or DESCRIBE. none
--max-rows N no Stop at this many rows. Studio refuses a number over its ceiling rather than lowering it. the server’s own ceiling
--field column name with --from/--to Which field --from and --to are about. Alone it narrows nothing, so the command refuses it. none
--from value no Open only the parts whose recorded range on --field reaches this value or later. Inclusive. A part the prune cannot rule out stays in. none
--to value no Open only the parts whose recorded range on --field starts before this value. Exclusive, so a day range runs from one midnight to the next. none
--partition NAME=VALUE no Open only the parts whose partition NAME matches one of the values given. Repeatable. A part carrying no value for NAME stays in. none
--no-session none no Refuse when no warm worker holds the workspace, rather than open a research session for the run’s dataset. off. It opens one, waits, asks and closes it
--no-activity none no Do not tell the dataset page what this command is doing. off
--json none no One JSON object instead of human lines. off
mr-data query RUN_ID 'SELECT …' --json
{
"schema_version": "mostlyright-thin-client-v4-query.v1",
"lane": "hosted",
"run_id": "7c9e1d34-8b52-4f61-9a03-2e5d6f7a8b90",
"query_id": "2f6b8d05-4e17-5a92-8c63-b0d5e1f7a934",
"dashboard_url": "https://mostlyright.md/datasets",
"replayed": false,
"polls": 3,
"status": "query_answered",
"state": "succeeded",
"sql": "SELECT city, max(max_temp_c) FROM run_table GROUP BY city",
"max_rows": 100,
"prune": { "field": "date", "from": "2026-09-01", "to": "2026-09-12" },
"part_count": 11,
"row_count": 20,
"truncated": false,
"elapsed_ms": 84,
"result_digest": "a1b2…",
"rows": { "row 1": { "city": "Boston", "max": 31.2 } }
}

Values are illustrative. replayed reads Studio’s Idempotent-Replay header. part_count is how much of the table the answer covers.

When the wait runs out, status reads query_still_running and the command exits 2. The payload carries state, sql, max_rows, deadline_at and a note. The query keeps running and Studio terminalizes it at its own deadline. Run the same command again to read it back.

Errors:

Code When
THIN_QUERY_STATEMENT_TOO_LARGE The submission is over 64 KiB. The command sent nothing.
THIN_REQUEST_INVALID An empty statement. A --max-rows under 1. --from/--to without --field. --field without either. A --partition that is not NAME=VALUE.
THIN_STUDIO_QUERY_STATEMENT_FORBIDDEN Studio refused the statement before queuing it. See The statement.
THIN_STUDIO_QUERY_ROW_LIMIT_EXCEEDED --max-rows sits over the server ceiling. Leave it out to take the ceiling.
THIN_STUDIO_QUERY_TABLE_UNAVAILABLE This run wrote no table, or the window and partitions narrowed it to no part at all.
THIN_STUDIO_QUERY_TOO_MANY_PARTS The question would open more parts than a bounded read may. The refusal names both the count and the ceiling. Narrow it, or use parts and read them yourself.
THIN_STUDIO_QUERY_NO_WARM_EXECUTOR You gave --no-session and no warm worker holds the workspace. Studio does not start one for a query.
THIN_STUDIO_QUERY_RESULT_TOO_LARGE The answer ran past the byte ceiling, so Studio rejected it rather than shortening it. Narrow the projection, add a WHERE, or lower --max-rows.
THIN_STUDIO_QUERY_TIMEOUT The query outlived its deadline. No partial result is recorded.
THIN_RESPONSE_INVALID Studio queued a query without an identifier, or returned a result belonging to another run.
one day of one table, without opening the whole version
mr-data query "$RUN_ID" \
'SELECT city, max(max_temp_c) AS max_c FROM run_table GROUP BY city ORDER BY max_c DESC' \
--field date --from 2026-09-11 --to 2026-09-12 --max-rows 100 --json
Terminal window
mr-data download RUN_ID [--output DIR] [--kind KIND …] [--no-activity] [--json]

Bring one run’s artifacts back to this computer and check each digest. A table version made of parts arrives whole. The manifest names every part of the version, including the parts earlier runs wrote. The command fetches each one through the version’s own signed reads. If a part cannot be fetched, the refusal names how many failed. The command never leaves a folder holding only some of the table.

Flag Argument Required Meaning Default
RUN_ID id yes The run to read. none
--output DIR no The folder to write into. Relative or absolute, with ~ expanded. The command overwrites nothing in it. run-artifacts
--kind one kind no Which artifacts to bring back, one --kind each. the shape the run wrote
--no-activity none no Do not tell the dataset page what this command is doing. off
--json none no One JSON object instead of human lines. off
--kind File suffix
table_parquet .parquet
table_manifest .json
table_part .parquet
column_profile .json
preview .json
receipt .json
raw_snapshot .tar

Without --kind the command reads the listing and brings back the shape the run wrote. That is table_manifest plus table_part plus receipt for a version made of parts, and table_parquet plus receipt for one written whole. It never brings all seven. A profile, a preview and a tar of raw sources each have their own command that reads without writing a file.

mr-data download RUN_ID --json
{
"schema_version": "mostlyright-thin-client-v4-download.v1",
"status": "artifacts_downloaded",
"lane": "hosted",
"run_id": "7c9e1d34-8b52-4f61-9a03-2e5d6f7a8b90",
"dashboard_url": "https://mostlyright.md/datasets",
"output": "run-artifacts",
"artifacts": [
{
"artifact_id": "5e2a9c74-1d6b-4308-b9c5-8f0a3d7e2b41",
"kind": "table_part",
"media_type": "application/vnd.apache.parquet",
"size_bytes": 6291456,
"digest": "9f3c…",
"path": "run-artifacts/part-00001.parquet",
"transferred": true
}
],
"version": {
"table_version_id": "9d4e7a21-0c3b-4f56-8e2a-1b6c5d9f0a37",
"manifest_digest": "a1b2…",
"part_count": 128,
"row_count": 4100000,
"parts_sealed_by_earlier_runs": 96
}
}

Values are illustrative. version appears only for a version made of parts. parts_sealed_by_earlier_runs counts how many of them this run did not write.

Errors:

Code When
THIN_REQUEST_INVALID A --kind outside the seven. The refusal lists all seven.
THIN_DESTINATION_EXISTS A file this download would write is already there. The command overwrites nothing.
THIN_DOWNLOAD_DIGEST_MISMATCH Bytes arrived that do not hash to the digest the listing or the manifest stated.
THIN_DOWNLOAD_TRUNCATED / THIN_DOWNLOAD_OVERRUN The transfer ran shorter or longer than the size stated.
THIN_VERSION_PART_UNREACHABLE One or more parts of the version did not arrive. The refusal names how many.
THIN_NO_MATCHING_ARTIFACT The run wrote nothing of the kinds asked for.
THIN_DOWNLOAD_REFUSED The signed read came back refused.
Terminal window
mr-data download "$RUN_ID" --output ./data --kind table_manifest --kind table_part --json
Terminal window
mr-data parts TABLE_ID [--version VERSION] [--field FIELD] [--from VALUE] [--to VALUE] [--json]

List one table version’s parts: the identifier, the row count, the byte size and the recorded bounds of each. A training loader shards this list. Give each worker the parts whose index modulo the world size is its own, open each with pyarrow.parquet.ParquetFile, and iterate its batches.

Listing downloads nothing, and the listing does not prove a part still reads. Whatever reads a part checks its bytes against the digest stated here.

Flag Argument Required Meaning Default
TABLE_ID id yes The table to read. none
--version VERSION no Which version to list, by its identifier. the live one, or the pinned one on a frozen table
--field column name no Which field --from and --to are about. the version’s first sort field
--from value no Keep only the parts whose recorded range reaches this value or later. Inclusive. none
--to value no Keep only the parts whose recorded range starts before this value. Exclusive. none
--json none no One JSON object instead of human lines. off

The window is half-open and lower-inclusive, the rule the query path uses. A part whose range starts exactly at --to drops out, and one whose range ends exactly at --from stays in. A part that records no bound on the field, or half of one, stays in. Pruning never drops a part it cannot rule out.

mr-data parts TABLE_ID --json
{
"schema_version": "mostlyright-thin-client-v4-table-parts.v1",
"status": "parts_listed",
"lane": "hosted",
"table_id": "3f8c2d10-6a4b-4c8e-9d1f-5b7a0c2e4d68",
"table_version_id": "9d4e7a21-0c3b-4f56-8e2a-1b6c5d9f0a37",
"table_version_number": 42,
"manifest_digest": "a1b2…",
"row_count": 4100000,
"part_count": 128,
"total_bytes": 811000000,
"partition_fields": ["city"],
"sort_fields": ["date"],
"parts": [
{
"part_id": "9f3c…",
"run_id": "7c9e1d34-8b52-4f61-9a03-2e5d6f7a8b90",
"artifact_id": "5e2a9c74-1d6b-4308-b9c5-8f0a3d7e2b41",
"part_key": "p00001",
"file_format": "parquet",
"file_size_in_bytes": 6291456,
"record_count": 32000,
"schema_digest": "a1b2…",
"partition": { "city": "Boston" },
"lower_bounds": { "2": "2026-09-01" },
"upper_bounds": { "2": "2026-09-11" },
"null_value_counts": {},
"sealed_at": "2026-09-12T09:00:00Z"
}
],
"parts_listed": 11
}

Values are illustrative. part_count is the version’s total. parts_listed is how many survived the window. Part rows come from the manifest, verbatim. part_id is the part’s content digest and its identity everywhere. artifact_id only says how to address its bytes. A version one run composed can name parts written by runs before it. Bounds carry a field id and the manifest’s canonical text encoding, which is what --from and --to compare against.

Errors:

Code When
THIN_NOTHING_LIVE The table is unpromoted or demoted, so no live version exists to list. Or it is promoted and no succeeded run of it resolves to a version. Name one with --version. The run that wrote a version carries its identifier, and mr-data status prints it.
THIN_RESPONSE_INVALID The version’s manifest states no part list, or states more parts than a version may name.
THIN_REQUEST_INVALID --from/--to with no --field on a version that declares no sort field, or a --field the version does not carry. The refusal lists the fields it does carry.
THIN_NOT_FOUND No such table, or no such version of it.
shard one day across four workers
mr-data parts "$TABLE_ID" --field date --from 2026-09-11 --to 2026-09-12 --json \
| jq -r --argjson world 4 --argjson rank 0 \
'.parts | to_entries[] | select(.key % $world == $rank) | .value.part_key'
  • CLI reference: conventions, exit codes, environment variables
  • Runs: the run these four read from
  • Tables: which version is live, and which is pinned