Skip to content

Commit 59b4e39

Browse files
committed
Merge origin/main into cli-reorg
Integrates #266 (query --dialect + SDK 0.14.0) with the reorg: - Dropped main's old flag-based `search` handler and top-level `Query`/`Queries` arms in favor of the reorg's `commands::search::dispatch` and re-parented surface; kept #266's `dialect` field/threading. - Added `--dialect` to both `query` (shortcut) and `databases query` (canonical), threaded through `query::execute`; search always passes `hotsql`. - Adopted SDK 0.14.0 (embedding-provider config map) via the auto-merged `parse_config_map` from #266. - Docs: kept reorg command spellings, kept #266's dialect notes.
2 parents 7d9bd8b + 51760b3 commit 59b4e39

10 files changed

Lines changed: 65 additions & 12 deletions

File tree

‎Cargo.lock‎

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Cargo.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ path = "src/main.rs"
1616
# behind a shared multi-thread tokio runtime. The `arrow` feature backs result
1717
# decode; `upload_file` runs the presigned direct-to-storage upload flow
1818
# (see src/sdk.rs::Api::upload).
19-
hotdata = { version = "0.13.0", features = ["arrow"] }
19+
hotdata = { version = "0.14.0", features = ["arrow"] }
2020
# Shared multi-thread runtime for the sync wrapper; block_on is called
2121
# concurrently from rayon worker threads (see src/indexes.rs). The presigned
2222
# upload (src/sdk.rs::Api::upload) drives the SDK's async `upload_file` on this

‎README.md‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,9 +177,19 @@ hotdata databases load --catalog demo --table listings --file ./listings.parquet
177177
```sh
178178
hotdata databases tables list # every queryable table, as connection.schema.table
179179
hotdata databases tables show <table> # column names and types for one table
180-
hotdata query "<sql>" [-o table|json|csv] # PostgreSQL dialect
180+
hotdata query "<sql>" [-o table|json|csv] # HotSQL (PostgreSQL dialect)
181181
```
182182

183+
Write your SQL in another dialect and have the server transpile it to HotSQL
184+
before it runs — use idioms like Snowflake `IFF(...)` or DuckDB `len(...)`:
185+
186+
```sh
187+
hotdata query "SELECT IFF(n > 0, 'pos', 'neg') FROM t" --dialect snowflake
188+
```
189+
190+
`--dialect` accepts `hotsql` (default, no transpilation), `duckdb`, `postgres`,
191+
or `snowflake`. Only read-only queries are accepted for a non-`hotsql` dialect.
192+
183193
Long-running queries fall back to async and print a `query_run_id` — poll with
184194
`hotdata query status <id>` (exit codes: `0` done, `1` failed, `2` running).
185195
Every result gets a `result-id`; re-fetch past results with

‎skills/hotdata/subskills/analytics/SKILL.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,12 @@ version: 0.26.0
1717
## Execute SQL
1818

1919
```bash
20-
hotdata query "<sql>" [--workspace-id <workspace_id>] [--database <database>] [--output table|json|csv]
20+
hotdata query "<sql>" [--workspace-id <workspace_id>] [--database <database>] [--dialect hotsql|duckdb|postgres|snowflake] [--output table|json|csv]
2121
hotdata query status <query_run_id>
2222
```
2323

2424
- **PostgreSQL dialect.** Quote mixed-case identifiers: `"CustomerName"`.
25+
- **`--dialect`** (default `hotsql`): write SQL in `duckdb`/`postgres`/`snowflake` and the server transpiles it to HotSQL before running (e.g. Snowflake `IFF(...)`, DuckDB `len(...)`). Read-only queries only for a non-`hotsql` dialect.
2526
- Use **`hotdata databases tables list`** for schema discovery — not `information_schema` via `query`.
2627
- Fully qualified names: `<catalog>.<schema>.<table>`, `<database>.<schema>.<table>`.
2728
- **Query scope:** every query runs inside one managed database (active or `--database`); it sees that database's own catalog plus **attached** catalogs only. To query an attached catalog's table, or **join a managed table against an attached catalog's table**, attach the catalog first: `hotdata databases attach <catalog>` — see **`hotdata`** skill → [Querying across catalogs](../../SKILL.md#querying-across-catalogs-attach). No managed database set → *"a database is required."*

‎src/cli.rs‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ pub enum Commands {
5252
#[arg(long, short = 'd')]
5353
database: Option<String>,
5454

55+
/// SQL dialect the query is written in — a non-`hotsql` dialect is
56+
/// transpiled to HotSQL server-side before it runs (read-only only)
57+
#[arg(long, default_value = "hotsql", value_parser = ["hotsql", "duckdb", "postgres", "snowflake"])]
58+
dialect: String,
59+
5560
/// Output format
5661
#[arg(long = "output", short = 'o', default_value = "table", value_parser = ["table", "json", "csv"])]
5762
output: String,

‎src/commands/databases.rs‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,11 @@ pub enum DatabasesCommands {
209209
#[arg(long, short = 'd')]
210210
database: Option<String>,
211211

212+
/// SQL dialect the query is written in — a non-`hotsql` dialect is
213+
/// transpiled to HotSQL server-side before it runs (read-only only)
214+
#[arg(long, default_value = "hotsql", value_parser = ["hotsql", "duckdb", "postgres", "snowflake"])]
215+
dialect: String,
216+
212217
/// Output format
213218
#[arg(long = "output", short = 'o', default_value = "table", value_parser = ["table", "json", "csv"])]
214219
output: String,

‎src/commands/embedding_providers.rs‎

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,25 @@ fn parse_config(raw: Option<&str>) -> Option<serde_json::Value> {
171171
})
172172
}
173173

174+
/// Parse `--config` into the object map the create request expects. Exits on
175+
/// invalid JSON (via [`parse_config`]) or a JSON value that isn't an object.
176+
fn parse_config_map(
177+
raw: Option<&str>,
178+
) -> Option<std::collections::HashMap<String, serde_json::Value>> {
179+
use crossterm::style::Stylize;
180+
match parse_config(raw)? {
181+
serde_json::Value::Object(map) => Some(map.into_iter().collect()),
182+
_ => {
183+
eprintln!(
184+
"{}",
185+
"--config must be a JSON object, e.g. '{\"model\": \"text-embedding-3-small\"}'"
186+
.red()
187+
);
188+
std::process::exit(1);
189+
}
190+
}
191+
}
192+
174193
pub fn list(workspace_id: &str, format: &str) {
175194
let api = Api::new(Some(workspace_id));
176195
let providers: Vec<Provider> = crate::client::sdk::block_with_wakeup(
@@ -255,8 +274,8 @@ pub fn create(
255274

256275
let api = Api::new(Some(workspace_id));
257276
let mut req = CreateEmbeddingProviderRequest::new(name.to_string(), provider_type.to_string());
258-
if let Some(cfg) = parse_config(config) {
259-
req.config = Some(Some(cfg));
277+
if let Some(cfg) = parse_config_map(config) {
278+
req.config = Some(cfg);
260279
}
261280
if let Some(k) = api_key {
262281
req.api_key = Some(Some(k.to_string()));

‎src/commands/query.rs‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -440,7 +440,7 @@ fn fail_run(error_msg: &str) -> ! {
440440
std::process::exit(1);
441441
}
442442

443-
pub fn execute(sql: &str, workspace_id: &str, database: Option<&str>, format: &str) {
443+
pub fn execute(sql: &str, workspace_id: &str, database: Option<&str>, format: &str, dialect: &str) {
444444
// Scope to the explicit --database flag, else the active database resolved
445445
// at construction (HOTDATA_DATABASE / current database). The scoped `Api`
446446
// carries the database into submit_query's `X-Database-Id` header and into
@@ -451,6 +451,11 @@ pub fn execute(sql: &str, workspace_id: &str, database: Option<&str>, format: &s
451451
let mut request = hotdata::models::QueryRequest::new(sql.to_string());
452452
request.r#async = Some(true);
453453
request.async_after_ms = Some(Some(1000));
454+
// `hotsql` is the server default and planned as-is; only send `dialect` for a
455+
// non-default dialect so ordinary queries keep their existing request shape.
456+
if dialect != "hotsql" {
457+
request.dialect = Some(Some(dialect.to_string()));
458+
}
454459

455460
let outcome = crate::client::sdk::block_with_wakeup(
456461
&api,

‎src/commands/search.rs‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,8 @@ pub fn run(
393393
select,
394394
limit,
395395
);
396-
query::execute(&sql, workspace_id, Some(&loc.database_id), output);
396+
// Search generates HotSQL directly — never a foreign dialect.
397+
query::execute(&sql, workspace_id, Some(&loc.database_id), output, "hotsql");
397398
}
398399

399400
fn remove(workspace_id: &str, database: Option<&str>, name: &str) {

‎src/main.rs‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ fn main() {
192192
sql,
193193
workspace_id,
194194
database,
195+
dialect,
195196
output,
196197
command,
197198
} => {
@@ -201,9 +202,13 @@ fn main() {
201202
query::poll(&id, &workspace_id, database.as_deref(), &output)
202203
}
203204
None => match sql {
204-
Some(sql) => {
205-
query::execute(&sql, &workspace_id, database.as_deref(), &output)
206-
}
205+
Some(sql) => query::execute(
206+
&sql,
207+
&workspace_id,
208+
database.as_deref(),
209+
&output,
210+
&dialect,
211+
),
207212
None => {
208213
use clap::CommandFactory;
209214
let mut cmd = Cli::command();
@@ -441,6 +446,7 @@ fn main() {
441446
Some(DatabasesCommands::Query {
442447
sql,
443448
database,
449+
dialect,
444450
output,
445451
command,
446452
}) => match command {
@@ -453,6 +459,7 @@ fn main() {
453459
&workspace_id,
454460
database.as_deref(),
455461
&output,
462+
&dialect,
456463
),
457464
None => {
458465
use clap::CommandFactory;

0 commit comments

Comments
 (0)