Skip to content

feat(ingest): add hotdata ingest raw-sql verb - #241

Merged
eddietejeda merged 1 commit into
mainfrom
feat/ingest-raw-sql
Jul 31, 2026
Merged

feat(ingest): add hotdata ingest raw-sql verb#241
eddietejeda merged 1 commit into
mainfrom
feat/ingest-raw-sql

Conversation

@eddietejeda

Copy link
Copy Markdown
Contributor

CLI front-door for POST /ingest/queries/raw (companion to dlthubworker#160, now merged + deployed to prod).

hotdata ingest raw-sql --source <name|id> --table <result> [--limit N] "<sql>"

Runs a source-native SQL query verbatim against a SQL datasource in its own dialect — joins, GROUP BY, CTEs, window functions, engine-specific functions all execute at the source; only the result set transfers, landing in --table in a fresh managed database. Complements new-import (the restricted SELECT <cols|*> FROM <ds>[.<table>] [WHERE] [LIMIT] grammar).

  • client (src/client/ingest.rs): RawSqlIngest request + create_raw_query (POST /queries/raw). Requires a workspace API key like the other enqueue routes.
  • command (src/commands/ingest.rs): raw-sql subcommand + handler, mirroring new-import's submit → drain → poll flow (--wait supported).
  • Skill doc (skills/hotdata/SKILL.md) updated with the verb + when to use it vs new-import.

Server-side safety (in dlthubworker#160): single read-only statement enforced, validated in the source dialect, decimals typed from cursor.description. Verified live in prod: a GROUP BY on neon_tpch drained to done with the correct pushed-down aggregation.

cargo check + cargo fmt clean; no new clippy warnings.

Front-door for POST /ingest/queries/raw (dlthubworker#160): run a source-native
SQL query verbatim against a SQL datasource — joins, GROUP BY, CTEs, window
functions, engine-specific functions execute at the source — and ingest the
result into --table in a fresh managed database. Complements `new-import` (the
restricted projection grammar).

    hotdata ingest raw-sql --source <name|id> --table <result> [--limit N] "<sql>"

- client: RawSqlIngest request + create_raw_query (POST /queries/raw); requires a
  workspace API key like the other enqueue routes.
- command: `raw-sql` subcommand + handler, mirroring new-import's submit/drain/
  poll flow. Skill doc updated.

The server enforces read-only single-statement + dialect-aware validation; the
endpoint is live in prod.
@eddietejeda
eddietejeda requested a review from a team as a code owner July 31, 2026 23:09
@eddietejeda
eddietejeda requested review from shefeek-jinnah and removed request for a team July 31, 2026 23:09
@codecov

codecov Bot commented Jul 31, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 48 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/commands/ingest.rs 0.00% 40 Missing ⚠️
src/client/ingest.rs 0.00% 8 Missing ⚠️

📢 Thoughts on this report? Let us know!

Comment thread skills/hotdata/SKILL.md
Agent tips:
- Import SQL is a **restricted grammar**: `SELECT <cols|*> FROM <datasource>[.<table>] [WHERE …] [LIMIT n]` — no joins/GROUP BY. Run real analytics with `hotdata query` against the imported database afterward.
- `new-import` SQL is a **restricted grammar**: `SELECT <cols|*> FROM <datasource>[.<table>] [WHERE …] [LIMIT n]` — no joins/GROUP BY. For those, either use `raw-sql` (pushes the full query down to a SQL source) or run `hotdata query` against the imported database afterward.
- `raw-sql` is SQL-datasources-only and read-only (a single SELECT; writes/DDL are refused). Prefer a **read-only source credential**. Cast ambiguous/mixed-precision decimal columns explicitly in the query.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: (not blocking) create_raw_query calls require_api_key(), so raw-sql is an enqueue verb — but the auth paragraph at skills/hotdata/SKILL.md:238 still lists only new-datasource, new-import, trigger-import, delete-datasource as needing a workspace API key. An agent reading that line will assume a login session works for raw-sql and hit NeedsApiKey. Worth adding raw-sql to that list.

Also, --limit isn't mentioned anywhere in the skill doc — the example only shows --source/--table.

Comment thread src/client/ingest.rs
/// Ingest the result of a source-native SQL query (SQL datasources only).
/// The SQL runs verbatim in the source dialect; the server enforces a single
/// read-only statement.
pub fn create_raw_query(&self, req: &RawSqlIngest) -> Result<IngestAck, IngestError> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: (not blocking) No test for this endpoint. The module's mockito suite pins the wire contract for the other enqueue routes (create_source_sends_api_key_bearer_and_workspace_header, enqueue_with_session_jwt_fails_fast_without_http); create_raw_query adds a new path (/ingest/queries/raw), a new body shape, and the skip_serializing_if on limit — all currently unverified. A single mock test asserting the path + Matcher::Json body with and without --limit, plus a NeedsApiKey case in the existing JWT test, would cover it.

super nit: (not blocking) the Surface: list in the module docs (lines 25–30) enumerates every method; create_raw_query isn't in it.

Comment thread src/commands/ingest.rs
Comment on lines +996 to +1000
let spinner = util::spinner("submitting raw-sql import…");
let ack = client.create_raw_query(&req).unwrap_or_else(|e| {
spinner.finish_and_clear();
e.exit()
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: (not blocking) new_import retries the enqueue once on a 404 (src/commands/ingest.rs:939-949) because the by-name datasource lookup reads snapshots that lag writes, so a submit right after new-datasource 404s briefly. raw-sql resolves --source by name the same way, so it has the same window but no retry — new-datasource … && ingest raw-sql --source <name> can fail where new-import would succeed. Reusing the same retry-once loop here would make the two front doors behave alike.

super nit: (not blocking) the tail of this fn (spinner → --wait poll → render_done → else drain + render_ack) is a verbatim copy of new_import's. A shared submit_and_track(&client, ack, output, poll) helper would keep the two from drifting. Also, the module header's command list (lines 5-11) doesn't mention raw-sql.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved — only non-blocking nits inline.

@eddietejeda
eddietejeda merged commit d6b0971 into main Jul 31, 2026
14 checks passed
@eddietejeda
eddietejeda deleted the feat/ingest-raw-sql branch July 31, 2026 23:12
@shefeek-jinnah

Copy link
Copy Markdown
Contributor

approved

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants