Summary
There's no way to load a local file into a service using the CLI alone. tiger db query can run SQL from a file, but it can't stream data, so COPY ... FROM STDIN isn't reachable and neither is anything equivalent. Today the only options are to install psql for \copy or to write a program against a Postgres driver.
Why this matters
The CLI's own description is "an MCP server for helping coding agents write production-level Postgres code." Getting data into a service is step one of most of that work, and it's currently the one step that requires stepping outside the CLI.
Concretely: I built a workshop where attendees point a coding agent at a Tiger Cloud service in a GitHub Codespace. Every other operation — DDL, queries, EXPLAIN, schema inspection, forking, teardown — is tiger and nothing else. Loading 1M rows of NYC 311 data is the sole reason a second tool has to exist in the image.
What I tried
1. COPY ... FROM STDIN — hangs indefinitely. This is arguably a bug on its own:
$ tiger db query <service-id> -c "COPY t FROM STDIN WITH (FORMAT csv, HEADER true)" < data.csv
# never returns; had to kill it
The server is waiting for CopyData frames the client never sends, and the client is waiting for a result set that will never arrive. An immediate "COPY FROM STDIN is not supported" would be much kinder than a hang.
2. -f with a file of INSERTs — works, but doesn't scale. tiger db query sends a file as one query string, so a 146 MB file of 1M rows gets:
Error: ERROR: out of memory (SQLSTATE 53200)
against a free service.
3. The workaround I shipped. Chunk the data into 10 gzipped files of 100k INSERTs each (~15 MB apiece) and stream them through stdin:
for f in data/load/*.sql.gz; do
gunzip -c "$f" | tiger db query "$SERVICE_ID"
done
This works and needs nothing but tiger and gunzip. It takes 78 seconds for 1M rows against a free service. The same data via a real COPY ... FROM STDIN (Node, pg-copy-streams) takes 12–17 seconds, so the workaround is roughly 5x slower and ships the dataset in a format that's ~6x larger uncompressed than the CSV it came from.
What would help
Something like:
tiger db copy <service-id> --table service_requests --from data.csv --format csv --header
tiger db copy <service-id> --table service_requests --from - # stdin
Reading from stdin matters as much as the file case — it keeps gunzip -c … | and other pipelines working without unpacking large files to disk.
Two smaller things that would each be worth having on their own, even without the above:
- Make
COPY ... FROM STDIN via tiger db query fail fast with a clear message instead of hanging.
- Consider streaming
-f files statement-by-statement rather than as one query string, so a large SQL file doesn't OOM the server.
Environment
Tiger CLI 0.24.0, darwin/arm64 and linux/arm64 (devcontainer). Reproduced against both a free (shared/shared) service and a paid 0.5 CPU / 2 GB service in us-east-1.
Summary
There's no way to load a local file into a service using the CLI alone.
tiger db querycan run SQL from a file, but it can't stream data, soCOPY ... FROM STDINisn't reachable and neither is anything equivalent. Today the only options are to installpsqlfor\copyor to write a program against a Postgres driver.Why this matters
The CLI's own description is "an MCP server for helping coding agents write production-level Postgres code." Getting data into a service is step one of most of that work, and it's currently the one step that requires stepping outside the CLI.
Concretely: I built a workshop where attendees point a coding agent at a Tiger Cloud service in a GitHub Codespace. Every other operation — DDL, queries,
EXPLAIN, schema inspection, forking, teardown — istigerand nothing else. Loading 1M rows of NYC 311 data is the sole reason a second tool has to exist in the image.What I tried
1.
COPY ... FROM STDIN— hangs indefinitely. This is arguably a bug on its own:The server is waiting for CopyData frames the client never sends, and the client is waiting for a result set that will never arrive. An immediate "COPY FROM STDIN is not supported" would be much kinder than a hang.
2.
-fwith a file ofINSERTs — works, but doesn't scale.tiger db querysends a file as one query string, so a 146 MB file of 1M rows gets:against a free service.
3. The workaround I shipped. Chunk the data into 10 gzipped files of 100k
INSERTs each (~15 MB apiece) and stream them through stdin:This works and needs nothing but
tigerandgunzip. It takes 78 seconds for 1M rows against a free service. The same data via a realCOPY ... FROM STDIN(Node,pg-copy-streams) takes 12–17 seconds, so the workaround is roughly 5x slower and ships the dataset in a format that's ~6x larger uncompressed than the CSV it came from.What would help
Something like:
Reading from stdin matters as much as the file case — it keeps
gunzip -c … |and other pipelines working without unpacking large files to disk.Two smaller things that would each be worth having on their own, even without the above:
COPY ... FROM STDINviatiger db queryfail fast with a clear message instead of hanging.-ffiles statement-by-statement rather than as one query string, so a large SQL file doesn't OOM the server.Environment
Tiger CLI 0.24.0, darwin/arm64 and linux/arm64 (devcontainer). Reproduced against both a free (
shared/shared) service and a paid 0.5 CPU / 2 GB service inus-east-1.