diff --git a/CHANGELOG.md b/CHANGELOG.md index 9042f05030b1..de42c41ff80c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,6 +54,13 @@ **Integrations**: +- The `prqlc` JS/wasm package now reports an error for an unknown `target`, + rather than silently compiling to generic SQL. A typo such as + `opts.target = "sql.postgrez"` previously fell back to `sql.any`, so the + caller got working-but-wrong SQL with no signal. The Python and C bindings + already propagated this error; an unset (empty) `target` still means + `sql.any`. (@prql-bot, #6238) + - The `prqlc` Python package now declares `requires-python = ">=3.10"`. Python 3.9 reached end-of-life in October 2025 and was never exercised by the test matrix, which runs 3.10 and 3.12. The stale `>=3.9` claim also blocked every diff --git a/prqlc/bindings/js/src/lib.rs b/prqlc/bindings/js/src/lib.rs index 31af3061c890..26b69c88126b 100644 --- a/prqlc/bindings/js/src/lib.rs +++ b/prqlc/bindings/js/src/lib.rs @@ -7,9 +7,13 @@ use wasm_bindgen::prelude::*; #[wasm_bindgen] pub fn compile(prql_query: &str, options: Option) -> Option { + let options = match options.map(prqlc::Options::try_from).transpose() { + Ok(options) => options.unwrap_or_default(), + Err(e) => return return_or_throw(Err(e)), + }; + return_or_throw( - prqlc::compile(prql_query, &options.map(|x| x.into()).unwrap_or_default()) - .map_err(|e| e.composed(&prql_query.into())), + prqlc::compile(prql_query, &options).map_err(|e| e.composed(&prql_query.into())), ) } @@ -109,17 +113,26 @@ impl CompileOptions { } } -impl From for prqlc::Options { - fn from(o: CompileOptions) -> Self { - let target = Target::from_str(&o.target).unwrap_or_default(); +impl TryFrom for prqlc::Options { + type Error = prqlc::ErrorMessages; + + fn try_from(o: CompileOptions) -> Result { + // An empty `target` is the unset default, and means `sql.any`. Anything + // else has to parse — otherwise a typo such as `sql.postgrez` would + // silently compile to generic SQL. + let target = if o.target.is_empty() { + Target::default() + } else { + Target::from_str(&o.target).map_err(prqlc::ErrorMessages::from)? + }; - prqlc::Options { + Ok(prqlc::Options { format: o.format, target, signature_comment: o.signature_comment, display: prqlc::DisplayOptions::Plain, ..Default::default() - } + }) } } diff --git a/prqlc/bindings/js/tests/test_all.mjs b/prqlc/bindings/js/tests/test_all.mjs index 716de8e73534..30ea8b27ee6c 100644 --- a/prqlc/bindings/js/tests/test_all.mjs +++ b/prqlc/bindings/js/tests/test_all.mjs @@ -70,6 +70,30 @@ describe("prqlc-js", () => { ); assert(res.includes("target:sql.mssql")); }); + + it("should throw on an unknown target rather than silently using sql.any", () => { + const opts = new prqlc.CompileOptions(); + opts.target = "sql.postgrez"; + + expect(() => prqlc.compile("from a | take 10", opts)).to.throw( + "sql.postgrez", + ); + }); + + it("should treat an unset target as sql.any", () => { + const opts = new prqlc.CompileOptions(); + opts.format = false; + opts.signature_comment = false; + + const res = prqlc.compile( + "prql target:sql.mssql\nfrom a | take 10", + opts, + ); + assert.equal( + res, + "SELECT * FROM a ORDER BY (SELECT NULL) OFFSET 0 ROWS FETCH FIRST 10 ROWS ONLY", + ); + }); }); describe("prql_to_pl", () => {