From 504885db34d789e30caf06f3169e5f64d9d36895 Mon Sep 17 00:00:00 2001 From: Tobias Brandt Date: Fri, 27 Mar 2026 17:37:35 +0200 Subject: [PATCH 01/12] chore: update insta snapshot format Update inline snapshot string delimiters (@r" -> @", r### -> r#) and remove deprecated snapshot_kind: text metadata from snap files. All changes are format-only with no semantic differences. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../tests/integration/bad_error_messages.rs | 16 +- .../prqlc/tests/integration/error_messages.rs | 60 ++- ...tion__queries__compile__append_select.snap | 1 - ...eries__compile__append_select_compute.snap | 1 - ...ile__append_select_multiple_with_null.snap | 1 - ...n__queries__compileall__append_select.snap | 1 - ...es__compileall__append_select_compute.snap | 1 - ...all__append_select_multiple_with_null.snap | 1 - ...ation__queries__compileall__pipelines.snap | 1 - ...__queries__compileall__set_ops_remove.snap | 1 - prqlc/prqlc/tests/integration/sql.rs | 464 +++++++++--------- 11 files changed, 268 insertions(+), 280 deletions(-) diff --git a/prqlc/prqlc/tests/integration/bad_error_messages.rs b/prqlc/prqlc/tests/integration/bad_error_messages.rs index 6ba0f88bcb71..8ceaa09b372a 100644 --- a/prqlc/prqlc/tests/integration/bad_error_messages.rs +++ b/prqlc/prqlc/tests/integration/bad_error_messages.rs @@ -24,7 +24,7 @@ fn test_bad_error_messages() { assert_snapshot!(compile(r###" from film group - "###).unwrap_err(), @r" + "###).unwrap_err(), @" Error: ╭─[ :3:5 ] │ @@ -44,7 +44,7 @@ fn test_bad_error_messages() { from employees filter f location - "#).unwrap_err(), @r" + "#).unwrap_err(), @" Error: ╭─[ :5:14 ] │ @@ -58,7 +58,7 @@ fn test_bad_error_messages() { assert_snapshot!(compile(r###" select tracks from artists - "###).unwrap_err(), @r" + "###).unwrap_err(), @" Error: ╭─[ :3:5 ] │ @@ -136,7 +136,7 @@ fn misplaced_type_error() { let foo = 123 from t select (true && foo) - "###).unwrap_err(), @r" + "###).unwrap_err(), @" Error: ╭─[ :2:15 ] │ @@ -152,7 +152,7 @@ fn test_hint_missing_args() { assert_snapshot!(compile(r###" from film select {film_id, lag film_id} - "###).unwrap_err(), @r" + "###).unwrap_err(), @" Error: ╭─[ :3:22 ] │ @@ -169,7 +169,7 @@ fn test_hint_missing_args() { fn test_relation_literal_contains_literals() { assert_snapshot!(compile(r###" [{a=(1+1)}] - "###).unwrap_err(), @r" + "###).unwrap_err(), @" Error: ╭─[ :2:9 ] │ @@ -196,7 +196,7 @@ fn nested_groups() { } ) ) - "###).unwrap_err(), @r" + "###).unwrap_err(), @" Error: ╭─[ :9:9 ] │ @@ -213,7 +213,7 @@ fn nested_groups() { fn just_std() { assert_snapshot!(compile(r###" std - "###).unwrap_err(), @r" + "###).unwrap_err(), @" Error: ╭─[ :1:1 ] │ diff --git a/prqlc/prqlc/tests/integration/error_messages.rs b/prqlc/prqlc/tests/integration/error_messages.rs index eab2b72bb251..84ba354d0fc8 100644 --- a/prqlc/prqlc/tests/integration/error_messages.rs +++ b/prqlc/prqlc/tests/integration/error_messages.rs @@ -14,7 +14,7 @@ fn test_errors() { from x derive y = (addadd 4 5 6) "###).unwrap_err(), - @r" + @" Error: ╭─[ :5:17 ] │ @@ -27,7 +27,7 @@ fn test_errors() { assert_snapshot!(compile(r###" from a select b "###).unwrap_err(), - @r" + @" Error: ╭─[ :2:5 ] │ @@ -42,7 +42,7 @@ fn test_errors() { select a select b "###).unwrap_err(), - @r" + @" Error: ╭─[ :4:12 ] │ @@ -58,7 +58,7 @@ fn test_errors() { from employees take 1.8 "###).unwrap_err(), - @r" + @" Error: ╭─[ :3:10 ] │ @@ -88,7 +88,7 @@ fn test_errors() { "#); // PARSER output (full compilation error): - assert_snapshot!(compile(mississippi).unwrap_err(), @r" + assert_snapshot!(compile(mississippi).unwrap_err(), @" Error: ╭─[ :1:23 ] │ @@ -98,7 +98,7 @@ fn test_errors() { ───╯ "); - assert_snapshot!(compile("Answer: T-H-A-T!").unwrap_err(), @r" + assert_snapshot!(compile("Answer: T-H-A-T!").unwrap_err(), @" Error: ╭─[ :1:16 ] │ @@ -117,7 +117,7 @@ fn test_union_all_sqlite() { from film remove film2 - "###).unwrap_err(), @r" + "###).unwrap_err(), @" Error: The dialect SQLiteDialect does not support EXCEPT ALL ↳ Hint: providing more column information will allow the query to be translated to an anti-join. ") @@ -129,7 +129,7 @@ fn test_regex_dialect() { prql target:sql.mssql from foo filter bar ~= 'love' - "###).unwrap_err(), @r" + "###).unwrap_err(), @" Error: ╭─[ :4:12 ] │ @@ -147,7 +147,7 @@ fn test_bad_function_type() { group foo (take) "###, ) - .unwrap_err(), @r" + .unwrap_err(), @" Error: ╭─[ :3:16 ] │ @@ -187,7 +187,7 @@ fn test_ambiguous() { derive date = x select date "#) - .unwrap_err(), @r" + .unwrap_err(), @" Error: ╭─[ :4:12 ] │ @@ -210,7 +210,7 @@ fn test_ambiguous_join() { join (from b | select {x}) true select x "#) - .unwrap_err(), @r" + .unwrap_err(), @" Error: ╭─[ :5:12 ] │ @@ -232,7 +232,7 @@ fn test_ambiguous_inference() { join b(==b_id) select x "#) - .unwrap_err(), @r" + .unwrap_err(), @" Error: ╭─[ :4:12 ] │ @@ -268,7 +268,7 @@ fn date_to_text_with_column_format() { from dates_to_display select {my_date, my_format} select {std.date.to_text my_date my_format} - "#).unwrap_err(), @r" + "#).unwrap_err(), @" Error: ╭─[ :4:11 ] │ @@ -285,7 +285,7 @@ fn date_trunc_with_column_unit() { from dates_to_display select {my_date, my_unit} select {std.date.trunc my_unit my_date} - "#).unwrap_err(), @r" + "#).unwrap_err(), @" Error: ╭─[ :4:11 ] │ @@ -321,7 +321,7 @@ fn available_columns() { from invoices select foo select bar - "#).unwrap_err(), @r" + "#).unwrap_err(), @" Error: ╭─[ :4:12 ] │ @@ -350,20 +350,16 @@ fn empty_interpolations() { #[test] fn no_query_entered() { // Empty query - assert_snapshot!(compile("").unwrap_err(), @r" - [E0001] Error: No PRQL query entered - "); + assert_snapshot!(compile("").unwrap_err(), @"[E0001] Error: No PRQL query entered"); // Comment-only query - assert_snapshot!(compile("# just a comment").unwrap_err(), @r" - [E0001] Error: No PRQL query entered - "); + assert_snapshot!(compile("# just a comment").unwrap_err(), @"[E0001] Error: No PRQL query entered"); } #[test] fn query_must_begin_with_from() { // Query with declaration but no 'from' - assert_snapshot!(compile("let x = 5").unwrap_err(), @r" + assert_snapshot!(compile("let x = 5").unwrap_err(), @" [E0001] Error: PRQL queries must begin with 'from' ↳ Hint: A query must start with a 'from' statement to define the main pipeline "); @@ -372,7 +368,7 @@ fn query_must_begin_with_from() { assert_snapshot!(compile(r#" let x = 5 let y = 10 - "#).unwrap_err(), @r" + "#).unwrap_err(), @" [E0001] Error: PRQL queries must begin with 'from' ↳ Hint: A query must start with a 'from' statement to define the main pipeline "); @@ -384,7 +380,7 @@ fn negative_number_in_transform() { assert_snapshot!(compile(r###" from artists sort -name - "###).unwrap_err(), @r" + "###).unwrap_err(), @" Error: expected a pipeline that resolves to a table, but found `internal std.sub` ↳ Hint: wrap negative numbers in parentheses, e.g. `sort (-column_name)` "); @@ -392,7 +388,7 @@ fn negative_number_in_transform() { assert_snapshot!(compile(r###" from pets take -10 - "###).unwrap_err(), @r" + "###).unwrap_err(), @" Error: expected a pipeline that resolves to a table, but found `internal std.sub` ↳ Hint: wrap negative numbers in parentheses, e.g. `sort (-column_name)` "); @@ -402,7 +398,7 @@ fn negative_number_in_transform() { group id ( sort -val ) - "###).unwrap_err(), @r" + "###).unwrap_err(), @" Error: expected a pipeline that resolves to a table, but found `internal std.sub` ↳ Hint: wrap negative numbers in parentheses, e.g. `sort (-column_name)` "); @@ -412,7 +408,7 @@ fn negative_number_in_transform() { fn empty_tuple_or_array_from() { assert_snapshot!(compile(r###" from {} - "###).unwrap_err(), @r" + "###).unwrap_err(), @" Error: ╭─[ :2:10 ] │ @@ -424,7 +420,7 @@ fn empty_tuple_or_array_from() { assert_snapshot!(compile(r###" from [] - "###).unwrap_err(), @r" + "###).unwrap_err(), @" Error: ╭─[ :2:10 ] │ @@ -437,7 +433,7 @@ fn empty_tuple_or_array_from() { assert_snapshot!(compile(r###" from {} select a - "###).unwrap_err(), @r" + "###).unwrap_err(), @" Error: ╭─[ :2:10 ] │ @@ -454,7 +450,7 @@ fn window_rows_expects_range() { assert_snapshot!(compile(r###" from t group sid (window rows:2 (sid)) - "###).unwrap_err(), @r" + "###).unwrap_err(), @" Error: ╭─[ :3:28 ] │ @@ -467,7 +463,7 @@ fn window_rows_expects_range() { assert_snapshot!(compile(r###" from t group sid (window range:2 (sid)) - "###).unwrap_err(), @r" + "###).unwrap_err(), @" Error: ╭─[ :3:29 ] │ @@ -484,7 +480,7 @@ fn bare_lambda_expression() { // a clear error, not a confusing internal message. assert_snapshot!(compile(r###" x -> y - "###).unwrap_err(), @r" + "###).unwrap_err(), @" Error: ╭─[ :2:5 ] │ diff --git a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compile__append_select.snap b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compile__append_select.snap index 8dfc7501dc1e..88f876b5d360 100644 --- a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compile__append_select.snap +++ b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compile__append_select.snap @@ -2,7 +2,6 @@ source: prqlc/prqlc/tests/integration/queries.rs expression: "from invoices\nselect { customer_id, invoice_id, billing_country }\ntake 10..15\nappend (\n from invoices\n select { customer_id, invoice_id, billing_country }\n take 40..45\n)\nselect { billing_country, invoice_id }\n" input_file: prqlc/prqlc/tests/integration/queries/append_select.prql -snapshot_kind: text --- SELECT * diff --git a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compile__append_select_compute.snap b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compile__append_select_compute.snap index c9df44669064..63ecf95c65c9 100644 --- a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compile__append_select_compute.snap +++ b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compile__append_select_compute.snap @@ -2,7 +2,6 @@ source: prqlc/prqlc/tests/integration/queries.rs expression: "from invoices\nderive total = case [total < 10 => total * 2, true => total]\nselect { customer_id, invoice_id, total }\ntake 5\nappend (\n from invoice_items\n derive unit_price = case [unit_price < 1 => unit_price * 2, true => unit_price]\n select { invoice_line_id, invoice_id, unit_price }\n take 5\n)\nselect { a = customer_id * 2, b = math.round 1 (invoice_id * total) }\n" input_file: prqlc/prqlc/tests/integration/queries/append_select_compute.prql -snapshot_kind: text --- WITH table_1 AS ( SELECT diff --git a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compile__append_select_multiple_with_null.snap b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compile__append_select_multiple_with_null.snap index e69f23f2855d..c8765731da64 100644 --- a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compile__append_select_multiple_with_null.snap +++ b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compile__append_select_multiple_with_null.snap @@ -2,7 +2,6 @@ source: prqlc/prqlc/tests/integration/queries.rs expression: "from invoices\nselect { customer_id, invoice_id, billing_country }\ntake 5\nappend (\n from employees\n select { employee_id, employee_id, country }\n take 5\n)\nappend (\n from invoice_items\n select { invoice_line_id, invoice_id, null }\n take 5\n)\nselect { billing_country, invoice_id }\n" input_file: prqlc/prqlc/tests/integration/queries/append_select_multiple_with_null.prql -snapshot_kind: text --- SELECT * diff --git a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__append_select.snap b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__append_select.snap index 7b997aacd721..2128fd2a81a0 100644 --- a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__append_select.snap +++ b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__append_select.snap @@ -2,7 +2,6 @@ source: prqlc/prqlc/tests/integration/queries.rs expression: "from invoices\nselect { customer_id, invoice_id, billing_country }\ntake 10..15\nappend (\n from invoices\n select { customer_id, invoice_id, billing_country }\n take 40..45\n)\nselect { billing_country, invoice_id }\n" input_file: prqlc/prqlc/tests/integration/queries/append_select.prql -snapshot_kind: text --- --- generic +++ postgres diff --git a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__append_select_compute.snap b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__append_select_compute.snap index 6aa00af03ac3..646883cc44ee 100644 --- a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__append_select_compute.snap +++ b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__append_select_compute.snap @@ -2,7 +2,6 @@ source: prqlc/prqlc/tests/integration/queries.rs expression: "from invoices\nderive total = case [total < 10 => total * 2, true => total]\nselect { customer_id, invoice_id, total }\ntake 5\nappend (\n from invoice_items\n derive unit_price = case [unit_price < 1 => unit_price * 2, true => unit_price]\n select { invoice_line_id, invoice_id, unit_price }\n take 5\n)\nselect { a = customer_id * 2, b = math.round 1 (invoice_id * total) }\n" input_file: prqlc/prqlc/tests/integration/queries/append_select_compute.prql -snapshot_kind: text --- --- generic +++ glaredb diff --git a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__append_select_multiple_with_null.snap b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__append_select_multiple_with_null.snap index ad4e030924e4..d8e8128e49c2 100644 --- a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__append_select_multiple_with_null.snap +++ b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__append_select_multiple_with_null.snap @@ -2,7 +2,6 @@ source: prqlc/prqlc/tests/integration/queries.rs expression: "from invoices\nselect { customer_id, invoice_id, billing_country }\ntake 5\nappend (\n from employees\n select { employee_id, employee_id, country }\n take 5\n)\nappend (\n from invoice_items\n select { invoice_line_id, invoice_id, null }\n take 5\n)\nselect { billing_country, invoice_id }\n" input_file: prqlc/prqlc/tests/integration/queries/append_select_multiple_with_null.prql -snapshot_kind: text --- --- generic +++ postgres diff --git a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__pipelines.snap b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__pipelines.snap index 1cf8fa17fd4b..3858b4e2db31 100644 --- a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__pipelines.snap +++ b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__pipelines.snap @@ -2,7 +2,6 @@ source: prqlc/prqlc/tests/integration/queries.rs expression: "# sqlite:skip (Only works on Sqlite implementations which have the extension\n# installed\n# https://stackoverflow.com/questions/24037982/how-to-use-regexp-in-sqlite)\n\nfrom tracks\n\nfilter (name ~= \"Love\")\nfilter ((milliseconds / 1000 / 60) | in 3..4)\nsort track_id\ntake 1..15\nselect {name, composer}\n" input_file: prqlc/prqlc/tests/integration/queries/pipelines.prql -snapshot_kind: text --- --- generic +++ clickhouse diff --git a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__set_ops_remove.snap b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__set_ops_remove.snap index 02fac1398659..4e28163d8bc0 100644 --- a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__set_ops_remove.snap +++ b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__set_ops_remove.snap @@ -2,7 +2,6 @@ source: prqlc/prqlc/tests/integration/queries.rs expression: "# mssql:test\nlet distinct = rel -> (from t = _param.rel | group {t.*} (take 1))\n\nfrom_text format:json '{ \"columns\": [\"a\"], \"data\": [[1], [2], [2], [3]] }'\ndistinct\nremove (from_text format:json '{ \"columns\": [\"a\"], \"data\": [[1], [2]] }')\nsort a\n" input_file: prqlc/prqlc/tests/integration/queries/set_ops_remove.prql -snapshot_kind: text --- --- generic +++ mssql diff --git a/prqlc/prqlc/tests/integration/sql.rs b/prqlc/prqlc/tests/integration/sql.rs index 006d87b3aca1..6b7aac28b46a 100644 --- a/prqlc/prqlc/tests/integration/sql.rs +++ b/prqlc/prqlc/tests/integration/sql.rs @@ -30,7 +30,7 @@ fn test_stdlib() { {salary_usd = min salary} ) "###).unwrap(), - @r" + @" SELECT MIN(salary) AS salary_usd FROM @@ -44,7 +44,7 @@ fn test_stdlib() { {salary_usd = (math.round 2 salary)} ) "###).unwrap(), - @r" + @" SELECT ROUND(salary, 2) AS salary_usd FROM @@ -78,7 +78,7 @@ fn test_stdlib_math_module() { salary_pow = (salary | math.pow 2), salary_pow_op = salary ** 2, } - "#).unwrap(), @r" + "#).unwrap(), @" SELECT ABS(salary) AS salary_abs, FLOOR(salary) AS salary_floor, @@ -131,7 +131,7 @@ fn test_stdlib_math_module_mssql() { salary_atan = math.atan salary, salary_pow = (salary | math.pow 2), } - "#).unwrap(), @r" + "#).unwrap(), @" SELECT ABS(salary) AS salary_abs, FLOOR(salary) AS salary_floor, @@ -174,7 +174,7 @@ fn test_stdlib_text_module() { name_contains = (name | text.contains "pika"), name_ends_with = (name | text.ends_with "pika"), } - "#).unwrap(), @r" + "#).unwrap(), @" SELECT LOWER(name) AS name_lower, UPPER(name) AS name_upper, @@ -335,7 +335,7 @@ fn date_diff_literals() { assert_snapshot!(compile(r#" from t derive { diff = date.diff month @2021-01-01 @2021-06-30 } - "#).unwrap(), @r" + "#).unwrap(), @" SELECT *, DATEDIFF(month, DATE '2021-01-01', DATE '2021-06-30') AS diff @@ -438,7 +438,7 @@ fn test_precedence_division() { p3 = x / (y / z), # needs parentheses np4 = (x / y) / z, # doesn't need parentheses } - "###).unwrap()), @r" + "###).unwrap()), @" SELECT *, a - (b + c) AS p1, @@ -461,7 +461,7 @@ fn test_sqlite_integer_division() { assert_snapshot!(compile_with_sql_dialect(r#" from t select { x = a // b } - "#, sql::Dialect::SQLite).unwrap(), @r" + "#, sql::Dialect::SQLite).unwrap(), @" SELECT CAST(ABS(a * 1.0 / b) AS INTEGER) * SIGN(a) * SIGN(b) AS x FROM @@ -479,7 +479,7 @@ fn test_precedence_01() { np1 = a + (b - c), # no parentheses np2 = (a + b) - c, # no parentheses } - "###).unwrap()), @r" + "###).unwrap()), @" SELECT *, a - (b + c) AS p1, @@ -500,7 +500,7 @@ fn test_precedence_02() { temp_f = temp_c * 9/5, temp_z = temp_x + 9 - 5, } - "###).unwrap()), @r" + "###).unwrap()), @" SELECT *, (temp_f - 32) / 1.8 AS temp_c, @@ -524,7 +524,7 @@ fn test_precedence_03() { result = c * sum_1 + sum_2, a * g } - "###).unwrap()), @r" + "###).unwrap()), @" SELECT c * (a + b) + a + b AS result, a * - a @@ -548,7 +548,7 @@ fn test_precedence_04() { is_not_null = !(a == null), (a + b) == null, } - "###).unwrap()), @r" + "###).unwrap()), @" SELECT a > 0 AS gtz, NOT a > 0 AS ltz, @@ -584,7 +584,7 @@ fn test_precedence_05() { -x, } "### - ).unwrap(), @r" + ).unwrap(), @" SELECT c - (a + b), c + a - b, @@ -625,7 +625,7 @@ fn test_append() { assert_snapshot!(compile(r###" from employees append managers - "###).unwrap(), @r" + "###).unwrap(), @" SELECT * FROM @@ -647,7 +647,7 @@ fn test_append() { select {name, cost = salary + bonuses} take 10 ) - "###).unwrap(), @r" + "###).unwrap(), @" SELECT * FROM @@ -682,7 +682,7 @@ fn test_append() { from employees union (from managers) - "###).unwrap(), @r" + "###).unwrap(), @" SELECT * FROM @@ -702,7 +702,7 @@ fn test_append() { from employees append managers union all_employees_of_some_other_company - "###).unwrap(), @r" + "###).unwrap(), @" SELECT * FROM @@ -728,7 +728,7 @@ fn test_remove_01() { from albums remove artists "#).unwrap(), - @r" + @" SELECT * FROM @@ -752,7 +752,7 @@ fn test_remove_02() { from artist | select artist_id ) "#).unwrap(), - @r" + @" WITH table_0 AS ( SELECT artist_id @@ -782,7 +782,7 @@ fn test_remove_03() { from artist | select artist_id ) "#).unwrap(), - @r" + @" WITH table_0 AS ( SELECT artist_id @@ -809,7 +809,7 @@ fn test_remove_04() { from album remove artist "#).unwrap_err(), - @r" + @" Error: The dialect SQLiteDialect does not support EXCEPT ALL ↳ Hint: providing more column information will allow the query to be translated to an anti-join. " @@ -828,7 +828,7 @@ fn test_remove_05() { select {artist_id, title} except (from artist | select {artist_id, name}) "#).unwrap(), - @r" + @" WITH table_0 AS ( SELECT artist_id, @@ -861,7 +861,7 @@ fn test_remove_06() { from album except artist "#).unwrap(), - @r" + @" SELECT * FROM @@ -881,7 +881,7 @@ fn test_intersect_01() { from album intersect artist "#).unwrap(), - @r" + @" SELECT * FROM @@ -905,7 +905,7 @@ fn test_intersect_02() { from artist | select artist_id ) "#).unwrap(), - @r" + @" WITH table_0 AS ( SELECT artist_id @@ -939,7 +939,7 @@ fn test_intersect_03() { ) distinct "#).unwrap(), - @r" + @" WITH table_0 AS ( SELECT artist_id @@ -978,7 +978,7 @@ fn test_intersect_04() { ) distinct "#).unwrap(), - @r" + @" WITH table_0 AS ( SELECT artist_id @@ -1017,7 +1017,7 @@ fn test_intersect_05() { from artist | select artist_id ) "#).unwrap(), - @r" + @" WITH table_0 AS ( SELECT artist_id @@ -1046,7 +1046,7 @@ fn test_intersect_06() { from album intersect artist "#).unwrap_err(), - @r" + @" Error: The dialect SQLiteDialect does not support INTERSECT ALL ↳ Hint: providing more column information will allow the query to be translated to an anti-join. " @@ -1060,7 +1060,7 @@ fn test_intersect_07() { join side:inner ds1 = bar.t2 (ds2.idx==ds1.idx) aggregate { count this } "#).unwrap(), - @r" + @" SELECT COUNT(*) FROM @@ -1119,7 +1119,7 @@ fn test_sort_in_nested_join_with_extra_derive_and_select() { select {this.my_new_col, this.new_name, this.other_new_name} ) (this.id == that.my_new_col) "#).unwrap(), - @r" + @" WITH table_1 AS ( SELECT CONCAT('artist: ', name) AS my_new_col, @@ -1173,7 +1173,7 @@ fn test_sort_in_nested_append() { take 2 ) "#).unwrap(), - @r" + @" SELECT * FROM @@ -1223,7 +1223,7 @@ fn test_sort_select_redundant_cte() { ) from b "# - ).unwrap()), @r" + ).unwrap()), @" WITH a AS ( SELECT foo @@ -1253,7 +1253,7 @@ join side:left ( s"SELECT id, name FROM `artists`" ) (this.artist_id == that.id) "#).unwrap(), - @r" + @" WITH table_0 AS ( SELECT album_id, @@ -1277,7 +1277,7 @@ join side:left ( FROM table_0 LEFT OUTER JOIN table_1 ON table_0.artist_id = table_1.id - " + " ) } @@ -1292,7 +1292,7 @@ fn test_rn_ids_are_unique() { group {x_id} ( take 3 ) - "###).unwrap()), @r" + "###).unwrap()), @" WITH table_1 AS ( SELECT *, @@ -1368,7 +1368,7 @@ fn test_quoting_03() { from `schema.table` join `schema.table2` (==id) join c = `schema.t-able` (`schema.table`.id == c.id) - "###).unwrap()), @r" + "###).unwrap()), @" SELECT `schema.table`.*, `schema.table2`.*, @@ -1414,7 +1414,7 @@ from `some_dataset.demo` select {`hash`} "; - assert_snapshot!(compile(prql).unwrap(), @r" + assert_snapshot!(compile(prql).unwrap(), @" SELECT `hash` FROM @@ -1428,7 +1428,7 @@ fn test_sorts_01() { from invoices sort {issued_at, -amount, +num_of_articles} "### - ).unwrap()), @r" + ).unwrap()), @" SELECT * FROM @@ -1445,7 +1445,7 @@ fn test_sorts_01() { sort {somefield} select {renamed = somefield} "# - ).unwrap()), @r" + ).unwrap()), @" WITH table_0 AS ( SELECT 'something' AS renamed, @@ -1508,7 +1508,7 @@ fn test_sorts_03() { select !{a.col} take 5 "# - ).unwrap()), @r" + ).unwrap()), @" WITH table_0 AS ( SELECT a.*, @@ -1538,7 +1538,7 @@ fn test_sort_before_aggregate() { sort a.col aggregate { result = sum a.col_to_agg } "# - ).unwrap()), @r" + ).unwrap()), @" SELECT COALESCE(SUM(col_to_agg), 0) AS result FROM @@ -1559,7 +1559,7 @@ fn test_numbers() { } "###; - assert_snapshot!((compile(query).unwrap()), @r" + assert_snapshot!((compile(query).unwrap()), @" SELECT 5.0000001 AS v, 5000 AS w, @@ -1581,7 +1581,7 @@ fn test_ranges() { far = (distance | in 100..), (country_founding | in @1776-07-04..@1787-09-17) } - "###).unwrap()), @r" + "###).unwrap()), @" SELECT *, distance <= 50 AS close, @@ -1601,7 +1601,7 @@ fn test_in_values_01() { filter (employee_id | in [1, 2, 5]) filter (f"{emp_group}.{role}" | in ["sales_ne.mgr", "sales_mw.mgr"]) filter (s"{metadata} ->> '$.location'" | in ["Northeast", "Midwest"]) - "#).unwrap()), @r" + "#).unwrap()), @" SELECT * FROM @@ -1659,7 +1659,7 @@ fn test_not_in_values() { assert_snapshot!((compile(r#" from employees filter !(title | in ["Sales Manager", "Sales Support Agent"]) - "#).unwrap()), @r" + "#).unwrap()), @" SELECT * FROM @@ -1674,7 +1674,7 @@ fn test_in_no_values() { assert_snapshot!((compile(r#" from employees filter (title | in []) - "#).unwrap()), @r" + "#).unwrap()), @" SELECT * FROM @@ -1689,7 +1689,7 @@ fn test_in_values_err_01() { assert_snapshot!((compile(r###" from employees derive { ng = ([1, 2] | in [3, 4]) } - "###).unwrap_err()), @r" + "###).unwrap_err()), @" Error: ╭─[ :3:29 ] │ @@ -1768,7 +1768,7 @@ fn test_dates() { time = @14:00, # datetime = @2011-02-01T10:00, } - "###).unwrap()), @r" + "###).unwrap()), @" SELECT *, DATE '2011-02-01' AS date, @@ -1786,7 +1786,7 @@ fn test_window_functions_00() { group last_name ( derive {count first_name} ) - "###).unwrap()), @r" + "###).unwrap()), @" SELECT *, COUNT(*) OVER (PARTITION BY last_name) @@ -1821,7 +1821,7 @@ fn test_window_functions_02() { derive {num_books_last_week = lag 7 num_books} "#; - assert_snapshot!((compile(query).unwrap()), @r" + assert_snapshot!((compile(query).unwrap()), @" WITH table_0 AS ( SELECT TO_CHAR(co.order_date, '%Y-%m') AS order_month, @@ -1872,7 +1872,7 @@ fn test_window_functions_03() { ) "###; - assert_snapshot!((compile(query).unwrap()), @r" + assert_snapshot!((compile(query).unwrap()), @" SELECT *, LAG(num_orders, 7) OVER () AS last_week, @@ -1894,7 +1894,7 @@ fn test_window_functions_04() { derive {last_week = lag 7 num_orders} "###; - assert_snapshot!((compile(query).unwrap()), @r" + assert_snapshot!((compile(query).unwrap()), @" SELECT *, RANK() OVER (PARTITION BY month) AS total_month, @@ -1913,7 +1913,7 @@ fn test_window_functions_05() { group month (sort num_orders | window expanding:true (derive {rank day})) derive {num_orders_last_week = lag 7 num_orders} "###; - assert_snapshot!((compile(query).unwrap()), @r" + assert_snapshot!((compile(query).unwrap()), @" SELECT *, RANK() OVER ( @@ -1936,7 +1936,7 @@ fn test_window_functions_06() { group c ( derive {d = sum b} ) - "###).unwrap()), @r" + "###).unwrap()), @" SELECT *, SUM(b) OVER () AS a, @@ -1953,7 +1953,7 @@ fn test_window_functions_07() { window expanding:true ( derive {running_total = sum b} ) - "###).unwrap()), @r" + "###).unwrap()), @" SELECT *, SUM(b) OVER (ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running_total @@ -1969,7 +1969,7 @@ fn test_window_functions_08() { window rolling:3 ( derive {last_three = sum b} ) - "###).unwrap()), @r" + "###).unwrap()), @" SELECT *, SUM(b) OVER (ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS last_three @@ -1985,7 +1985,7 @@ fn test_window_functions_09() { window rows:0..4 ( derive {next_four_rows = sum b} ) - "###).unwrap()), @r" + "###).unwrap()), @" SELECT *, SUM(b) OVER ( @@ -2005,7 +2005,7 @@ fn test_window_functions_10() { window range:-4..4 ( derive {next_four_days = sum b} ) - "###).unwrap()), @r" + "###).unwrap()), @" SELECT *, SUM(b) OVER ( @@ -2025,7 +2025,7 @@ fn test_window_functions_11() { from employees sort age derive {num = row_number this} - "###).unwrap()), @r" + "###).unwrap()), @" SELECT *, ROW_NUMBER() OVER ( @@ -2050,7 +2050,7 @@ fn test_window_functions_12() { sort b derive {c = lag 1 a} ) - "###).unwrap()), @r" + "###).unwrap()), @" WITH table_0 AS ( SELECT *, @@ -2076,7 +2076,7 @@ fn test_window_functions_12() { group b ( derive {c = lag 1 a} ) - "###).unwrap()), @r" + "###).unwrap()), @" WITH table_0 AS ( SELECT LAG(a, 1) OVER () AS b, @@ -2104,7 +2104,7 @@ fn test_window_functions_13() { group {grp} ( window (derive {count = row_number this}) ) - "###).unwrap()), @r" + "###).unwrap()), @" WITH table_0 AS ( SELECT *, @@ -2131,7 +2131,7 @@ fn test_window_single_item_range() { last_user = min user_id } ) - "###).unwrap(), @r" + "###).unwrap(), @" SELECT *, MIN(user_id) OVER ( @@ -2152,7 +2152,7 @@ fn test_name_resolving() { derive x = 5 select {y = 6, z = x + y + a} "###; - assert_snapshot!((compile(query).unwrap()), @r" + assert_snapshot!((compile(query).unwrap()), @" SELECT 6 AS y, 5 + 6 + a AS z @@ -2208,7 +2208,7 @@ fn test_filter() { assert_snapshot!((compile(r###" from employees filter age > 25 && age < 40 - "###).unwrap()), @r" + "###).unwrap()), @" SELECT * FROM @@ -2222,7 +2222,7 @@ fn test_filter() { from employees filter age > 25 filter age < 40 - "###).unwrap()), @r" + "###).unwrap()), @" SELECT * FROM @@ -2238,7 +2238,7 @@ fn test_nulls_01() { assert_snapshot!((compile(r###" from employees select amount = null - "###).unwrap()), @r" + "###).unwrap()), @" SELECT NULL AS amount FROM @@ -2252,7 +2252,7 @@ fn test_nulls_02() { assert_snapshot!((compile(r###" from employees derive amount = amount + 2 ?? 3 * 5 - "###).unwrap()), @r" + "###).unwrap()), @" SELECT *, COALESCE(amount + 2, 3 * 5) AS amount @@ -2267,7 +2267,7 @@ fn test_nulls_03() { assert_snapshot!((compile(r###" from employees filter first_name == null && null == last_name - "###).unwrap()), @r" + "###).unwrap()), @" SELECT * FROM @@ -2284,7 +2284,7 @@ fn test_nulls_04() { assert_snapshot!((compile(r###" from employees filter first_name != null && null != last_name - "###).unwrap()), @r" + "###).unwrap()), @" SELECT * FROM @@ -2300,7 +2300,7 @@ fn test_take_01() { assert_snapshot!((compile(r###" from employees take ..10 - "###).unwrap()), @r" + "###).unwrap()), @" SELECT * FROM @@ -2315,7 +2315,7 @@ fn test_take_02() { assert_snapshot!((compile(r###" from employees take 5..10 - "###).unwrap()), @r" + "###).unwrap()), @" SELECT * FROM @@ -2330,7 +2330,7 @@ fn test_take_03() { assert_snapshot!((compile(r###" from employees take 5.. - "###).unwrap()), @r" + "###).unwrap()), @" SELECT * FROM @@ -2343,7 +2343,7 @@ fn test_take_04() { assert_snapshot!((compile(r###" from employees take 5..5 - "###).unwrap()), @r" + "###).unwrap()), @" SELECT * FROM @@ -2360,7 +2360,7 @@ fn test_take_05() { from employees take 11..20 take 1..5 - "###).unwrap()), @r" + "###).unwrap()), @" SELECT * FROM @@ -2378,7 +2378,7 @@ fn test_take_06() { take 11..20 sort name take 1..5 - "###).unwrap()), @r" + "###).unwrap()), @" WITH table_0 AS ( SELECT * @@ -2403,7 +2403,7 @@ fn test_take_07() { assert_snapshot!((compile(r###" from employees take 0..1 - "###).unwrap_err()), @r" + "###).unwrap_err()), @" Error: ╭─[ :3:5 ] │ @@ -2419,7 +2419,7 @@ fn test_take_08() { assert_snapshot!((compile(r###" from employees take (-1..) - "###).unwrap_err()), @r" + "###).unwrap_err()), @" Error: ╭─[ :3:5 ] │ @@ -2436,7 +2436,7 @@ fn test_take_09() { from employees select a take 5..5.6 - "###).unwrap_err()), @r" + "###).unwrap_err()), @" Error: ╭─[ :4:5 ] │ @@ -2452,7 +2452,7 @@ fn test_take_10() { assert_snapshot!((compile(r###" from employees take (-1) - "###).unwrap_err()), @r" + "###).unwrap_err()), @" Error: ╭─[ :3:5 ] │ @@ -2470,7 +2470,7 @@ fn test_take_mssql() { from tracks take 3..5 - "#).unwrap()), @r" + "#).unwrap()), @" SELECT * FROM @@ -2489,7 +2489,7 @@ fn test_take_mssql() { from tracks take ..5 - "#).unwrap()), @r" + "#).unwrap()), @" SELECT * FROM @@ -2508,7 +2508,7 @@ fn test_take_mssql() { from tracks take 3.. - "#).unwrap()), @r" + "#).unwrap()), @" SELECT * FROM @@ -2529,7 +2529,7 @@ fn test_mssql_distinct_fetch() { take 100 group {this.`District`} (take 1) select {this.`District`} - "#).unwrap()), @r###" + "#).unwrap()), @r#" SELECT DISTINCT "District" FROM @@ -2538,7 +2538,7 @@ fn test_mssql_distinct_fetch() { "District" OFFSET 0 ROWS FETCH FIRST 100 ROWS ONLY - "###); + "#); // Case 2: ExprWithAlias - uses the alias for ORDER BY assert_snapshot!((compile(r#" @@ -2548,7 +2548,7 @@ fn test_mssql_distinct_fetch() { take 100 group {d = this.`District`} (take 1) select {d} - "#).unwrap()), @r###" + "#).unwrap()), @r#" SELECT DISTINCT "District" AS d FROM @@ -2557,7 +2557,7 @@ fn test_mssql_distinct_fetch() { d OFFSET 0 ROWS FETCH FIRST 100 ROWS ONLY - "###); + "#); // Case 3: Multiple columns - uses first column for ORDER BY assert_snapshot!((compile(r#" @@ -2567,7 +2567,7 @@ fn test_mssql_distinct_fetch() { take 100 group {this.`A`, this.`B`} (take 1) select {this.`A`, this.`B`} - "#).unwrap()), @r###" + "#).unwrap()), @r#" SELECT DISTINCT "A", "B" @@ -2577,7 +2577,7 @@ fn test_mssql_distinct_fetch() { "A" OFFSET 0 ROWS FETCH FIRST 100 ROWS ONLY - "###); + "#); } #[test] @@ -2587,7 +2587,7 @@ fn test_distinct_01() { from employees derive {rn = row_number id} filter rn > 2 - "###).unwrap()), @r" + "###).unwrap()), @" WITH table_0 AS ( SELECT *, @@ -2611,7 +2611,7 @@ fn test_distinct_02() { from employees select first_name group first_name (take 1) - "###).unwrap()), @r" + "###).unwrap()), @" SELECT DISTINCT first_name FROM @@ -2626,7 +2626,7 @@ fn test_distinct_03() { from employees select {first_name, last_name} group {first_name, last_name} (take 1) - "###).unwrap()), @r" + "###).unwrap()), @" SELECT DISTINCT first_name, last_name @@ -2641,7 +2641,7 @@ fn test_distinct_04() { assert_snapshot!((compile(r###" from employees group {first_name, last_name} (take 1) - "###).unwrap()), @r" + "###).unwrap()), @" WITH table_0 AS ( SELECT *, @@ -2672,7 +2672,7 @@ fn test_distinct_06() { assert_snapshot!((compile(r###" from employees group department (take 3) - "###).unwrap()), @r" + "###).unwrap()), @" WITH table_0 AS ( SELECT *, @@ -2693,7 +2693,7 @@ fn test_distinct_07() { assert_snapshot!((compile(r###" from employees group department (sort salary | take 2..3) - "###).unwrap()), @r" + "###).unwrap()), @" WITH table_0 AS ( SELECT *, @@ -2718,7 +2718,7 @@ fn test_distinct_08() { assert_snapshot!((compile(r###" from employees group department (sort salary | take 4..4) - "###).unwrap()), @r" + "###).unwrap()), @" WITH table_0 AS ( SELECT *, @@ -2748,7 +2748,7 @@ fn test_distinct_09() { take 1 ) sort billing_city - ").unwrap(), @r" + ").unwrap(), @" WITH table_0 AS ( SELECT billing_city, @@ -2779,7 +2779,7 @@ fn test_distinct_on_01() { sort age take 1 ) - "###).unwrap()), @r" + "###).unwrap()), @" SELECT DISTINCT ON (department) * FROM @@ -2798,7 +2798,7 @@ fn test_distinct_on_02() { from x select {class, begins} group {begins} (take 1) - "###).unwrap()), @r" + "###).unwrap()), @" SELECT DISTINCT ON (begins) begins, class @@ -2818,7 +2818,7 @@ fn test_distinct_on_03() { ) derive foo = 1 select foo - "###).unwrap()), @r" + "###).unwrap()), @" WITH table_0 AS ( SELECT DISTINCT ON (col1) * @@ -2844,7 +2844,7 @@ fn test_distinct_on_04() { take 1 ) select {a.id, b.y} - "###).unwrap()), @r" + "###).unwrap()), @" SELECT DISTINCT ON (a.id) a.id, b.y @@ -2867,7 +2867,7 @@ fn test_group_take_n_01() { sort age take 2 ) - "###).unwrap()), @r" + "###).unwrap()), @" WITH table_0 AS ( SELECT *, @@ -2898,7 +2898,7 @@ fn test_group_take_n_02() { sort age take 2.. ) - "###).unwrap()), @r" + "###).unwrap()), @" WITH table_0 AS ( SELECT *, @@ -2924,7 +2924,7 @@ fn test_join() { assert_snapshot!((compile(r###" from x join y (==id) - "###).unwrap()), @r" + "###).unwrap()), @" SELECT x.*, y.* @@ -2943,7 +2943,7 @@ fn test_join_side_literal() { from x join y (==id) side:my_side - "###).unwrap()), @r" + "###).unwrap()), @" SELECT x.*, y.* @@ -2960,7 +2960,7 @@ fn test_join_side_literal_err() { from x join y (==id) side:my_side - "###).unwrap_err()), @r" + "###).unwrap_err()), @" Error: ╭─[ :5:24 ] │ @@ -2980,7 +2980,7 @@ fn test_join_side_literal_via_func() { from x my_join default_db.y this.id s:"left" - "###).unwrap()), @r" + "###).unwrap()), @" SELECT x.*, y.* @@ -3030,7 +3030,7 @@ fn test_join_with_param_name_collision() { select { event_id = a.event_id, } - "###).unwrap()), @r" + "###).unwrap()), @" WITH a AS ( SELECT event_id, @@ -3103,7 +3103,7 @@ fn test_f_string() { assert_snapshot!( compile(query).unwrap(), - @r" + @" SELECT CONCAT( 'Hello my name is ', @@ -3126,7 +3126,7 @@ fn test_f_string() { .with_target(Target::Sql(Some(sql::Dialect::SQLite))) ).unwrap(), - @r" + @" SELECT 'Hello my name is ' || prefix || first_name || ' ' || last_name, 'and I am ' || year_born - now() || ' years old.' @@ -3150,7 +3150,7 @@ fn test_sql_of_ast_1() { let sql = compile(query).unwrap(); assert_snapshot!(sql, - @r" + @" SELECT title, country, @@ -3176,7 +3176,7 @@ fn test_sql_of_ast_02() { from employees aggregate sum_salary = s"sum({salary})" filter sum_salary > 100 - "#).unwrap(), @r" + "#).unwrap(), @" SELECT sum(salary) AS sum_salary FROM @@ -3201,7 +3201,7 @@ fn test_bare_s_string() { let sql = compile(query).unwrap(); assert_snapshot!(sql, - @r" + @" WITH table_0 AS ( SELECT SUM(a) @@ -3225,7 +3225,7 @@ fn test_bare_s_string_01() { let a = s"select insensitive from rude" from a "#).unwrap(), - @r" + @" WITH table_0 AS ( SELECT insensitive @@ -3247,7 +3247,7 @@ fn test_bare_s_string_02() { let a = s"sElEcT insensitive from rude" from a "#).unwrap(), - @r" + @" WITH table_0 AS ( SELECT insensitive @@ -3273,7 +3273,7 @@ fn test_bare_s_string_03() { bar" from a - "#).unwrap(), @r" + "#).unwrap(), @" WITH table_0 AS ( SELECT foo @@ -3291,7 +3291,7 @@ fn test_bare_s_string_03() { fn test_bare_s_string_04() { assert_snapshot!(compile(r#" s"SELECTTfoo" - "#).unwrap_err(), @r" + "#).unwrap_err(), @" Error: s-strings representing a table must start with `SELECT ` ↳ Hint: this is a limitation by current compiler implementation "); @@ -3307,7 +3307,7 @@ fn test_table_definition_with_expr_call() { let sql = compile(query).unwrap(); assert_snapshot!(sql, - @r" + @" WITH e AS ( SELECT * @@ -3332,7 +3332,7 @@ fn test_prql_to_sql_1() { count salary, sum salary, } - "#).unwrap(), @r" + "#).unwrap(), @" SELECT COUNT(*), COALESCE(SUM(salary), 0) @@ -3348,7 +3348,7 @@ fn test_prql_to_sql_1() { skill_width = count_distinct specialty, } ) - "#).unwrap(), @r" + "#).unwrap(), @" SELECT team, COUNT(DISTINCT specialty) AS skill_width @@ -3502,7 +3502,7 @@ fn test_nonatomic() { sort sum_gross_cost "#; - assert_snapshot!((compile(query).unwrap()), @r" + assert_snapshot!((compile(query).unwrap()), @" WITH table_1 AS ( SELECT title, @@ -3550,7 +3550,7 @@ fn test_nonatomic() { filter sum_gross_cost > 0 "###; - assert_snapshot!((compile(query).unwrap()), @r" + assert_snapshot!((compile(query).unwrap()), @" SELECT title, country, @@ -3582,7 +3582,7 @@ fn test_nonatomic_table() { select {name, salary, average_country_salary} "#; - assert_snapshot!((compile(query).unwrap()), @r" + assert_snapshot!((compile(query).unwrap()), @" WITH table_0 AS ( SELECT country @@ -3618,7 +3618,7 @@ fn test_table_names_between_splits_01() { derive emp_no = employees.emp_no join s = salaries (==emp_no) select {employees.emp_no, d.name, s.salary} - "###).unwrap(), @r" + "###).unwrap(), @" WITH table_0 AS ( SELECT employees.emp_no, @@ -3646,7 +3646,7 @@ fn test_table_names_between_splits_02() { take 10 join salaries (==emp_no) select {e.*, salaries.salary} - "###).unwrap(), @r" + "###).unwrap(), @" WITH table_0 AS ( SELECT * @@ -3675,7 +3675,7 @@ fn test_table_alias_01() { } ) select {emp_no, emp_salary} - "###).unwrap()), @r" + "###).unwrap()), @" SELECT e.emp_no, AVG(salaries.salary) AS emp_salary @@ -3693,7 +3693,7 @@ fn test_table_alias_02() { from e = employees select e.first_name filter e.first_name == "Fred" - "#).unwrap()), @r" + "#).unwrap()), @" SELECT first_name FROM @@ -3754,7 +3754,7 @@ fn test_targets() { take 3 "###; - assert_snapshot!((compile(query).unwrap()), @r" + assert_snapshot!((compile(query).unwrap()), @" SELECT `FirstName`, `last name` @@ -3774,7 +3774,7 @@ fn test_target_clickhouse() { derive {event_type_dotted = `event.type`} "###; - assert_snapshot!((compile(query).unwrap()), @r" + assert_snapshot!((compile(query).unwrap()), @" SELECT *, `event.type` AS event_type_dotted @@ -3827,7 +3827,7 @@ fn test_literal() { let sql = compile(query).unwrap(); assert_snapshot!(sql, - @r" + @" SELECT *, true AS always_true @@ -3856,7 +3856,7 @@ join y (foo == only_in_x) "###; assert_snapshot!(compile(query).unwrap(), - @r" + @" WITH x AS ( SELECT foo AS only_in_x @@ -3906,7 +3906,7 @@ fn test_double_aggregate() { } ) "###).unwrap(), - @r" + @" SELECT type, COALESCE(SUM(amount), 0) AS total_amt, @@ -3932,7 +3932,7 @@ fn test_window_function_coalesce() { } ) "###).unwrap(), - @r" + @" SELECT SUM(a) OVER () AS cumsum_a, SUM(a) OVER () AS cumsum_b @@ -3954,7 +3954,7 @@ fn test_casting() { e = (a | as float) / 10, } "###).unwrap(), - @r" + @" SELECT a, CAST(a AS int) + 10 AS b, @@ -3982,7 +3982,7 @@ fn test_toposort() { from b "###).unwrap(), - @r" + @" WITH b AS ( SELECT * @@ -4006,7 +4006,7 @@ fn test_inline_tables() { ) join s = (from salaries | select {emp_id, salary}) (==emp_id) "###).unwrap(), - @r" + @" WITH table_0 AS ( SELECT emp_id, @@ -4038,7 +4038,7 @@ fn test_filter_and_select_unchanged_alias() { filter account.name != null select {name = account.name} "###).unwrap(), - @r" + @" SELECT name FROM @@ -4057,7 +4057,7 @@ fn test_filter_and_select_changed_alias() { filter account.name != null select {renamed_name = account.name} "###).unwrap(), - @r" + @" SELECT name AS renamed_name FROM @@ -4073,7 +4073,7 @@ fn test_filter_and_select_changed_alias() { filter name != "Bob" select name = name ?? "Default" "#).unwrap(), - @r" + @" SELECT COALESCE(name, 'Default') AS name FROM @@ -4090,7 +4090,7 @@ fn test_unused_alias() { assert_snapshot!(compile(r###" from account select n = {account.name} - "###).unwrap_err(), @r" + "###).unwrap_err(), @" Error: ╭─[ :3:16 ] │ @@ -4108,7 +4108,7 @@ fn test_table_s_string_01() { assert_snapshot!(compile(r#" let main = s"SELECT DISTINCT ON first_name, age FROM employees ORDER BY age ASC" "#).unwrap(), - @r" + @" WITH table_0 AS ( SELECT DISTINCT ON first_name, @@ -4133,7 +4133,7 @@ fn test_table_s_string_02() { """ join s = s"SELECT * FROM salaries" (==id) "#).unwrap(), - @r" + @" WITH table_0 AS ( SELECT DISTINCT ON first_name, @@ -4165,7 +4165,7 @@ fn test_table_s_string_03() { s"""SELECT * FROM employees""" filter country == "USA" "#).unwrap(), - @r" + @" WITH table_0 AS ( SELECT * @@ -4188,7 +4188,7 @@ fn test_table_s_string_04() { select {e = this} filter e.country == "USA" "#).unwrap(), - @r" + @" WITH table_0 AS ( SELECT * @@ -4212,7 +4212,7 @@ fn test_table_s_string_05() { weeks_between @2022-06-03 (current_week + 4) "#).unwrap(), - @r" + @" WITH table_0 AS ( SELECT generate_series( @@ -4233,7 +4233,7 @@ fn test_table_s_string_06() { assert_snapshot!(compile(r#" s"SELECT * FROM {default_db.x}" "#).unwrap(), - @r" + @" WITH table_0 AS ( SELECT * @@ -4274,7 +4274,7 @@ fn test_direct_table_references() { select x "###, ) - .unwrap(), @r" + .unwrap(), @" SELECT * FROM @@ -4293,7 +4293,7 @@ fn test_table_variable_in_scalar_context() { filter actor_id == mod_id "#, ) - .unwrap_err(), @r#" + .unwrap_err(), @" Error: ╭─[ :5:24 ] │ @@ -4303,7 +4303,7 @@ fn test_table_variable_in_scalar_context() { │ │ Help: use a join instead, or inline the subquery ───╯ - "#); + "); } #[test] @@ -4313,7 +4313,7 @@ fn test_name_shadowing() { from x select {a, a, a = a + 1} "###).unwrap(), - @r" + @" SELECT a AS _expr_0, a + 1 AS a @@ -4330,7 +4330,7 @@ fn test_name_shadowing() { derive a = a + 1 derive a = a + 2 "###).unwrap(), - @r" + @" SELECT a AS _expr_0, a + 1, @@ -4367,7 +4367,7 @@ fn test_output_column_deduplication() { derive r = s"RANK() OVER ()" filter r == 1 "#).unwrap(), - @r" + @" WITH table_0 AS ( SELECT *, @@ -4395,7 +4395,7 @@ fn test_case_01() { true => f'{first_name} {last_name}' ] "###).unwrap(), - @r" + @" SELECT *, CASE @@ -4418,7 +4418,7 @@ fn test_case_02() { first_name != null => f'{first_name} {last_name}' ] "###).unwrap(), - @r" + @" SELECT *, CASE @@ -4442,7 +4442,7 @@ fn test_case_03() { ] group category (aggregate {count this}) "###).unwrap(), - @r" + @" WITH table_0 AS ( SELECT CASE @@ -4500,7 +4500,7 @@ fn test_static_analysis() { ], } "###).unwrap(), - @r" + @" SELECT 3 AS a, false AS b, @@ -4529,7 +4529,7 @@ fn test_closures_and_pipelines() { arg "citrus" ) "#).unwrap(), - @r" + @" SELECT 'apples' || 'bananas' || 'citrus' AS x FROM @@ -4547,7 +4547,7 @@ fn test_basic_agg() { count this, } "#).unwrap(), - @r" + @" SELECT COUNT(*), COUNT(*) @@ -4564,7 +4564,7 @@ fn test_exclude_columns_01() { select {track_id, title, composer, bytes} select !{title, composer} "#).unwrap(), - @r" + @" SELECT track_id, bytes @@ -4581,7 +4581,7 @@ fn test_exclude_columns_02() { select {track_id, title, composer, bytes} group !{title, composer} (aggregate {count this}) "#).unwrap(), - @r" + @" SELECT track_id, bytes, @@ -4602,7 +4602,7 @@ fn test_exclude_columns_03() { derive nick = name select !{artists.*} "#).unwrap(), - @r" + @" SELECT name AS nick FROM @@ -4618,7 +4618,7 @@ fn test_exclude_columns_04() { from tracks select !{milliseconds,bytes} "#).unwrap(), - @r" + @" SELECT * EXCEPT (milliseconds, bytes) FROM @@ -4650,7 +4650,7 @@ fn test_exclude_columns_06() { from tracks select !{milliseconds,bytes} "#).unwrap(), - @r" + @" SELECT * EXCLUDE (milliseconds, bytes) FROM @@ -4666,7 +4666,7 @@ fn test_exclude_columns_07() { from s"SELECT * FROM foo" select !{bar} "#).unwrap(), - @r" + @" WITH table_0 AS ( SELECT * @@ -4693,7 +4693,7 @@ fn test_custom_transforms() { my_transform take 3 "#).unwrap(), - @r" + @" SELECT *, single * 2 AS double @@ -4714,7 +4714,7 @@ fn test_name_inference() { select {artist_id + album_id} # nothing inferred infer "#).unwrap(), - @r" + @" SELECT artist_id + album_id FROM @@ -4742,7 +4742,7 @@ fn test_name_inference() { assert_snapshot!( sql1, - @r" + @" SELECT artist_id FROM @@ -4761,7 +4761,7 @@ a,b,c """ select {b, c} "#).unwrap(), - @r" + @" WITH table_0 AS ( SELECT '1' AS a, @@ -4791,7 +4791,7 @@ fn test_from_text_02() { ''' select {b, c} "#).unwrap(), - @r" + @" WITH table_0 AS ( SELECT 1 AS a, @@ -4825,7 +4825,7 @@ fn test_from_text_03() { }''' select {b, c} "#).unwrap(), - @r" + @" WITH table_0 AS ( SELECT 1 AS a, @@ -4852,7 +4852,7 @@ fn test_from_text_04() { assert_snapshot!(compile(r#" std.from_text 'a,b' "#).unwrap(), - @r" + @" WITH table_0 AS ( SELECT NULL AS a, @@ -4874,7 +4874,7 @@ fn test_from_text_05() { assert_snapshot!(compile(r#" std.from_text format:json '''{"columns": ["a", "b", "c"], "data": []}''' "#).unwrap(), - @r" + @" WITH table_0 AS ( SELECT NULL AS a, @@ -4898,7 +4898,7 @@ fn test_from_text_06() { assert_snapshot!(compile(r#" std.from_text '' "#).unwrap(), - @r" + @" WITH table_0 AS ( SELECT NULL @@ -4918,7 +4918,7 @@ fn test_from_text_07() { assert_snapshot!(compile(r#" std.from_text format:json '''{"columns": [], "data": [[], []]}''' "#).unwrap(), - @r" + @" WITH table_0 AS ( SELECT UNION @@ -4948,7 +4948,7 @@ fn test_header() { from a take 5 - "#).as_str()).unwrap(),@r" + "#).as_str()).unwrap(),@" SELECT * FROM @@ -4985,7 +4985,7 @@ fn test_header_target_error() { assert_snapshot!(compile(r#" prql dialect:foo.bar from a - "#).unwrap_err(),@r" + "#).unwrap_err(),@" Error: ╭─[ :1:1 ] │ @@ -5002,7 +5002,7 @@ fn shortest_prql_version() { let mut escape_version = insta::Settings::new(); escape_version.add_filter(r"'.*'", "[VERSION]"); escape_version.bind(|| { - assert_snapshot!(compile(r#"[{version = prql.version}]"#).unwrap(),@r" + assert_snapshot!(compile(r#"[{version = prql.version}]"#).unwrap(),@" WITH table_0 AS ( SELECT [VERSION] AS version @@ -5016,7 +5016,7 @@ fn shortest_prql_version() { assert_snapshot!(compile(r#" from x derive y = std.prql.version - "#).unwrap(),@r" + "#).unwrap(),@" SELECT *, [VERSION] AS y @@ -5038,7 +5038,7 @@ fn test_loop() { select n = n * 2 take 4 "#).unwrap(), - @r" + @" WITH RECURSIVE table_0 AS ( SELECT 1 AS n @@ -5082,7 +5082,7 @@ fn test_loop_2() { select manager.* ) "#).unwrap(), - @r" + @" WITH RECURSIVE table_0 AS ( SELECT * @@ -5124,7 +5124,7 @@ fn test_params() { } filter i.total > $3 "#).unwrap(), - @r" + @" SELECT id, total @@ -5150,7 +5150,7 @@ fn test_datetime() { assert_snapshot!( compile(query).unwrap(), - @r" + @" SELECT DATE '2022-12-31' AS date, TIME '08:30' AS time, @@ -5179,7 +5179,7 @@ fn test_datetime_sqlite() { timestamp3 = @2021-03-14T03:05+08:00, } "#).unwrap(), - @r" + @" SELECT DATE('2022-12-31') AS date, TIME('08:30') AS time, @@ -5200,7 +5200,7 @@ fn test_datetime_parsing() { from test_tables select {date = @2022-12-31, time = @08:30, timestamp = @2020-01-01T13:19:55-0800} "#).unwrap(), - @r" + @" SELECT DATE '2022-12-31' AS date, TIME '08:30' AS time, @@ -5216,7 +5216,7 @@ fn test_now() { assert_snapshot!(compile(r#" from test_tables filter test_time < date.now - "#).unwrap(), @r" + "#).unwrap(), @" SELECT * FROM @@ -5229,7 +5229,7 @@ fn test_now() { prql target:sql.mysql from test_tables filter test_time < date.now - "#).unwrap(), @r" + "#).unwrap(), @" SELECT * FROM @@ -5242,7 +5242,7 @@ fn test_now() { prql target:sql.bigquery from test_tables filter test_time < date.now - "#).unwrap(), @r" + "#).unwrap(), @" SELECT * FROM @@ -5255,7 +5255,7 @@ fn test_now() { prql target:sql.clickhouse from test_tables filter test_time < date.now - "#).unwrap(), @r" + "#).unwrap(), @" SELECT * FROM @@ -5270,7 +5270,7 @@ fn test_lower() { from test_tables derive {lower_name = (name | text.lower)} "#).unwrap(), - @r" + @" SELECT *, LOWER(name) AS lower_name @@ -5287,7 +5287,7 @@ fn test_upper() { derive {upper_name = text.upper name} select {upper_name} "#).unwrap(), - @r" + @" SELECT UPPER(name) AS upper_name FROM @@ -5301,7 +5301,7 @@ fn test_1535() { assert_snapshot!(compile(r#" from x.y.z "#).unwrap(), - @r" + @" SELECT * FROM @@ -5316,7 +5316,7 @@ fn test_read_parquet_duckdb() { std.read_parquet 'x.parquet' join (std.read_parquet "y.parquet") (==foo) "#).unwrap(), - @r" + @" WITH table_0 AS ( SELECT * @@ -5346,7 +5346,7 @@ fn test_read_parquet_with_named_args() { assert_snapshot!(compile_with_sql_dialect(r#" std.read_parquet 'data.parquet' union_by_name:true "#, sql::Dialect::DuckDb).unwrap(), - @r" + @" WITH table_0 AS ( SELECT * @@ -5369,7 +5369,7 @@ fn test_read_parquet_with_named_args() { assert_snapshot!(compile_with_sql_dialect(r#" std.read_parquet 'data.parquet' union_by_name:true binary_as_string:true "#, sql::Dialect::DuckDb).unwrap(), - @r" + @" WITH table_0 AS ( SELECT * @@ -5395,7 +5395,7 @@ fn test_read_json_duckdb() { assert_snapshot!(compile_with_sql_dialect(r#" from (read_json 'data.json') "#, sql::Dialect::DuckDb).unwrap(), - @r" + @" WITH table_0 AS ( SELECT * @@ -5415,7 +5415,7 @@ fn test_read_json_clickhouse() { assert_snapshot!(compile_with_sql_dialect(r#" from (read_json 'data.json') "#, sql::Dialect::ClickHouse).unwrap(), - @r" + @" WITH table_0 AS ( SELECT * @@ -5435,7 +5435,7 @@ fn test_read_json_generic() { assert_snapshot!(compile(r#" from (read_json 'data.json') "#).unwrap(), - @r" + @" WITH table_0 AS ( SELECT * @@ -5459,7 +5459,7 @@ fn test_excess_columns() { sort d select {title} "#).unwrap(), - @r" + @" WITH table_0 AS ( SELECT title, @@ -5499,7 +5499,7 @@ fn test_intervals() { from foo select dt = 1years + 1months + 1weeks + 1days + 1hours + 1minutes + 1seconds + 1milliseconds + 1microseconds "#).unwrap(), - @r" + @" SELECT INTERVAL 1 YEAR + INTERVAL 1 MONTH + INTERVAL 1 WEEK + INTERVAL 1 DAY + INTERVAL 1 HOUR + INTERVAL 1 MINUTE + INTERVAL 1 SECOND + INTERVAL 1 MILLISECOND + INTERVAL 1 MICROSECOND AS dt FROM @@ -5517,7 +5517,7 @@ fn test_into() { from table_a select {x, y} "#).unwrap(), - @r" + @" WITH table_a AS ( SELECT * @@ -5552,7 +5552,7 @@ fn test_array_01() { let main = (my_relation | filter b) "#).unwrap(), - @r" + @" WITH table_0 AS ( SELECT 3 AS a, @@ -5597,7 +5597,7 @@ fn test_array_02() { passing_as_arg = x [1,2,3], nested = ['a', ['b']] } - "###).unwrap(), @r" + "###).unwrap(), @" WITH table_0 AS ( SELECT NULL AS a @@ -5628,7 +5628,7 @@ fn test_array_03() { from employees select {e = this} select [e.first_name, e.last_name] - "###).unwrap(), @r" + "###).unwrap(), @" SELECT [first_name, last_name] FROM @@ -5644,7 +5644,7 @@ fn test_double_stars() { take 5 filter (tb2.c3 < 100) "#).unwrap(), - @r" + @" WITH table_0 AS ( SELECT tb1.*, @@ -5672,7 +5672,7 @@ fn test_double_stars() { take 5 filter (tb2.c3 < 100) "#).unwrap(), - @r" + @" WITH table_0 AS ( SELECT tb1.*, @@ -5705,7 +5705,7 @@ fn test_lineage() { """ derive a = a "#).unwrap(), - @r" + @" WITH table_0 AS ( SELECT ' 1' AS a @@ -5733,7 +5733,7 @@ fn test_lineage() { }""" derive a = a + 1 "#).unwrap(), - @r" + @" WITH table_0 AS ( SELECT 1 AS a @@ -5758,7 +5758,7 @@ fn test_type_as_column_name() { from foo f"#) - .unwrap(), @r" + .unwrap(), @" SELECT date FROM @@ -5816,7 +5816,7 @@ fn test_returning_constants_only() { select {d = 10} "###, ) - .unwrap(), @r" + .unwrap(), @" WITH table_0 AS ( SELECT 10 AS d, @@ -5842,7 +5842,7 @@ fn test_returning_constants_only() { select d = 10 "###, ) - .unwrap(), @r" + .unwrap(), @" WITH table_1 AS ( SELECT NULL @@ -5884,7 +5884,7 @@ fn test_conflicting_names_at_split() { } "#, ) - .unwrap(), @r" + .unwrap(), @" WITH table_0 AS ( SELECT wp.id, @@ -5941,7 +5941,7 @@ fn test_relation_var_name_clashes_01() { filter x > 0 "###, ) - .unwrap(), @r" + .unwrap(), @" WITH table_0 AS ( SELECT * @@ -5974,7 +5974,7 @@ fn test_relation_var_name_clashes_02() { join t (==x) "###, ) - .unwrap(), @r" + .unwrap(), @" SELECT t.*, table_0.* @@ -6038,7 +6038,7 @@ fn test_select_repeated_and_derived() { derive {cccc8 = 0,cccc9 = 0,cccc10 = 0} "###, ) - .unwrap(), @r###" + .unwrap(), @" WITH table_0 AS ( SELECT c2 AS _expr_0 @@ -6060,7 +6060,7 @@ fn test_select_repeated_and_derived() { 0 AS cccc10 FROM table_0 - "###); + "); } #[test] @@ -6072,7 +6072,7 @@ fn test_group_exclude() { group {a} (derive c = a + 1) "###, ) - .unwrap_err(), @r" + .unwrap_err(), @" Error: ╭─[ :4:27 ] │ @@ -6115,7 +6115,7 @@ fn test_table_declarations() { from my_schema.my_table | join another_table (==id) | take 10 "###, ) - .unwrap(), @r" + .unwrap(), @" SELECT my_table.id, my_table.a, @@ -6138,7 +6138,7 @@ fn test_param_declarations() { from x | filter b == a "###, ) - .unwrap(), @r" + .unwrap(), @" SELECT * FROM @@ -6155,7 +6155,7 @@ fn test_relation_aliasing() { from x | select {y = this} | select {y.hello} "###, ) - .unwrap(), @r" + .unwrap(), @" SELECT hello FROM @@ -6176,7 +6176,7 @@ fn test_import() { from x | select a "###, ) - .unwrap(), @r" + .unwrap(), @" SELECT 1 FROM @@ -6206,7 +6206,7 @@ derive { d = c } select !{ c } group { d } ( aggregate { b = sum b } ) -sort { d }"###).unwrap(), @r" +sort { d }"###).unwrap(), @" WITH table_1 AS ( SELECT b @@ -6241,7 +6241,7 @@ fn test_type_error_placement() { let foo = x -> (x | as integer) from t select (true && (foo y)) - "###).unwrap(), @r" + "###).unwrap(), @" SELECT true AND CAST(y AS integer) @@ -6266,7 +6266,7 @@ fn test_missing_columns_group_complex_compute() { select {this.`year_label`} "#, ) - .unwrap(), @r" + .unwrap(), @" SELECT DISTINCT ON ( EXTRACT( @@ -6310,7 +6310,7 @@ fn test_append_select_compute() { take 5 ) select { a = customer_id * 2, b = math.round 1 (invoice_id * total) } - "###).unwrap(), @r" + "###).unwrap(), @" WITH table_1 AS ( SELECT * @@ -6463,7 +6463,7 @@ fn test_append_with_cte() { from employees_wrap derive { source = "employees" } ) - "###).unwrap(), @r" + "###).unwrap(), @" WITH invoices_wrap AS ( SELECT invoice_id, @@ -6811,7 +6811,7 @@ fn test_redshift_uses_double_pipe_over_concat() { concatenated = f"{col_one} + {col_two}" } "###, sql::Dialect::Redshift - ).unwrap(), @r" + ).unwrap(), @" SELECT *, col_one || ' + ' || col_two AS concatenated @@ -6849,7 +6849,7 @@ fn test_redshift_text_contains_uses_double_pipe() { has_substring = (name | text.contains "pika") } "###, sql::Dialect::Redshift - ).unwrap(), @r" + ).unwrap(), @" SELECT name, name LIKE '%' || 'pika' || '%' AS has_substring @@ -6942,7 +6942,7 @@ fn test_group_with_only_sort() { group { a.department } ( sort a.salary ) - "###).unwrap(), @r" + "###).unwrap(), @" SELECT * FROM @@ -6959,7 +6959,7 @@ fn test_group_empty_preserves_sort() { sort a take 1 ) - "###).unwrap(), @r" + "###).unwrap(), @" SELECT * FROM @@ -7207,7 +7207,7 @@ fn test_partial_application_of_transform() { assert_snapshot!(compile(r#" let foo = a -> take a from invoices | foo 10 - "#).unwrap(), @r" + "#).unwrap(), @" SELECT * FROM @@ -7220,7 +7220,7 @@ fn test_partial_application_of_transform() { assert_snapshot!(compile(r#" let foo = a r -> take a r from invoices | foo 10 - "#).unwrap(), @r" + "#).unwrap(), @" SELECT * FROM From c971aff480c442fa9421d0eb463f22ac5c1ccfd4 Mon Sep 17 00:00:00 2001 From: Tobias Brandt Date: Fri, 27 Mar 2026 17:37:54 +0200 Subject: [PATCH 02/12] chore: update stale snapshot metadata and content Non-format changes from insta snapshot regeneration: - compile/fmt append_select_nulls.snap: expression field corrected to match current .prql file (stale '# duckdb:skip' / '# postgres:skip' comments were removed from the file in a prior commit but the snap metadata was never regenerated) - compileall snap files: leading blank lines added to snapshot content. Each blank line represents a dialect whose output matches the generic dialect (empty diff). The previous insta version trimmed these; the new version preserves them. The actual diff content is unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ...ntegration__queries__compile__append_select_nulls.snap | 2 +- .../integration__queries__compileall__aggregation.snap | 5 +++++ .../integration__queries__compileall__append_select.snap | 6 ++++++ ...ation__queries__compileall__append_select_compute.snap | 4 ++++ ...ies__compileall__append_select_multiple_with_null.snap | 6 ++++++ ...gration__queries__compileall__append_select_nulls.snap | 6 ++++++ ...ration__queries__compileall__append_select_simple.snap | 8 ++++++++ .../snapshots/integration__queries__compileall__cast.snap | 5 +++++ .../integration__queries__compileall__constants_only.snap | 6 ++++++ .../integration__queries__compileall__distinct_on.snap | 1 + .../integration__queries__compileall__group_all.snap | 4 ++++ .../integration__queries__compileall__group_sort.snap | 5 +++++ .../integration__queries__compileall__math_module.snap | 4 ++++ .../integration__queries__compileall__pipelines.snap | 1 + .../integration__queries__compileall__read_csv.snap | 1 + .../integration__queries__compileall__set_ops_remove.snap | 5 +++++ .../integration__queries__compileall__sort_2.snap | 1 + .../integration__queries__compileall__sort_3.snap | 1 + .../integration__queries__compileall__switch.snap | 4 ++++ .../snapshots/integration__queries__compileall__take.snap | 5 +++++ .../integration__queries__compileall__text_module.snap | 1 + .../integration__queries__fmt__append_select_nulls.snap | 2 +- 22 files changed, 81 insertions(+), 2 deletions(-) diff --git a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compile__append_select_nulls.snap b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compile__append_select_nulls.snap index 3f39d95669a5..74d5e0688cfe 100644 --- a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compile__append_select_nulls.snap +++ b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compile__append_select_nulls.snap @@ -1,6 +1,6 @@ --- source: prqlc/prqlc/tests/integration/queries.rs -expression: "# duckdb:skip\n# postgres:skip\n\nfrom invoices\nselect {an_id = invoice_id, name = null}\ntake 2\nappend (\n from employees\n select {an_id = null, name = first_name}\n take 2\n)\n" +expression: "from invoices\nselect {an_id = invoice_id, name = null}\ntake 2\nappend (\n from employees\n select {an_id = null, name = first_name}\n take 2\n)\n" input_file: prqlc/prqlc/tests/integration/queries/append_select_nulls.prql --- SELECT diff --git a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__aggregation.snap b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__aggregation.snap index f417f0349efa..0bd42ddf132b 100644 --- a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__aggregation.snap +++ b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__aggregation.snap @@ -3,6 +3,11 @@ source: prqlc/prqlc/tests/integration/queries.rs expression: "# mysql:skip\n# clickhouse:skip\n# glaredb:skip (the string_agg function is not supported)\nfrom tracks\nfilter genre_id == 100\nderive empty_name = name == ''\naggregate {sum track_id, concat_array name, all empty_name, any empty_name}\n" input_file: prqlc/prqlc/tests/integration/queries/aggregation.prql --- + + + + + --- generic +++ sqlite @@ -1,9 +1,9 @@ diff --git a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__append_select.snap b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__append_select.snap index 2128fd2a81a0..d75033b6ea90 100644 --- a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__append_select.snap +++ b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__append_select.snap @@ -3,6 +3,12 @@ source: prqlc/prqlc/tests/integration/queries.rs expression: "from invoices\nselect { customer_id, invoice_id, billing_country }\ntake 10..15\nappend (\n from invoices\n select { customer_id, invoice_id, billing_country }\n take 40..45\n)\nselect { billing_country, invoice_id }\n" input_file: prqlc/prqlc/tests/integration/queries/append_select.prql --- + + + + + + --- generic +++ postgres @@ -1,26 +1,19 @@ diff --git a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__append_select_compute.snap b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__append_select_compute.snap index 646883cc44ee..9dda659d54c8 100644 --- a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__append_select_compute.snap +++ b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__append_select_compute.snap @@ -3,6 +3,10 @@ source: prqlc/prqlc/tests/integration/queries.rs expression: "from invoices\nderive total = case [total < 10 => total * 2, true => total]\nselect { customer_id, invoice_id, total }\ntake 5\nappend (\n from invoice_items\n derive unit_price = case [unit_price < 1 => unit_price * 2, true => unit_price]\n select { invoice_line_id, invoice_id, unit_price }\n take 5\n)\nselect { a = customer_id * 2, b = math.round 1 (invoice_id * total) }\n" input_file: prqlc/prqlc/tests/integration/queries/append_select_compute.prql --- + + + + --- generic +++ glaredb @@ -29,13 +29,13 @@ diff --git a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__append_select_multiple_with_null.snap b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__append_select_multiple_with_null.snap index d8e8128e49c2..0ebabe369419 100644 --- a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__append_select_multiple_with_null.snap +++ b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__append_select_multiple_with_null.snap @@ -3,6 +3,12 @@ source: prqlc/prqlc/tests/integration/queries.rs expression: "from invoices\nselect { customer_id, invoice_id, billing_country }\ntake 5\nappend (\n from employees\n select { employee_id, employee_id, country }\n take 5\n)\nappend (\n from invoice_items\n select { invoice_line_id, invoice_id, null }\n take 5\n)\nselect { billing_country, invoice_id }\n" input_file: prqlc/prqlc/tests/integration/queries/append_select_multiple_with_null.prql --- + + + + + + --- generic +++ postgres @@ -1,40 +1,29 @@ diff --git a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__append_select_nulls.snap b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__append_select_nulls.snap index 52e6f5c5f59e..d4688b36398e 100644 --- a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__append_select_nulls.snap +++ b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__append_select_nulls.snap @@ -3,6 +3,12 @@ source: prqlc/prqlc/tests/integration/queries.rs expression: "from invoices\nselect {an_id = invoice_id, name = null}\ntake 2\nappend (\n from employees\n select {an_id = null, name = first_name}\n take 2\n)\n" input_file: prqlc/prqlc/tests/integration/queries/append_select_nulls.prql --- + + + + + + --- generic +++ postgres @@ -1,26 +1,19 @@ diff --git a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__append_select_simple.snap b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__append_select_simple.snap index 48d407c366d4..79395043c609 100644 --- a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__append_select_simple.snap +++ b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__append_select_simple.snap @@ -3,6 +3,14 @@ source: prqlc/prqlc/tests/integration/queries.rs expression: "from invoices\nselect { invoice_id, billing_country }\nappend (\n from invoices\n select { invoice_id = `invoice_id` + 100, billing_country }\n)\nfilter (billing_country | text.starts_with(\"I\"))\n" input_file: prqlc/prqlc/tests/integration/queries/append_select_simple.prql --- + + + + + + + + --- generic +++ sqlite @@ -11,11 +11,11 @@ diff --git a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__cast.snap b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__cast.snap index 902e7d317d93..419af3719f45 100644 --- a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__cast.snap +++ b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__cast.snap @@ -3,6 +3,11 @@ source: prqlc/prqlc/tests/integration/queries.rs expression: "# mssql:test\nfrom tracks\nsort {-bytes}\nselect {\n name,\n bin = ((album_id | as REAL) * 99)\n}\ntake 20\n" input_file: prqlc/prqlc/tests/integration/queries/cast.prql --- + + + + + --- generic +++ mssql @@ -1,19 +1,19 @@ diff --git a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__constants_only.snap b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__constants_only.snap index f1af63caa960..b8518c1381f3 100644 --- a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__constants_only.snap +++ b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__constants_only.snap @@ -3,6 +3,12 @@ source: prqlc/prqlc/tests/integration/queries.rs expression: "from genres\ntake 10\nfilter true\ntake 20\nfilter true\nselect d = 10\n" input_file: prqlc/prqlc/tests/integration/queries/constants_only.prql --- + + + + + + --- generic +++ postgres @@ -1,20 +1,18 @@ diff --git a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__distinct_on.snap b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__distinct_on.snap index e4821e3c2726..94098bc758f8 100644 --- a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__distinct_on.snap +++ b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__distinct_on.snap @@ -3,6 +3,7 @@ source: prqlc/prqlc/tests/integration/queries.rs expression: "# mssql:test\nfrom tracks\nselect {genre_id, media_type_id, album_id}\ngroup {genre_id, media_type_id} (sort {-album_id} | take 1)\nsort {-genre_id, media_type_id}\n" input_file: prqlc/prqlc/tests/integration/queries/distinct_on.prql --- + --- generic +++ clickhouse @@ -1,25 +1,21 @@ diff --git a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__group_all.snap b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__group_all.snap index c12a3166b1fb..6a21ebd488a7 100644 --- a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__group_all.snap +++ b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__group_all.snap @@ -3,6 +3,10 @@ source: prqlc/prqlc/tests/integration/queries.rs expression: "# mssql:test\nfrom a=albums\ntake 10\njoin tracks (==album_id)\ngroup {a.album_id, a.title} (aggregate price = (sum tracks.unit_price | math.round 2))\nsort album_id\n" input_file: prqlc/prqlc/tests/integration/queries/group_all.prql --- + + + + --- generic +++ glaredb @@ -3,19 +3,22 @@ diff --git a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__group_sort.snap b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__group_sort.snap index 3c16dd9ddd15..6a924732fbec 100644 --- a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__group_sort.snap +++ b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__group_sort.snap @@ -3,6 +3,11 @@ source: prqlc/prqlc/tests/integration/queries.rs expression: "# mssql:test\nfrom tracks\nderive d = album_id + 1\ngroup d (\n aggregate {\n n1 = (track_id | sum),\n }\n)\nsort d\ntake 10\nselect { d1 = d, n1 }\n" input_file: prqlc/prqlc/tests/integration/queries/group_sort.prql --- + + + + + --- generic +++ mssql @@ -8,21 +8,21 @@ diff --git a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__math_module.snap b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__math_module.snap index c6ecc427418c..db2c36f16ceb 100644 --- a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__math_module.snap +++ b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__math_module.snap @@ -3,6 +3,10 @@ source: prqlc/prqlc/tests/integration/queries.rs expression: "# mssql:test\n# sqlite:skip (see https://github.com/rusqlite/rusqlite/issues/1211)\nfrom invoices\ntake 5\nselect {\n total_original = (total | math.round 2),\n total_x = (math.pi - total | math.round 2 | math.abs),\n total_floor = (math.floor total),\n total_ceil = (math.ceil total),\n total_log10 = (math.log10 total | math.round 3),\n total_log2 = (math.log 2 total | math.round 3),\n total_sqrt = (math.sqrt total | math.round 3),\n total_ln = (math.ln total | math.exp | math.round 2),\n total_cos = (math.cos total | math.acos | math.round 2),\n total_sin = (math.sin total | math.asin | math.round 2),\n total_tan = (math.tan total | math.atan | math.round 2),\n total_deg = (total | math.degrees | math.radians | math.round 2),\n total_square = (total | math.pow 2 | math.round 2),\n total_square_op = ((total ** 2) | math.round 2),\n}\n" input_file: prqlc/prqlc/tests/integration/queries/math_module.prql --- + + + + --- generic +++ glaredb @@ -1,19 +1,19 @@ diff --git a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__pipelines.snap b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__pipelines.snap index 3858b4e2db31..3b063d5ace30 100644 --- a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__pipelines.snap +++ b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__pipelines.snap @@ -3,6 +3,7 @@ source: prqlc/prqlc/tests/integration/queries.rs expression: "# sqlite:skip (Only works on Sqlite implementations which have the extension\n# installed\n# https://stackoverflow.com/questions/24037982/how-to-use-regexp-in-sqlite)\n\nfrom tracks\n\nfilter (name ~= \"Love\")\nfilter ((milliseconds / 1000 / 60) | in 3..4)\nsort track_id\ntake 1..15\nselect {name, composer}\n" input_file: prqlc/prqlc/tests/integration/queries/pipelines.prql --- + --- generic +++ clickhouse @@ -1,20 +1,20 @@ diff --git a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__read_csv.snap b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__read_csv.snap index e577b889c860..9ab06c858c7f 100644 --- a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__read_csv.snap +++ b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__read_csv.snap @@ -3,6 +3,7 @@ source: prqlc/prqlc/tests/integration/queries.rs expression: "# sqlite:skip\n# postgres:skip\n# mysql:skip\nfrom (read_csv \"data_file_root/media_types.csv\")\nappend (read_json \"data_file_root/media_types.json\")\nsort media_type_id\n" input_file: prqlc/prqlc/tests/integration/queries/read_csv.prql --- + --- generic +++ clickhouse @@ -1,24 +1,24 @@ diff --git a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__set_ops_remove.snap b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__set_ops_remove.snap index 4e28163d8bc0..24fcad9b7e48 100644 --- a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__set_ops_remove.snap +++ b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__set_ops_remove.snap @@ -3,6 +3,11 @@ source: prqlc/prqlc/tests/integration/queries.rs expression: "# mssql:test\nlet distinct = rel -> (from t = _param.rel | group {t.*} (take 1))\n\nfrom_text format:json '{ \"columns\": [\"a\"], \"data\": [[1], [2], [2], [3]] }'\ndistinct\nremove (from_text format:json '{ \"columns\": [\"a\"], \"data\": [[1], [2]] }')\nsort a\n" input_file: prqlc/prqlc/tests/integration/queries/set_ops_remove.prql --- + + + + + --- generic +++ mssql @@ -21,21 +21,20 @@ diff --git a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__sort_2.snap b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__sort_2.snap index 844b2b3adce3..05354e41b36a 100644 --- a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__sort_2.snap +++ b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__sort_2.snap @@ -3,6 +3,7 @@ source: prqlc/prqlc/tests/integration/queries.rs expression: "from albums\nselect { AA=album_id, artist_id }\nsort AA\nfilter AA >= 25\njoin artists (==artist_id)\n" input_file: prqlc/prqlc/tests/integration/queries/sort_2.prql --- + --- generic +++ clickhouse @@ -1,25 +1,25 @@ diff --git a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__sort_3.snap b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__sort_3.snap index 6f00fa61ea58..04673cb3b4c0 100644 --- a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__sort_3.snap +++ b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__sort_3.snap @@ -3,6 +3,7 @@ source: prqlc/prqlc/tests/integration/queries.rs expression: "from [{track_id=0, album_id=1, genre_id=2}]\nselect { AA=track_id, album_id, genre_id }\nsort AA\njoin side:left [{album_id=1, album_title=\"Songs\"}] (==album_id)\nselect { AA, AT = album_title ?? \"unknown\", genre_id }\nfilter AA < 25\njoin side:left [{genre_id=1, genre_title=\"Rock\"}] (==genre_id)\nselect { AA, AT, GT = genre_title ?? \"unknown\" }\n" input_file: prqlc/prqlc/tests/integration/queries/sort_3.prql --- + --- generic +++ clickhouse @@ -1,52 +1,52 @@ diff --git a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__switch.snap b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__switch.snap index 1eb4672e1cbb..d039d59dd9c0 100644 --- a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__switch.snap +++ b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__switch.snap @@ -3,6 +3,10 @@ source: prqlc/prqlc/tests/integration/queries.rs expression: "# glaredb:skip (May be a bag of String type conversion for Postgres Client)\n# mssql:test\nfrom tracks\nsort milliseconds\nselect display = case [\n composer != null => composer,\n genre_id < 17 => 'no composer',\n true => f'unknown composer'\n]\ntake 10\n" input_file: prqlc/prqlc/tests/integration/queries/switch.prql --- + + + + --- generic +++ mssql @@ -2,20 +2,20 @@ diff --git a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__take.snap b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__take.snap index cd091395cf97..4fe197fe68dd 100644 --- a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__take.snap +++ b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__take.snap @@ -3,6 +3,11 @@ source: prqlc/prqlc/tests/integration/queries.rs expression: "# mssql:test\nfrom tracks\nsort {+track_id}\ntake 3..5\n" input_file: prqlc/prqlc/tests/integration/queries/take.prql --- + + + + + --- generic +++ mssql @@ -1,8 +1,8 @@ diff --git a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__text_module.snap b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__text_module.snap index 8b40f6929917..3144769538ba 100644 --- a/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__text_module.snap +++ b/prqlc/prqlc/tests/integration/snapshots/integration__queries__compileall__text_module.snap @@ -3,6 +3,7 @@ source: prqlc/prqlc/tests/integration/queries.rs expression: "# mssql:test\n# glaredb:skip — TODO: started raising an error on 2024-05-20; see `window.prql`\n# for more details\nfrom albums\nselect {\n title,\n title_and_spaces = f\" {title} \",\n low = (title | text.lower),\n up = (title | text.upper),\n ltrimmed = (title | text.ltrim),\n rtrimmed = (title | text.rtrim),\n trimmed = (title | text.trim),\n len = (title | text.length),\n subs = (title | text.extract 2 5),\n replace = (title | text.replace \"al\" \"PIKA\"),\n}\nsort {title}\nfilter (title | text.starts_with \"Black\") || (title | text.contains \"Sabbath\") || (title | text.ends_with \"os\")\n" input_file: prqlc/prqlc/tests/integration/queries/text_module.prql --- + --- generic +++ clickhouse @@ -2,33 +2,33 @@ diff --git a/prqlc/prqlc/tests/integration/snapshots/integration__queries__fmt__append_select_nulls.snap b/prqlc/prqlc/tests/integration/snapshots/integration__queries__fmt__append_select_nulls.snap index 93ba65005e2b..9509e33fd5e6 100644 --- a/prqlc/prqlc/tests/integration/snapshots/integration__queries__fmt__append_select_nulls.snap +++ b/prqlc/prqlc/tests/integration/snapshots/integration__queries__fmt__append_select_nulls.snap @@ -1,6 +1,6 @@ --- source: prqlc/prqlc/tests/integration/queries.rs -expression: "# duckdb:skip\n# postgres:skip\n\nfrom invoices\nselect {an_id = invoice_id, name = null}\ntake 2\nappend (\n from employees\n select {an_id = null, name = first_name}\n take 2\n)\n" +expression: "from invoices\nselect {an_id = invoice_id, name = null}\ntake 2\nappend (\n from employees\n select {an_id = null, name = first_name}\n take 2\n)\n" input_file: prqlc/prqlc/tests/integration/queries/append_select_nulls.prql --- from invoices From 0bad64c5b2682b5de945e392a456e71204bf60f2 Mon Sep 17 00:00:00 2001 From: Tobias Brandt Date: Fri, 27 Mar 2026 19:01:29 +0200 Subject: [PATCH 03/12] ci: add Docker Hub login to workflows pulling Docker images Authenticate with Docker Hub before pulling images to avoid rate limits. - test-rust.yaml: login before docker compose up (postgres, mysql, clickhouse) - build-devcontainer.yaml: login before image builds as defensive measure Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/build-devcontainer.yaml | 5 +++++ .github/workflows/test-rust.yaml | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/.github/workflows/build-devcontainer.yaml b/.github/workflows/build-devcontainer.yaml index 7d3ac6a71167..20246cb4b51f 100644 --- a/.github/workflows/build-devcontainer.yaml +++ b/.github/workflows/build-devcontainer.yaml @@ -32,6 +32,11 @@ jobs: timeout-minutes: 240 steps: - uses: actions/checkout@v5 + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} - uses: docker/login-action@v3 with: registry: ghcr.io diff --git a/.github/workflows/test-rust.yaml b/.github/workflows/test-rust.yaml index fdb195eef79f..89e994857399 100644 --- a/.github/workflows/test-rust.yaml +++ b/.github/workflows/test-rust.yaml @@ -35,6 +35,12 @@ jobs: uses: actions/checkout@v5 with: fetch-tags: true + - name: Log in to Docker Hub + if: ${{ contains(inputs.features, 'test-dbs-external') }} + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Run docker compose # This can go early because the DBs take a few seconds to start up. if: ${{ contains(inputs.features, 'test-dbs-external') }} From b19df4603c8a950bc1378e66fe1beb2d29ac3810 Mon Sep 17 00:00:00 2001 From: Tobias Brandt Date: Fri, 27 Mar 2026 19:19:59 +0200 Subject: [PATCH 04/12] ci: pass secrets to reusable workflows for Docker Hub login The Docker Hub login step in test-rust and build-devcontainer workflows needs access to DOCKERHUB_USERNAME and DOCKERHUB_TOKEN secrets. These are only available via 'secrets: inherit' on workflow_call invocations. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/release.yaml | 1 + .github/workflows/tests.yaml | 3 +++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index dcf94c54701a..1bcc55cee06c 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -412,5 +412,6 @@ jobs: push-devcontainer: if: github.event_name == 'release' uses: ./.github/workflows/build-devcontainer.yaml + secrets: inherit with: push: true diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 3f4fa7ab88e2..79fc3d88eb72 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -178,6 +178,7 @@ jobs: needs: rules if: needs.rules.outputs.rust == 'true' || needs.rules.outputs.main == 'true' uses: ./.github/workflows/test-rust.yaml + secrets: inherit strategy: matrix: include: @@ -326,6 +327,7 @@ jobs: # target: aarch64-unknown-linux-musl uses: ./.github/workflows/test-rust.yaml + secrets: inherit with: os: ${{ matrix.os }} target: ${{ matrix.target }} @@ -534,6 +536,7 @@ jobs: needs: rules if: needs.rules.outputs.devcontainer-build == 'true' uses: ./.github/workflows/build-devcontainer.yaml + secrets: inherit # One problem with this setup is that if another commit is merged to main, # this workflow will cancel existing jobs, and so this won't get pushed. We # have another workflow which runs on each release, so the image should get From 46e945a293a44cd47078bc754d7757c24a9f69e6 Mon Sep 17 00:00:00 2001 From: Tobias Brandt Date: Fri, 27 Mar 2026 12:21:30 +0200 Subject: [PATCH 05/12] feat(cli)!: Extract CLI binary into separate prqlc-cli crate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Split the prqlc crate into a pure library (prqlc) and a CLI binary (prqlc-cli). This fixes the structural MSRV problem where CLI-only dependencies (clap, clio, notify, etc.) forced prqlc's MSRV higher than needed, blocking Dependabot PRs. Key changes: - prqlc: Remove `cli` feature, add `display` feature gating ariadne+anstream as optional deps. Library is now pure with no binary target. - prqlc-cli: New binary crate with all CLI code moved here. Binary name remains `prqlc` via [[bin]] config. Depends on prqlc with `display` and `serde_yaml` features. - error_message.rs: Split composed() — location always computed via new offset_to_line_col() helper (no ariadne needed), display rendering behind #[cfg(feature = "display")]. - semantic/reporting.rs: Remove dead label_references()/Labeler code. - utils/mod.rs: Gate color functions behind `display` feature with no-op fallback. - CI: Update build-prqlc action to build prqlc-cli package, upgrade cargo-msrv, update release features. - Install via: cargo install prqlc-cli Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/actions/build-prqlc/action.yaml | 5 +- .github/workflows/release.yaml | 2 +- .github/workflows/tests.yaml | 5 +- CHANGELOG.md | 5 + Cargo.lock | 47 +++++-- Cargo.toml | 1 + README.md | 4 +- prqlc/Taskfile.yaml | 2 +- prqlc/prqlc-cli/Cargo.toml | 64 +++++++++ prqlc/prqlc-cli/build.rs | 11 ++ .../src/cli/docs_generator.rs | 0 .../{prqlc => prqlc-cli}/src/cli/highlight.rs | 0 prqlc/{prqlc => prqlc-cli}/src/cli/jinja.rs | 0 prqlc/{prqlc => prqlc-cli}/src/cli/lsp.rs | 0 prqlc/{prqlc => prqlc-cli}/src/cli/mod.rs | 7 +- .../prqlc__cli__test__shell_completion-2.snap | 0 .../prqlc__cli__test__shell_completion-3.snap | 0 .../prqlc__cli__test__shell_completion-4.snap | 0 .../prqlc__cli__test__shell_completion.snap | 0 prqlc/{prqlc => prqlc-cli}/src/cli/test.rs | 4 +- prqlc/{prqlc => prqlc-cli}/src/cli/watch.rs | 0 prqlc/{prqlc => prqlc-cli}/src/main.rs | 8 +- prqlc/prqlc/Cargo.toml | 48 +------ prqlc/prqlc/src/error_message.rs | 128 +++++++++++++++--- prqlc/prqlc/src/lib.rs | 18 ++- prqlc/prqlc/src/semantic/reporting.rs | 127 ----------------- prqlc/prqlc/src/utils/mod.rs | 24 +++- 27 files changed, 278 insertions(+), 232 deletions(-) create mode 100644 prqlc/prqlc-cli/Cargo.toml create mode 100644 prqlc/prqlc-cli/build.rs rename prqlc/{prqlc => prqlc-cli}/src/cli/docs_generator.rs (100%) rename prqlc/{prqlc => prqlc-cli}/src/cli/highlight.rs (100%) rename prqlc/{prqlc => prqlc-cli}/src/cli/jinja.rs (100%) rename prqlc/{prqlc => prqlc-cli}/src/cli/lsp.rs (100%) rename prqlc/{prqlc => prqlc-cli}/src/cli/mod.rs (99%) rename prqlc/{prqlc => prqlc-cli}/src/cli/snapshots/prqlc__cli__test__shell_completion-2.snap (100%) rename prqlc/{prqlc => prqlc-cli}/src/cli/snapshots/prqlc__cli__test__shell_completion-3.snap (100%) rename prqlc/{prqlc => prqlc-cli}/src/cli/snapshots/prqlc__cli__test__shell_completion-4.snap (100%) rename prqlc/{prqlc => prqlc-cli}/src/cli/snapshots/prqlc__cli__test__shell_completion.snap (100%) rename prqlc/{prqlc => prqlc-cli}/src/cli/test.rs (99%) rename prqlc/{prqlc => prqlc-cli}/src/cli/watch.rs (100%) rename prqlc/{prqlc => prqlc-cli}/src/main.rs (62%) diff --git a/.github/actions/build-prqlc/action.yaml b/.github/actions/build-prqlc/action.yaml index c5edacb9ff30..668f14b9feeb 100644 --- a/.github/actions/build-prqlc/action.yaml +++ b/.github/actions/build-prqlc/action.yaml @@ -76,9 +76,8 @@ runs: # even at the cost of slighly less efficiency.) args: --profile=${{ inputs.profile }} --locked --target=${{ inputs.target }} - --no-default-features --features=${{ inputs.features }} ${{ - contains(inputs.target, 'musl') && '--package=prqlc' || - '--all-targets' }} + --package=prqlc-cli --no-default-features --features=${{ + inputs.features }} - name: Create artifact for Linux and macOS shell: bash diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 1bcc55cee06c..9194a30b286f 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -68,7 +68,7 @@ jobs: with: target: ${{ matrix.target }} profile: release - features: cli + features: default - name: Upload release artifact if: github.event_name == 'release' uses: softprops/action-gh-release@v2 diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 79fc3d88eb72..c632522c5221 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -557,13 +557,10 @@ jobs: - uses: baptiste0928/cargo-install@v3 with: crate: cargo-msrv - # TODO: remove this version pinning - # The latest 0.16 supports workspace inheritance, so the check will fail - version: "0.15" # Note this currently uses a manually maintained key in # `prqlc/prqlc/Cargo.toml`, because of # https://github.com/foresterre/cargo-msrv/issues/590 - - name: Verify minimum rust version — prqlc + - name: Verify minimum rust version — prqlc library # Ideally we'd check all crates, ref https://github.com/foresterre/cargo-msrv/issues/295 working-directory: prqlc/prqlc run: cargo msrv verify diff --git a/CHANGELOG.md b/CHANGELOG.md index 41ee44c8e383..f8582e1cab3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,11 @@ **Internal changes**: +- Extract CLI binary into separate `prqlc-cli` crate. The `prqlc` crate is now a + pure library; the `cli` feature has been removed and replaced by a `display` + feature that gates ariadne/anstream. The CLI binary name remains `prqlc`. + Install with `cargo install prqlc-cli`. (@snth) + **New Contributors**: ## 0.13.11 — 2026-03-19 diff --git a/Cargo.lock b/Cargo.lock index 538037732452..ed65e73efcbe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2870,14 +2870,8 @@ name = "prqlc" version = "0.13.12" dependencies = [ "anstream 1.0.0", - "anyhow", "ariadne", "chrono", - "clap", - "clap_complete_command", - "clio", - "color-eyre", - "colorchoice-clap", "connector_arrow", "criterion", "csv", @@ -2887,14 +2881,9 @@ dependencies = [ "glob", "insta", "insta-cmd", - "is-terminal", "itertools 0.14.0", "log", - "lsp-server", - "lsp-types", - "minijinja", "mysql", - "notify", "postgres", "prqlc-parser", "regex", @@ -2917,7 +2906,6 @@ dependencies = [ "tokio", "tokio-util", "vergen-gitcl", - "walkdir", ] [[package]] @@ -2929,6 +2917,41 @@ dependencies = [ "serde_json", ] +[[package]] +name = "prqlc-cli" +version = "0.13.12" +dependencies = [ + "anstream 1.0.0", + "anyhow", + "ariadne", + "clap", + "clap_complete_command", + "clio", + "color-eyre", + "colorchoice-clap", + "glob", + "insta", + "insta-cmd", + "is-terminal", + "itertools 0.14.0", + "log", + "lsp-server", + "lsp-types", + "minijinja", + "notify", + "prqlc", + "regex", + "schemars", + "serde", + "serde_json", + "serde_yaml", + "similar", + "similar-asserts", + "tempfile", + "vergen-gitcl", + "walkdir", +] + [[package]] name = "prqlc-js" version = "0.13.12" diff --git a/Cargo.toml b/Cargo.toml index df400e7e251f..d2761a6dfa38 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "prqlc/bindings/js", "prqlc/bindings/prqlc-c", "prqlc/bindings/prqlc-python", + "prqlc/prqlc-cli", "prqlc/prqlc-macros", "prqlc/prqlc-parser", "prqlc/prqlc", diff --git a/README.md b/README.md index 15b1287138bc..e502df9c21d6 100644 --- a/README.md +++ b/README.md @@ -143,8 +143,8 @@ To stay in touch with PRQL: This repo is composed of: - **[prqlc](./prqlc/)** — the compiler, written in rust, whose main role is to - compile PRQL into SQL. Also contains the CLI and bindings from various - languages. + compile PRQL into SQL. Also contains the [CLI](./prqlc/prqlc-cli/) and + bindings from various languages. - **[web](./web/)** — our web content: the [Book][prql book], [Website][prql website], and [Playground][prql playground]. diff --git a/prqlc/Taskfile.yaml b/prqlc/Taskfile.yaml index 8a9b98e2b544..b316b7850059 100644 --- a/prqlc/Taskfile.yaml +++ b/prqlc/Taskfile.yaml @@ -15,7 +15,7 @@ includes: dir: ./bindings/prqlc-python vars: - packages_core: -p prqlc-parser -p prqlc + packages_core: -p prqlc-parser -p prqlc -p prqlc-cli packages_addon: -p prqlc-macros -p compile-files packages_bindings: -p prql -p prql-java -p prqlc-js -p prqlc-c -p prqlc-python diff --git a/prqlc/prqlc-cli/Cargo.toml b/prqlc/prqlc-cli/Cargo.toml new file mode 100644 index 000000000000..b9d7c0f548f3 --- /dev/null +++ b/prqlc/prqlc-cli/Cargo.toml @@ -0,0 +1,64 @@ +[package] +description = "CLI for the PRQL compiler — compiles PRQL to SQL." +name = "prqlc-cli" + +edition.workspace = true +license.workspace = true +repository.workspace = true +version.workspace = true + +# No rust-version constraint — CLI can track latest deps freely. + +build = "build.rs" + +[features] +default = ["lsp"] +lsp = ["lsp-server", "lsp-types"] + +[[bin]] +name = "prqlc" +path = "src/main.rs" + +[dependencies] +prqlc = { path = "../prqlc", features = ["display", "serde_yaml"] } + +anyhow = { version = "1.0.102", features = ["backtrace"] } +anstream = { version = "1.0.0", features = ["auto"] } +ariadne = "0.5.1" +clap = { version = "4.5.53", features = ["derive", "env", "wrap_help"] } +clap_complete_command = "0.5.1" +clio = { version = "0.3.3", features = ["clap-parse"] } +color-eyre = "0.6.5" +colorchoice-clap = "1.0.0" +is-terminal = "0.4.17" +itertools = { workspace = true } +log = { workspace = true } +minijinja = { version = "2.18.0", features = ["unstable_machinery"] } +notify = "7.0.0" +regex = "1.12.3" +schemars = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +serde_yaml = { workspace = true } +walkdir = "2.5.0" + +lsp-server = { version = "0.7.9", optional = true } +lsp-types = { version = "0.97.0", optional = true } + +[build-dependencies] +vergen-gitcl = { version = "1.0.0", features = ["build"] } + +[dev-dependencies] +glob = "0.3.3" +insta = { workspace = true } +insta-cmd = { workspace = true } +similar = { workspace = true } +similar-asserts = { workspace = true } +tempfile = "3.27.0" +walkdir = "2.5.0" + +[lints.rust] +unsafe_code = "forbid" + +[lints.clippy] +result_large_err = "allow" diff --git a/prqlc/prqlc-cli/build.rs b/prqlc/prqlc-cli/build.rs new file mode 100644 index 000000000000..10d868331a7d --- /dev/null +++ b/prqlc/prqlc-cli/build.rs @@ -0,0 +1,11 @@ +use std::error::Error; +// gix failing on https://github.com/rustyhorde/vergen/issues/359, and `git2` +// fails on `aarch64` so we're using `gitcl`. Switch to `gitx` when that bug is +// fixed. +use vergen_gitcl::{Emitter, GitclBuilder as GitBuilder}; + +pub fn main() -> Result<(), Box> { + let git = GitBuilder::default().describe(true, true, None).build()?; + Emitter::default().add_instructions(&git)?.emit()?; + Ok(()) +} diff --git a/prqlc/prqlc/src/cli/docs_generator.rs b/prqlc/prqlc-cli/src/cli/docs_generator.rs similarity index 100% rename from prqlc/prqlc/src/cli/docs_generator.rs rename to prqlc/prqlc-cli/src/cli/docs_generator.rs diff --git a/prqlc/prqlc/src/cli/highlight.rs b/prqlc/prqlc-cli/src/cli/highlight.rs similarity index 100% rename from prqlc/prqlc/src/cli/highlight.rs rename to prqlc/prqlc-cli/src/cli/highlight.rs diff --git a/prqlc/prqlc/src/cli/jinja.rs b/prqlc/prqlc-cli/src/cli/jinja.rs similarity index 100% rename from prqlc/prqlc/src/cli/jinja.rs rename to prqlc/prqlc-cli/src/cli/jinja.rs diff --git a/prqlc/prqlc/src/cli/lsp.rs b/prqlc/prqlc-cli/src/cli/lsp.rs similarity index 100% rename from prqlc/prqlc/src/cli/lsp.rs rename to prqlc/prqlc-cli/src/cli/lsp.rs diff --git a/prqlc/prqlc/src/cli/mod.rs b/prqlc/prqlc-cli/src/cli/mod.rs similarity index 99% rename from prqlc/prqlc/src/cli/mod.rs rename to prqlc/prqlc-cli/src/cli/mod.rs index 65aee158fc15..53655c912539 100644 --- a/prqlc/prqlc/src/cli/mod.rs +++ b/prqlc/prqlc-cli/src/cli/mod.rs @@ -1,5 +1,3 @@ -#![cfg(not(target_family = "wasm"))] - use std::collections::HashMap; use std::env; use std::fs::File; @@ -41,7 +39,7 @@ mod lsp; mod test; mod watch; -/// Entrypoint called by [`crate::main`] +/// CLI entrypoint pub fn main() -> color_eyre::eyre::Result<()> { let cli = Cli::parse(); @@ -84,6 +82,7 @@ pub fn main() -> color_eyre::eyre::Result<()> { } #[derive(Parser, Debug, Clone)] +#[command(name = "prqlc")] struct Cli { #[command(subcommand)] command: Option, @@ -100,7 +99,7 @@ pub fn compiler_version_str() -> &'static str { } #[derive(Subcommand, Debug, Clone)] -#[command(name = env!("CARGO_PKG_NAME"), about, version=compiler_version_str())] +#[command(name = "prqlc", about, version=compiler_version_str())] enum Command { /// Parse into PL AST Parse { diff --git a/prqlc/prqlc/src/cli/snapshots/prqlc__cli__test__shell_completion-2.snap b/prqlc/prqlc-cli/src/cli/snapshots/prqlc__cli__test__shell_completion-2.snap similarity index 100% rename from prqlc/prqlc/src/cli/snapshots/prqlc__cli__test__shell_completion-2.snap rename to prqlc/prqlc-cli/src/cli/snapshots/prqlc__cli__test__shell_completion-2.snap diff --git a/prqlc/prqlc/src/cli/snapshots/prqlc__cli__test__shell_completion-3.snap b/prqlc/prqlc-cli/src/cli/snapshots/prqlc__cli__test__shell_completion-3.snap similarity index 100% rename from prqlc/prqlc/src/cli/snapshots/prqlc__cli__test__shell_completion-3.snap rename to prqlc/prqlc-cli/src/cli/snapshots/prqlc__cli__test__shell_completion-3.snap diff --git a/prqlc/prqlc/src/cli/snapshots/prqlc__cli__test__shell_completion-4.snap b/prqlc/prqlc-cli/src/cli/snapshots/prqlc__cli__test__shell_completion-4.snap similarity index 100% rename from prqlc/prqlc/src/cli/snapshots/prqlc__cli__test__shell_completion-4.snap rename to prqlc/prqlc-cli/src/cli/snapshots/prqlc__cli__test__shell_completion-4.snap diff --git a/prqlc/prqlc/src/cli/snapshots/prqlc__cli__test__shell_completion.snap b/prqlc/prqlc-cli/src/cli/snapshots/prqlc__cli__test__shell_completion.snap similarity index 100% rename from prqlc/prqlc/src/cli/snapshots/prqlc__cli__test__shell_completion.snap rename to prqlc/prqlc-cli/src/cli/snapshots/prqlc__cli__test__shell_completion.snap diff --git a/prqlc/prqlc/src/cli/test.rs b/prqlc/prqlc-cli/src/cli/test.rs similarity index 99% rename from prqlc/prqlc/src/cli/test.rs rename to prqlc/prqlc-cli/src/cli/test.rs index d7bd71a656f7..ef1cc9243ea5 100644 --- a/prqlc/prqlc/src/cli/test.rs +++ b/prqlc/prqlc-cli/src/cli/test.rs @@ -579,7 +579,7 @@ fn project_path() -> PathBuf { // We canonicalize so that it doesn't matter where the cwd is. .canonicalize() .unwrap() - .join("tests/integration/project") + .join("../prqlc/tests/integration/project") } fn prqlc_command() -> Command { @@ -603,7 +603,7 @@ fn normalize_prqlc(cmd: &mut Command) -> &mut Command { #[test] fn compile_no_prql_files() { - assert_cmd_snapshot!(prqlc_command().args(["compile", "README.md"]), @r" + assert_cmd_snapshot!(prqlc_command().args(["compile", "Cargo.toml"]), @r" success: false exit_code: 1 ----- stdout ----- diff --git a/prqlc/prqlc/src/cli/watch.rs b/prqlc/prqlc-cli/src/cli/watch.rs similarity index 100% rename from prqlc/prqlc/src/cli/watch.rs rename to prqlc/prqlc-cli/src/cli/watch.rs diff --git a/prqlc/prqlc/src/main.rs b/prqlc/prqlc-cli/src/main.rs similarity index 62% rename from prqlc/prqlc/src/main.rs rename to prqlc/prqlc-cli/src/main.rs index a26c6a8a31f4..ac9c00ed805b 100644 --- a/prqlc/prqlc/src/main.rs +++ b/prqlc/prqlc-cli/src/main.rs @@ -1,7 +1,7 @@ -#[cfg(all(not(target_family = "wasm"), feature = "cli"))] +#[cfg(not(target_family = "wasm"))] mod cli; -#[cfg(all(not(target_family = "wasm"), feature = "cli"))] +#[cfg(not(target_family = "wasm"))] fn main() -> color_eyre::eyre::Result<()> { // Use a larger stack size (8 MiB) to avoid stack overflows on Windows, // where the default stack is only 1 MiB. @@ -16,7 +16,7 @@ fn main() -> color_eyre::eyre::Result<()> { Ok(()) } -#[cfg(any(target_family = "wasm", not(feature = "cli")))] +#[cfg(target_family = "wasm")] fn main() { - panic!("Crate is not built with the `cli` feature enabled, or was built for a wasm target."); + panic!("prqlc-cli cannot be built for wasm targets."); } diff --git a/prqlc/prqlc/Cargo.toml b/prqlc/prqlc/Cargo.toml index 7ada5748de08..62c078ccbad0 100644 --- a/prqlc/prqlc/Cargo.toml +++ b/prqlc/prqlc/Cargo.toml @@ -15,21 +15,8 @@ metadata.msrv = "1.75.0" build = "build.rs" [features] -cli = [ - "anyhow", - "clap_complete_command", - "clap", - "clio", - "color-eyre", - "colorchoice-clap", - "is-terminal", - "minijinja", - "notify", - "serde_yaml", - "walkdir", -] -default = ["cli"] -lsp = ["lsp-server", "lsp-types"] # Just a stub without any real functionality +display = ["dep:ariadne", "dep:anstream"] +default = [] serde_yaml = ["prqlc-parser/serde_yaml", "dep:serde_yaml"] test-dbs = [ "rusqlite", @@ -56,8 +43,8 @@ test-dbs-external = [ [dependencies] prqlc-parser = { path = "../prqlc-parser", version = "0.13.12" } -anstream = { version = "1.0.0", features = ["auto"] } -ariadne = "0.5.1" +anstream = { version = "1.0.0", features = ["auto"], optional = true } +ariadne = { version = "0.5.1", optional = true } chrono = "0.4.44" csv = "1.4.0" enum-as-inner = { workspace = true } @@ -78,36 +65,12 @@ sqlparser = { version = "0.60.0", features = [ ], default-features = false } strum = { workspace = true } strum_macros = { workspace = true } -lsp-server = { version = "0.7.9", optional = true } -lsp-types = { version = "0.97.0", optional = true } [build-dependencies] vergen-gitcl = { version = "1.0.0", features = ["build"] } [target.'cfg(not(target_family="wasm"))'.dependencies] -# unique dependencies from the CLI, marked as optional and included in the 'cli' -# feature -anyhow = { version = "1.0.102", features = ["backtrace"], optional = true } -clap = { version = "4.5.53", features = [ - "derive", - "env", - "wrap_help", -], optional = true } -clap_complete_command = { version = "0.5.1", optional = true } -clio = { version = "0.3.3", features = ['clap-parse'], optional = true } -color-eyre = { version = "0.6.5", optional = true } -colorchoice-clap = { version = "1.0.0", optional = true } -is-terminal = { version = "0.4.17", optional = true } -notify = { version = "7.0.0", optional = true } -walkdir = { version = "2.5.0", optional = true } - -# We use minijinja just for the Jinja lexer, which is not part of the -# public interface which is covered by semver guarantees. -minijinja = { version = "2.18.0", features = [ - "unstable_machinery", -], optional = true } - # For integration tests. These are gated by the `test-dbs` and `test-dbs-external` features, # rather than dev-dependencies, because dev-dependencies can't be optional. @@ -152,9 +115,6 @@ criterion = { version = "0.8.2", default-features = false } # https://bheisler.github.io/criterion.rs/book/faq.html#cargo-bench-gives-unrecognized-option-errors-for-valid-command-line-options [lib] bench = false -[[bin]] -bench = false -name = "prqlc" [[bench]] harness = false name = "bench" diff --git a/prqlc/prqlc/src/error_message.rs b/prqlc/prqlc/src/error_message.rs index 58105d4065be..40dd6c32e940 100644 --- a/prqlc/prqlc/src/error_message.rs +++ b/prqlc/prqlc/src/error_message.rs @@ -1,9 +1,14 @@ -use std::collections::HashMap; use std::error::Error as StdError; use std::fmt::{self, Debug, Display, Formatter}; + +#[cfg(feature = "display")] +use std::collections::HashMap; +#[cfg(feature = "display")] use std::ops::Range; +#[cfg(feature = "display")] use std::path::PathBuf; +#[cfg(feature = "display")] use ariadne::{Cache, Config, Label, Report, ReportKind, Source}; use serde::Serialize; @@ -135,8 +140,6 @@ impl ErrorMessages { /// Computes message location and builds the pretty display. pub fn composed(mut self, sources: &SourceTree) -> Self { - let mut cache = FileTreeCache::new(sources); - for e in &mut self.inner { let Some(span) = e.span else { continue; @@ -144,24 +147,64 @@ impl ErrorMessages { let Some(source_path) = sources.source_ids.get(&span.source_id) else { continue; }; - - let Ok(source) = cache.fetch(source_path) else { + let Some(source_str) = sources.sources.get(source_path) else { continue; }; - e.location = e.compose_location(source); + + e.location = compose_location(span, source_str); assert!( e.location.is_some(), "span {:?} is out of bounds of the source (len = {})", e.span, - source.len() + source_str.len() ); - e.display = e.compose_display(source_path.clone(), &mut cache); + + #[cfg(feature = "display")] + { + let mut cache = FileTreeCache::new(sources); + e.display = e.compose_display(source_path.clone(), &mut cache); + } } self } } +/// Convert a byte offset to (line, col), both 0-based. +/// Byte offsets that fall inside a multi-byte character are mapped to +/// that character's position. +fn offset_to_line_col(source: &str, offset: usize) -> Option<(usize, usize)> { + if offset > source.len() { + return None; + } + let mut line = 0; + let mut col = 0; + for (i, ch) in source.char_indices() { + if offset >= i && offset < i + ch.len_utf8() { + return Some((line, col)); + } + if ch == '\n' { + line += 1; + col = 0; + } else { + col += 1; + } + } + // offset == source.len() (one past the end) + if offset == source.len() { + return Some((line, col)); + } + None +} + +/// Compute source location from a span without ariadne. +fn compose_location(span: Span, source: &str) -> Option { + let start = offset_to_line_col(source, span.start)?; + let end = offset_to_line_col(source, span.end)?; + Some(SourceLocation { start, end }) +} + +#[cfg(feature = "display")] impl ErrorMessage { fn compose_display(&self, source_path: PathBuf, cache: &mut FileTreeCache) -> Option { // We always pass color to ariadne as true, and then (currently) strip later. @@ -196,23 +239,15 @@ impl ErrorMessage { .ok() .map(|x| crate::utils::maybe_strip_colors(x.as_str())) } - - fn compose_location(&self, source: &Source) -> Option { - let span = self.span?; - - let start = source.get_offset_line(span.start)?; - let end = source.get_offset_line(span.end)?; - Some(SourceLocation { - start: (start.1, start.2), - end: (end.1, end.2), - }) - } } +#[cfg(feature = "display")] struct FileTreeCache<'a> { file_tree: &'a SourceTree, cache: HashMap, } + +#[cfg(feature = "display")] impl<'a> FileTreeCache<'a> { fn new(file_tree: &'a SourceTree) -> Self { FileTreeCache { @@ -222,6 +257,7 @@ impl<'a> FileTreeCache<'a> { } } +#[cfg(feature = "display")] impl Cache for FileTreeCache<'_> { type Storage = String; fn fetch(&mut self, id: &PathBuf) -> Result<&Source, impl fmt::Debug> { @@ -240,3 +276,57 @@ impl Cache for FileTreeCache<'_> { id.as_os_str().to_str().map(str::to_string) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn offset_to_line_col_basic() { + let src = "hello\nworld"; + // 'h' at offset 0 + assert_eq!(offset_to_line_col(src, 0), Some((0, 0))); + // 'e' at offset 1 + assert_eq!(offset_to_line_col(src, 1), Some((0, 1))); + // '\n' at offset 5 + assert_eq!(offset_to_line_col(src, 5), Some((0, 5))); + // 'w' at offset 6 + assert_eq!(offset_to_line_col(src, 6), Some((1, 0))); + // 'd' at offset 10 + assert_eq!(offset_to_line_col(src, 10), Some((1, 4))); + // one past end + assert_eq!(offset_to_line_col(src, 11), Some((1, 5))); + // out of bounds + assert_eq!(offset_to_line_col(src, 12), None); + } + + #[test] + fn offset_to_line_col_empty() { + assert_eq!(offset_to_line_col("", 0), Some((0, 0))); + assert_eq!(offset_to_line_col("", 1), None); + } + + #[test] + fn offset_to_line_col_multibyte() { + let src = "á\nb"; // á is 2 bytes + // offset 0 = 'á' + assert_eq!(offset_to_line_col(src, 0), Some((0, 0))); + // offset 1 = mid-character (inside 'á'), maps to same char + assert_eq!(offset_to_line_col(src, 1), Some((0, 0))); + // offset 2 = '\n' (after the 2-byte char) + assert_eq!(offset_to_line_col(src, 2), Some((0, 1))); + // offset 3 = 'b' + assert_eq!(offset_to_line_col(src, 3), Some((1, 0))); + } + + #[test] + fn offset_to_line_col_curly_quote() { + // U+2019 RIGHT SINGLE QUOTATION MARK is 3 bytes in UTF-8 + let src = "S\u{2019}s"; + assert_eq!(offset_to_line_col(src, 0), Some((0, 0))); // 'S' + assert_eq!(offset_to_line_col(src, 1), Some((0, 1))); // start of ''' + assert_eq!(offset_to_line_col(src, 2), Some((0, 1))); // mid ''' + assert_eq!(offset_to_line_col(src, 3), Some((0, 1))); // mid ''' + assert_eq!(offset_to_line_col(src, 4), Some((0, 2))); // 's' + } +} diff --git a/prqlc/prqlc/src/lib.rs b/prqlc/prqlc/src/lib.rs index 910035810ee7..5560fb363436 100644 --- a/prqlc/prqlc/src/lib.rs +++ b/prqlc/prqlc/src/lib.rs @@ -70,7 +70,7 @@ //! - Compile, format & debug PRQL from command line. //! //! ```sh -//! $ cargo install --locked prqlc +//! $ cargo install --locked prqlc-cli //! $ prqlc compile query.prql //! ``` //! @@ -78,8 +78,10 @@ //! //! The following feature flags are available: //! -//! * `cli`: enables the `prqlc` CLI binary. This is enabled by default. When -//! consuming this crate from another rust library, it can be disabled. +//! * `display`: enables pretty-printed error messages with annotated source +//! code, using the `ariadne` crate. When disabled, `ErrorMessage.display` +//! is always `None` but `ErrorMessage.location` is still populated. +//! Library consumers who want pretty errors should enable this feature. //! * `test-dbs`: enables the `prqlc` in-process test databases as part of the //! crate's tests. This significantly increases compile times so is not //! enabled by default. @@ -103,7 +105,6 @@ use std::sync::OnceLock; use std::{collections::HashMap, path::PathBuf, str::FromStr}; -use anstream::adapter::strip_str; use semver::Version; use serde::{Deserialize, Serialize}; use strum::VariantNames; @@ -121,9 +122,9 @@ pub mod ir; pub mod parser; pub mod semantic; pub mod sql; -#[cfg(feature = "cli")] +#[cfg(feature = "display")] pub mod utils; -#[cfg(not(feature = "cli"))] +#[cfg(not(feature = "display"))] pub(crate) mod utils; pub type Result = core::result::Result; @@ -212,7 +213,9 @@ pub fn compile(prql: &str, options: &Options) -> Result { .inner .into_iter() .map(|e| ErrorMessage { - display: e.display.map(|s| strip_str(&s).to_string()), + display: e.display.map(|s| { + utils::maybe_strip_colors(&s) + }), ..e }) .collect(), @@ -547,6 +550,7 @@ mod tests { use crate::Target; pub fn compile(prql: &str) -> Result { + #[cfg(feature = "display")] anstream::ColorChoice::Never.write_global(); super::compile(prql, &super::Options::default().no_signature()) } diff --git a/prqlc/prqlc/src/semantic/reporting.rs b/prqlc/prqlc/src/semantic/reporting.rs index 34a2b7765d5b..dfe8eb3176b5 100644 --- a/prqlc/prqlc/src/semantic/reporting.rs +++ b/prqlc/prqlc/src/semantic/reporting.rs @@ -1,140 +1,13 @@ use std::collections::HashMap; -use std::ops::Range; -use ariadne::{Color, Label, Report, ReportBuilder, ReportKind, Source}; use schemars::JsonSchema; use serde::Serialize; -use crate::ir::decl::{DeclKind, Module, RootModule, TableDecl, TableExpr}; use crate::ir::pl; use crate::ir::pl::PlFold; use crate::pr; use crate::{Result, Span}; -pub fn label_references(root_mod: &RootModule, source_id: String, source: String) -> Vec { - let report_span = (source_id.clone(), 0..source.len()); - - let mut report = Report::build(ReportKind::Custom("Info", Color::Blue), report_span); - - let source = Source::from(source); - - // label all idents and function calls - let mut labeler = Labeler { - root_mod, - source: &source, - source_id: &source_id, - report: &mut report, - }; - labeler.label_module(&labeler.root_mod.module); - - let mut out = Vec::new(); - report - .finish() - .write((source_id, source), &mut out) - .unwrap(); - out -} - -/// Traverses AST and add labels for each of the idents and function calls -struct Labeler<'a> { - root_mod: &'a RootModule, - source: &'a Source, - source_id: &'a str, - report: &'a mut ReportBuilder<'static, (String, Range)>, -} - -impl Labeler<'_> { - fn label_module(&mut self, module: &Module) { - for (_, decl) in module.names.iter() { - if let DeclKind::TableDecl(TableDecl { - expr: TableExpr::RelationVar(expr), - .. - }) = &decl.kind - { - self.fold_expr(*expr.clone()).unwrap(); - } - } - } - - fn get_span_lines(&mut self, id: usize) -> Option { - let decl_span = self.root_mod.span_map.get(&id); - decl_span.map(|decl_span| { - let line_span = self.source.get_line_range(&Range::from(*decl_span)); - if line_span.len() <= 1 { - format!(" at line {}", line_span.start + 1) - } else { - format!(" at lines {}-{}", line_span.start + 1, line_span.end) - } - }) - } -} - -impl pl::PlFold for Labeler<'_> { - fn fold_expr(&mut self, node: pl::Expr) -> Result { - if let Some(ident) = node.kind.as_ident() { - if let Some(span) = node.span { - let decl = self.root_mod.module.get(ident); - - let ident = format!("[{ident}]"); - - let (decl, color) = if let Some(decl) = decl { - let color = match &decl.kind { - DeclKind::Expr(_) => Color::Blue, - DeclKind::Ty(_) => Color::Green, - DeclKind::Column { .. } => Color::Yellow, - DeclKind::InstanceOf(_, _) => Color::Yellow, - DeclKind::TableDecl { .. } => Color::Red, - DeclKind::Module(module) => { - self.label_module(module); - - Color::Cyan - } - DeclKind::LayeredModules(_) => Color::Cyan, - DeclKind::Infer(_) => Color::White, - DeclKind::QueryDef(_) => Color::White, - DeclKind::Import(_) => Color::White, - }; - - let location = decl - .declared_at - .and_then(|id| self.get_span_lines(id)) - .unwrap_or_default(); - - let decl = match &decl.kind { - DeclKind::TableDecl(TableDecl { ty, .. }) => { - format!( - "table {}", - ty.as_ref().and_then(|t| t.name.clone()).unwrap_or_default() - ) - } - _ => decl.to_string(), - }; - - (format!("{decl}{location}"), color) - } else if let Some(decl_id) = node.target_id { - let lines = self.get_span_lines(decl_id).unwrap_or_default(); - - (format!("variable{lines}"), Color::Yellow) - } else { - ("".to_string(), Color::White) - }; - - let label_span = (self.source_id.to_string(), span.start..span.end); - - self.report.add_label( - Label::new(label_span) - .with_message(format!("{ident} {decl}")) - .with_color(color), - ); - } - } - Ok(pl::Expr { - kind: self.fold_expr_kind(node.kind)?, - ..node - }) - } -} - /// Traverses AST and collects all node.frame pub fn collect_frames(expr: pl::Expr) -> FrameCollector { let mut collector = FrameCollector { diff --git a/prqlc/prqlc/src/utils/mod.rs b/prqlc/prqlc/src/utils/mod.rs index 83ff7b2e5139..431519048b3b 100644 --- a/prqlc/prqlc/src/utils/mod.rs +++ b/prqlc/prqlc/src/utils/mod.rs @@ -1,9 +1,8 @@ mod id_gen; mod toposort; -use std::{io::stderr, sync::OnceLock}; +use std::sync::OnceLock; -use anstream::adapter::strip_str; pub use id_gen::{IdGenerator, NameGenerator}; use itertools::Itertools; use regex::Regex; @@ -92,7 +91,9 @@ pub(crate) fn valid_ident() -> &'static Regex { }) } +#[cfg(feature = "display")] fn should_use_color() -> bool { + use std::io::stderr; match anstream::AutoStream::choice(&stderr()) { anstream::ColorChoice::Auto => true, anstream::ColorChoice::Always => true, @@ -104,7 +105,9 @@ fn should_use_color() -> bool { /// Strip colors, for external libraries which don't yet strip themselves, and /// for insta snapshot tests. This will respond to environment variables such as /// `CLI_COLOR`. +#[cfg(feature = "display")] pub fn maybe_strip_colors(s: &str) -> String { + use anstream::adapter::strip_str; if !should_use_color() { strip_str(s).to_string() } else { @@ -112,7 +115,24 @@ pub fn maybe_strip_colors(s: &str) -> String { } } +/// When the `display` feature is disabled, return the string unchanged. +#[cfg(not(feature = "display"))] +pub fn maybe_strip_colors(s: &str) -> String { + s.to_string() +} + #[test] fn test_write_ident_part() { assert!(!valid_ident().is_match("")); } + +#[cfg(all(test, feature = "display"))] +mod tests { + use super::*; + + #[test] + fn test_maybe_strip_colors_no_ansi() { + let plain = "hello world"; + assert_eq!(maybe_strip_colors(plain), "hello world"); + } +} From 36652b61639d11822e775935d03eacb52a9bc405 Mon Sep 17 00:00:00 2001 From: Tobias Brandt Date: Fri, 27 Mar 2026 14:19:40 +0200 Subject: [PATCH 06/12] fix(ci): split build-prqlc action and fix post-crate-split CI failures MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename build-prqlc action → build-prqlc-cli (it builds the CLI binary) - Remove test-dbs feature from CLI release matrix (belongs to prqlc library) - Add anyhow as dev-dependency of prqlc (used by integration test runner) - Make 'display' feature default for prqlc (error formatting is core) - Fix JS test: remove erroneous 'new' on get_targets() function call - Update all release.yaml job references from build-prqlc → build-prqlc-cli Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../{build-prqlc => build-prqlc-cli}/action.yaml | 9 +++++---- .github/workflows/release.yaml | 13 ++++++------- .github/workflows/tests.yaml | 4 ++-- Cargo.lock | 1 + prqlc/bindings/js/tests/test_all.mjs | 2 +- prqlc/prqlc/Cargo.toml | 3 ++- 6 files changed, 17 insertions(+), 15 deletions(-) rename .github/actions/{build-prqlc => build-prqlc-cli}/action.yaml (94%) diff --git a/.github/actions/build-prqlc/action.yaml b/.github/actions/build-prqlc-cli/action.yaml similarity index 94% rename from .github/actions/build-prqlc/action.yaml rename to .github/actions/build-prqlc-cli/action.yaml index 668f14b9feeb..abbf3575a792 100644 --- a/.github/actions/build-prqlc/action.yaml +++ b/.github/actions/build-prqlc-cli/action.yaml @@ -1,6 +1,6 @@ -name: build-prqlc +name: build-prqlc-cli description: > - Build prqlc + Build prqlc CLI binary Note that much of this is copy/pasted into build-prqlc-c, so changes here should generally be copied into that file. @@ -76,8 +76,9 @@ runs: # even at the cost of slighly less efficiency.) args: --profile=${{ inputs.profile }} --locked --target=${{ inputs.target }} - --package=prqlc-cli --no-default-features --features=${{ - inputs.features }} + --no-default-features --features=${{ inputs.features }} ${{ + contains(inputs.target, 'musl') && '--package=prqlc-cli' || + '--all-targets' }} - name: Create artifact for Linux and macOS shell: bash diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 9194a30b286f..e22e90dce3f4 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -40,7 +40,7 @@ jobs: } }) - build-prqlc: + build-prqlc-cli: runs-on: ${{ matrix.os }} strategy: fail-fast: false @@ -57,13 +57,12 @@ jobs: # Intel macOS build - os: macos-15-intel target: x86_64-apple-darwin - features: default,test-dbs permissions: contents: write steps: - name: 📂 Checkout code uses: actions/checkout@v5 - - uses: ./.github/actions/build-prqlc + - uses: ./.github/actions/build-prqlc-cli id: build-artifact with: target: ${{ matrix.target }} @@ -91,7 +90,7 @@ jobs: ./temp_path/prqlc --help build-prqlc-c: - # Mostly a copy/paste of `build-prqlc`. + # Mostly a copy/paste of `build-prqlc-cli`. runs-on: ${{ matrix.os }} strategy: fail-fast: false @@ -124,7 +123,7 @@ jobs: publish-winget: runs-on: ubuntu-24.04 - needs: build-prqlc + needs: build-prqlc-cli if: github.event_name == 'release' steps: - name: publish @@ -144,7 +143,7 @@ jobs: target: - x86_64-unknown-linux-musl - aarch64-unknown-linux-musl - needs: build-prqlc + needs: build-prqlc-cli permissions: contents: write steps: @@ -199,7 +198,7 @@ jobs: target: - x86_64-unknown-linux-musl #- aarch64-unknown-linux-musl # https://github.com/jiro4989/build-rpm-action/issues/6 - needs: build-prqlc + needs: build-prqlc-cli permissions: contents: write steps: diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index c632522c5221..5a3369167c42 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -664,7 +664,7 @@ jobs: "test-taskfile" ] - build-prqlc: + build-prqlc-cli: runs-on: ${{ matrix.os }} needs: rules if: needs.rules.outputs.rust == 'true' || needs.rules.outputs.main == 'true' @@ -691,7 +691,7 @@ jobs: steps: - name: 📂 Checkout code uses: actions/checkout@v5 - - uses: ./.github/actions/build-prqlc + - uses: ./.github/actions/build-prqlc-cli with: target: ${{ matrix.target }} profile: dev diff --git a/Cargo.lock b/Cargo.lock index ed65e73efcbe..e85032dbe599 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2870,6 +2870,7 @@ name = "prqlc" version = "0.13.12" dependencies = [ "anstream 1.0.0", + "anyhow", "ariadne", "chrono", "connector_arrow", diff --git a/prqlc/bindings/js/tests/test_all.mjs b/prqlc/bindings/js/tests/test_all.mjs index 669ccd29ba4f..28d908145786 100644 --- a/prqlc/bindings/js/tests/test_all.mjs +++ b/prqlc/bindings/js/tests/test_all.mjs @@ -101,7 +101,7 @@ describe("prqlc-js", () => { describe("get_targets", () => { it("return a list of targets", () => { - const targets = new prqlc.get_targets(); + const targets = prqlc.get_targets(); assert(targets.length > 0); assert(targets.includes("sql.sqlite")); }); diff --git a/prqlc/prqlc/Cargo.toml b/prqlc/prqlc/Cargo.toml index 62c078ccbad0..91b79104d9ce 100644 --- a/prqlc/prqlc/Cargo.toml +++ b/prqlc/prqlc/Cargo.toml @@ -16,7 +16,7 @@ build = "build.rs" [features] display = ["dep:ariadne", "dep:anstream"] -default = [] +default = ["display"] serde_yaml = ["prqlc-parser/serde_yaml", "dep:serde_yaml"] test-dbs = [ "rusqlite", @@ -97,6 +97,7 @@ tokio = { version = "1.50.0", optional = true, features = ["full"] } tokio-util = { version = "0.7.18", optional = true, features = ["compat"] } [dev-dependencies] +anyhow = { workspace = true } glob = { version = "0.3.3" } insta = { workspace = true } insta-cmd = { workspace = true } From 1882c00523f15da5794ca1f4bc02b09699f810b1 Mon Sep 17 00:00:00 2001 From: Tobias Brandt Date: Fri, 27 Mar 2026 15:17:11 +0200 Subject: [PATCH 07/12] =?UTF-8?q?fix(ci):=20Fix=20CI=20failures=20?= =?UTF-8?q?=E2=80=94=20formatting,=20binary=20lookup,=20unused=20deps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix rustfmt issues in error_message.rs and lib.rs - Replace get_cargo_bin("prqlc") with robust fallback that finds the binary relative to the test executable (fixes nextest/unit test compat) - Remove unused dev-deps: insta-cmd from prqlc, glob from prqlc-cli - Fix clippy unnecessary_map_or lint in prqlc-cli Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Cargo.lock | 2 -- prqlc/prqlc-cli/Cargo.toml | 1 - prqlc/prqlc-cli/src/cli/mod.rs | 2 +- prqlc/prqlc-cli/src/cli/test.rs | 15 +++++++++++++-- prqlc/prqlc/Cargo.toml | 1 - prqlc/prqlc/src/error_message.rs | 2 +- prqlc/prqlc/src/lib.rs | 4 +--- 7 files changed, 16 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e85032dbe599..ddaab5756cdc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2881,7 +2881,6 @@ dependencies = [ "futures", "glob", "insta", - "insta-cmd", "itertools 0.14.0", "log", "mysql", @@ -2930,7 +2929,6 @@ dependencies = [ "clio", "color-eyre", "colorchoice-clap", - "glob", "insta", "insta-cmd", "is-terminal", diff --git a/prqlc/prqlc-cli/Cargo.toml b/prqlc/prqlc-cli/Cargo.toml index b9d7c0f548f3..6b5986090b4b 100644 --- a/prqlc/prqlc-cli/Cargo.toml +++ b/prqlc/prqlc-cli/Cargo.toml @@ -49,7 +49,6 @@ lsp-types = { version = "0.97.0", optional = true } vergen-gitcl = { version = "1.0.0", features = ["build"] } [dev-dependencies] -glob = "0.3.3" insta = { workspace = true } insta-cmd = { workspace = true } similar = { workspace = true } diff --git a/prqlc/prqlc-cli/src/cli/mod.rs b/prqlc/prqlc-cli/src/cli/mod.rs index 53655c912539..eeed8cdad9b0 100644 --- a/prqlc/prqlc-cli/src/cli/mod.rs +++ b/prqlc/prqlc-cli/src/cli/mod.rs @@ -579,7 +579,7 @@ pub fn write_log(path: &std::path::Path) -> Result<()> { } fn drop_module_def(stmts: &mut Vec, name: &str) { - stmts.retain(|x| x.kind.as_module_def().map_or(true, |m| m.name != name)); + stmts.retain(|x| x.kind.as_module_def().is_none_or(|m| m.name != name)); } fn read_files(input: &mut clio::ClioPath) -> Result { diff --git a/prqlc/prqlc-cli/src/cli/test.rs b/prqlc/prqlc-cli/src/cli/test.rs index ef1cc9243ea5..8762d50d4257 100644 --- a/prqlc/prqlc-cli/src/cli/test.rs +++ b/prqlc/prqlc-cli/src/cli/test.rs @@ -6,7 +6,6 @@ use std::process::Command; use std::str::FromStr; use insta_cmd::assert_cmd_snapshot; -use insta_cmd::get_cargo_bin; use tempfile::TempDir; use walkdir::WalkDir; @@ -583,7 +582,19 @@ fn project_path() -> PathBuf { } fn prqlc_command() -> Command { - let mut cmd = Command::new(get_cargo_bin("prqlc")); + let bin = std::env::var_os("CARGO_BIN_EXE_prqlc") + .map(PathBuf::from) + .unwrap_or_else(|| { + // CARGO_BIN_EXE_* is only set for integration tests. For unit tests + // (including nextest), find the binary relative to the test executable. + let test_bin = std::env::current_exe().expect("cannot determine test binary path"); + let mut dir = test_bin.parent().unwrap().to_path_buf(); + if dir.ends_with("deps") { + dir.pop(); + } + dir.join("prqlc") + }); + let mut cmd = Command::new(bin); normalize_prqlc(&mut cmd); cmd } diff --git a/prqlc/prqlc/Cargo.toml b/prqlc/prqlc/Cargo.toml index 91b79104d9ce..79ef6ce35ecf 100644 --- a/prqlc/prqlc/Cargo.toml +++ b/prqlc/prqlc/Cargo.toml @@ -100,7 +100,6 @@ tokio-util = { version = "0.7.18", optional = true, features = ["compat"] } anyhow = { workspace = true } glob = { version = "0.3.3" } insta = { workspace = true } -insta-cmd = { workspace = true } rstest = "0.26.1" similar = { workspace = true } similar-asserts = { workspace = true } diff --git a/prqlc/prqlc/src/error_message.rs b/prqlc/prqlc/src/error_message.rs index 40dd6c32e940..4e6ac0f0871b 100644 --- a/prqlc/prqlc/src/error_message.rs +++ b/prqlc/prqlc/src/error_message.rs @@ -309,7 +309,7 @@ mod tests { #[test] fn offset_to_line_col_multibyte() { let src = "á\nb"; // á is 2 bytes - // offset 0 = 'á' + // offset 0 = 'á' assert_eq!(offset_to_line_col(src, 0), Some((0, 0))); // offset 1 = mid-character (inside 'á'), maps to same char assert_eq!(offset_to_line_col(src, 1), Some((0, 0))); diff --git a/prqlc/prqlc/src/lib.rs b/prqlc/prqlc/src/lib.rs index 5560fb363436..c360cb0a39b5 100644 --- a/prqlc/prqlc/src/lib.rs +++ b/prqlc/prqlc/src/lib.rs @@ -213,9 +213,7 @@ pub fn compile(prql: &str, options: &Options) -> Result { .inner .into_iter() .map(|e| ErrorMessage { - display: e.display.map(|s| { - utils::maybe_strip_colors(&s) - }), + display: e.display.map(|s| utils::maybe_strip_colors(&s)), ..e }) .collect(), From bb43283e18bda65a387b78f31dbb4ebb3ea38207 Mon Sep 17 00:00:00 2001 From: Tobias Brandt Date: Fri, 27 Mar 2026 15:46:56 +0200 Subject: [PATCH 08/12] fix(ci): Fix display feature gating, color stripping, and bench target MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add unconditional strip_colors() for DisplayOptions::Plain — restores the pre-PR behavior where compile() always stripped ANSI codes, fixing 36 integration test failures in test-deps-min-versions and book tests - Remove default-features = false from bindings and mdbook-prql — the old cli feature that motivated disabling defaults no longer exists; default features now just include display, which all consumers need - Add bench = false to prqlc-cli [[bin]] — prevents cargo bench from trying to pass criterion flags (--warm-up-time) to the CLI binary Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- prqlc/bindings/elixir/native/prql/Cargo.toml | 2 +- prqlc/bindings/java/Cargo.toml | 2 +- prqlc/bindings/js/Cargo.toml | 2 +- prqlc/bindings/prqlc-c/Cargo.toml | 2 +- prqlc/bindings/prqlc-python/Cargo.toml | 2 +- prqlc/prqlc-cli/Cargo.toml | 1 + prqlc/prqlc/src/lib.rs | 2 +- prqlc/prqlc/src/utils/mod.rs | 24 ++++++++++++++++---- web/book/Cargo.toml | 2 +- 9 files changed, 27 insertions(+), 12 deletions(-) diff --git a/prqlc/bindings/elixir/native/prql/Cargo.toml b/prqlc/bindings/elixir/native/prql/Cargo.toml index 590cb106e389..98384040f062 100644 --- a/prqlc/bindings/elixir/native/prql/Cargo.toml +++ b/prqlc/bindings/elixir/native/prql/Cargo.toml @@ -20,5 +20,5 @@ path = "src/lib.rs" test = false [target.'cfg(not(any(target_family="wasm")))'.dependencies] -prqlc = { path = "../../../../prqlc", default-features = false, version = "0.13.12" } +prqlc = { path = "../../../../prqlc", version = "0.13.12" } rustler = "0.37.0" diff --git a/prqlc/bindings/java/Cargo.toml b/prqlc/bindings/java/Cargo.toml index 61053f667acf..0659233685b0 100644 --- a/prqlc/bindings/java/Cargo.toml +++ b/prqlc/bindings/java/Cargo.toml @@ -17,7 +17,7 @@ test = false [dependencies] jni = "0.21.1" -prqlc = {path = "../../prqlc", default-features = false} +prqlc = {path = "../../prqlc"} [package.metadata.release] tag-name = "{{version}}" diff --git a/prqlc/bindings/js/Cargo.toml b/prqlc/bindings/js/Cargo.toml index 067bb9d48a47..63de1d1ec8ed 100644 --- a/prqlc/bindings/js/Cargo.toml +++ b/prqlc/bindings/js/Cargo.toml @@ -20,7 +20,7 @@ test = false default = ["console_error_panic_hook"] [target.'cfg(target_family="wasm")'.dependencies] -prqlc = {path = "../../prqlc", default-features = false} +prqlc = {path = "../../prqlc"} wasm-bindgen = "0.2.114" # The `console_error_panic_hook` crate provides better debugging of panics by diff --git a/prqlc/bindings/prqlc-c/Cargo.toml b/prqlc/bindings/prqlc-c/Cargo.toml index be4ba139a468..41d69a68ff1e 100644 --- a/prqlc/bindings/prqlc-c/Cargo.toml +++ b/prqlc/bindings/prqlc-c/Cargo.toml @@ -24,7 +24,7 @@ test = false [dependencies] libc = "0.2.183" -prqlc = {path = "../../prqlc", default-features = false} +prqlc = {path = "../../prqlc"} serde_json = {workspace = true} [package.metadata.release] diff --git a/prqlc/bindings/prqlc-python/Cargo.toml b/prqlc/bindings/prqlc-python/Cargo.toml index 5de5bed6e702..a3bde5af15ba 100644 --- a/prqlc/bindings/prqlc-python/Cargo.toml +++ b/prqlc/bindings/prqlc-python/Cargo.toml @@ -21,7 +21,7 @@ pyo3 = {workspace = true} [dependencies] # Renamed to avoid conflicts in lib.rs -prqlc_lib = {package = "prqlc", path = "../../prqlc", default-features = false} +prqlc_lib = {package = "prqlc", path = "../../prqlc"} [dev-dependencies] insta = {workspace = true} diff --git a/prqlc/prqlc-cli/Cargo.toml b/prqlc/prqlc-cli/Cargo.toml index 6b5986090b4b..c00ccdb52037 100644 --- a/prqlc/prqlc-cli/Cargo.toml +++ b/prqlc/prqlc-cli/Cargo.toml @@ -18,6 +18,7 @@ lsp = ["lsp-server", "lsp-types"] [[bin]] name = "prqlc" path = "src/main.rs" +bench = false [dependencies] prqlc = { path = "../prqlc", features = ["display", "serde_yaml"] } diff --git a/prqlc/prqlc/src/lib.rs b/prqlc/prqlc/src/lib.rs index c360cb0a39b5..0eff8bdbd7f3 100644 --- a/prqlc/prqlc/src/lib.rs +++ b/prqlc/prqlc/src/lib.rs @@ -213,7 +213,7 @@ pub fn compile(prql: &str, options: &Options) -> Result { .inner .into_iter() .map(|e| ErrorMessage { - display: e.display.map(|s| utils::maybe_strip_colors(&s)), + display: e.display.map(|s| utils::strip_colors(&s)), ..e }) .collect(), diff --git a/prqlc/prqlc/src/utils/mod.rs b/prqlc/prqlc/src/utils/mod.rs index 431519048b3b..1b7e08be3941 100644 --- a/prqlc/prqlc/src/utils/mod.rs +++ b/prqlc/prqlc/src/utils/mod.rs @@ -102,14 +102,28 @@ fn should_use_color() -> bool { } } -/// Strip colors, for external libraries which don't yet strip themselves, and -/// for insta snapshot tests. This will respond to environment variables such as -/// `CLI_COLOR`. +/// Unconditionally strip ANSI color codes from a string. +/// Used by the `Plain` display mode to guarantee clean output regardless of +/// environment. #[cfg(feature = "display")] -pub fn maybe_strip_colors(s: &str) -> String { +pub fn strip_colors(s: &str) -> String { use anstream::adapter::strip_str; + strip_str(s).to_string() +} + +/// When the `display` feature is disabled, no ANSI codes are present. +#[cfg(not(feature = "display"))] +pub fn strip_colors(s: &str) -> String { + s.to_string() +} + +/// Strip colors conditionally, based on whether the environment supports color. +/// Used by `compose_display` so that the `display` field respects terminal +/// settings and env vars like `NO_COLOR` / `CLICOLOR_FORCE`. +#[cfg(feature = "display")] +pub fn maybe_strip_colors(s: &str) -> String { if !should_use_color() { - strip_str(s).to_string() + strip_colors(s) } else { s.to_string() } diff --git a/web/book/Cargo.toml b/web/book/Cargo.toml index 02b06c5c4dc7..c1e32a7861d1 100644 --- a/web/book/Cargo.toml +++ b/web/book/Cargo.toml @@ -23,7 +23,7 @@ anyhow = { workspace = true } itertools = { workspace = true } mdbook-core = { version = "0.5.2", default-features = false } mdbook-preprocessor = { version = "0.5.2", default-features = false } -prqlc = { path = "../../prqlc/prqlc", default-features = false } +prqlc = { path = "../../prqlc/prqlc" } pulldown-cmark = { version = "0.13.0", default-features = false } pulldown-cmark-to-cmark = "22.0.0" serde_json = { workspace = true } From 1329f939ac6a6a56d25fd8a2204b49abcfa7170d Mon Sep 17 00:00:00 2001 From: Tobias Brandt Date: Fri, 27 Mar 2026 16:43:47 +0200 Subject: [PATCH 09/12] fix: resolve CI failures for dead code and binary discovery - Remove unreachable maybe_strip_colors #[cfg(not(feature = "display"))] variant that caused dead_code error on wasm32 builds - Add shared test_utils module with prqlc_command() that auto-builds the prqlc binary on demand using std::sync::Once, fixing test failures when cargo test doesn't build the binary (e.g. minimal-versions check) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- prqlc/prqlc-cli/src/cli/docs_generator.rs | 24 +-------- prqlc/prqlc-cli/src/cli/highlight.rs | 25 +-------- prqlc/prqlc-cli/src/cli/mod.rs | 2 + prqlc/prqlc-cli/src/cli/test.rs | 34 +----------- prqlc/prqlc-cli/src/cli/test_utils.rs | 63 +++++++++++++++++++++++ prqlc/prqlc/src/utils/mod.rs | 6 --- 6 files changed, 71 insertions(+), 83 deletions(-) create mode 100644 prqlc/prqlc-cli/src/cli/test_utils.rs diff --git a/prqlc/prqlc-cli/src/cli/docs_generator.rs b/prqlc/prqlc-cli/src/cli/docs_generator.rs index a30aae5cc97a..78a5f7f1e14a 100644 --- a/prqlc/prqlc-cli/src/cli/docs_generator.rs +++ b/prqlc/prqlc-cli/src/cli/docs_generator.rs @@ -310,10 +310,9 @@ Generated with [prqlc](https://prql-lang.org/) {}. #[cfg(test)] mod tests { - use std::process::Command; - use insta_cmd::assert_cmd_snapshot; - use insta_cmd::get_cargo_bin; + + use super::super::test_utils::prqlc_command; #[test] fn generate_html_docs() { @@ -508,23 +507,4 @@ mod tests { ----- stderr ----- "); } - - fn prqlc_command() -> Command { - let mut cmd = Command::new(get_cargo_bin("prqlc")); - normalize_prqlc(&mut cmd); - cmd - } - - fn normalize_prqlc(cmd: &mut Command) -> &mut Command { - cmd - // We set `CLICOLOR_FORCE` in CI to force color output, but we don't want `prqlc` to - // output color for our snapshot tests. And it seems to override the - // `--color=never` flag. - .env_remove("CLICOLOR_FORCE") - .env("NO_COLOR", "1") - .args(["--color=never"]) - // We don't want the tests to be affected by the user's `RUST_BACKTRACE` setting. - .env_remove("RUST_BACKTRACE") - .env_remove("RUST_LOG") - } } diff --git a/prqlc/prqlc-cli/src/cli/highlight.rs b/prqlc/prqlc-cli/src/cli/highlight.rs index 0ddad1dc8984..b6dcc22a284d 100644 --- a/prqlc/prqlc-cli/src/cli/highlight.rs +++ b/prqlc/prqlc-cli/src/cli/highlight.rs @@ -103,10 +103,9 @@ fn is_transform(ident: &str) -> bool { #[cfg(test)] mod tests { - use std::process::Command; - use insta_cmd::assert_cmd_snapshot; - use insta_cmd::get_cargo_bin; + + use super::super::test_utils::prqlc_command; #[test] fn highlight() { @@ -138,24 +137,4 @@ mod tests { ----- stderr ----- "#); } - - // TODO: import from existing location, need to adjust visibility - fn prqlc_command() -> Command { - let mut cmd = Command::new(get_cargo_bin("prqlc")); - normalize_prqlc(&mut cmd); - cmd - } - - fn normalize_prqlc(cmd: &mut Command) -> &mut Command { - cmd - // We set `CLICOLOR_FORCE` in CI to force color output, but we don't want `prqlc` to - // output color for our snapshot tests. And it seems to override the - // `--color=never` flag. - .env_remove("CLICOLOR_FORCE") - .env("NO_COLOR", "1") - .args(["--color=never"]) - // We don't want the tests to be affected by the user's `RUST_BACKTRACE` setting. - .env_remove("RUST_BACKTRACE") - .env_remove("RUST_LOG") - } } diff --git a/prqlc/prqlc-cli/src/cli/mod.rs b/prqlc/prqlc-cli/src/cli/mod.rs index eeed8cdad9b0..002e73fbd332 100644 --- a/prqlc/prqlc-cli/src/cli/mod.rs +++ b/prqlc/prqlc-cli/src/cli/mod.rs @@ -37,6 +37,8 @@ mod jinja; mod lsp; #[cfg(test)] mod test; +#[cfg(test)] +mod test_utils; mod watch; /// CLI entrypoint diff --git a/prqlc/prqlc-cli/src/cli/test.rs b/prqlc/prqlc-cli/src/cli/test.rs index 8762d50d4257..e4a5b3ed364f 100644 --- a/prqlc/prqlc-cli/src/cli/test.rs +++ b/prqlc/prqlc-cli/src/cli/test.rs @@ -2,13 +2,14 @@ use std::env::current_dir; use std::fs; use std::path::Path; use std::path::PathBuf; -use std::process::Command; use std::str::FromStr; use insta_cmd::assert_cmd_snapshot; use tempfile::TempDir; use walkdir::WalkDir; +use super::test_utils::prqlc_command; + #[cfg(not(windows))] // Windows has slightly different output (e.g. `prqlc.exe`), so we exclude. #[test] fn help() { @@ -581,37 +582,6 @@ fn project_path() -> PathBuf { .join("../prqlc/tests/integration/project") } -fn prqlc_command() -> Command { - let bin = std::env::var_os("CARGO_BIN_EXE_prqlc") - .map(PathBuf::from) - .unwrap_or_else(|| { - // CARGO_BIN_EXE_* is only set for integration tests. For unit tests - // (including nextest), find the binary relative to the test executable. - let test_bin = std::env::current_exe().expect("cannot determine test binary path"); - let mut dir = test_bin.parent().unwrap().to_path_buf(); - if dir.ends_with("deps") { - dir.pop(); - } - dir.join("prqlc") - }); - let mut cmd = Command::new(bin); - normalize_prqlc(&mut cmd); - cmd -} - -fn normalize_prqlc(cmd: &mut Command) -> &mut Command { - cmd - // We set `CLICOLOR_FORCE` in CI to force color output, but we don't want `prqlc` to - // output color for our snapshot tests. And it seems to override the - // `--color=never` flag. - .env_remove("CLICOLOR_FORCE") - .env("NO_COLOR", "1") - .args(["--color=never"]) - // We don't want the tests to be affected by the user's `RUST_BACKTRACE` setting. - .env_remove("RUST_BACKTRACE") - .env_remove("RUST_LOG") -} - #[test] fn compile_no_prql_files() { assert_cmd_snapshot!(prqlc_command().args(["compile", "Cargo.toml"]), @r" diff --git a/prqlc/prqlc-cli/src/cli/test_utils.rs b/prqlc/prqlc-cli/src/cli/test_utils.rs new file mode 100644 index 000000000000..43f61df8ed89 --- /dev/null +++ b/prqlc/prqlc-cli/src/cli/test_utils.rs @@ -0,0 +1,63 @@ +use std::path::PathBuf; +use std::process::Command; +use std::sync::Once; + +static BUILD_PRQLC: Once = Once::new(); + +/// Return a `Command` that runs the `prqlc` binary with color/backtrace +/// stripped so snapshot tests are deterministic. +/// +/// When `CARGO_BIN_EXE_prqlc` is set (integration tests), it uses that path. +/// Otherwise it locates the binary relative to the test executable and builds +/// it if necessary — `cargo test` for a bin-only crate does not produce the +/// non-test binary automatically. +pub fn prqlc_command() -> Command { + let bin = prqlc_bin_path(); + let mut cmd = Command::new(bin); + normalize_prqlc(&mut cmd); + cmd +} + +fn prqlc_bin_path() -> PathBuf { + if let Some(bin) = std::env::var_os("CARGO_BIN_EXE_prqlc") { + return PathBuf::from(bin); + } + + // Locate the target directory from the test binary path. + let test_bin = std::env::current_exe().expect("cannot determine test binary path"); + let mut dir = test_bin.parent().unwrap().to_path_buf(); + if dir.ends_with("deps") { + dir.pop(); + } + + let bin_name = if cfg!(windows) { "prqlc.exe" } else { "prqlc" }; + let bin_path = dir.join(bin_name); + + // `cargo test` for a [[bin]]-only crate builds the test harness (in deps/) + // but does NOT build the actual binary. Build it on demand if missing. + if !bin_path.exists() { + BUILD_PRQLC.call_once(|| { + let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_string()); + let status = Command::new(cargo) + .args(["build", "--bin", "prqlc"]) + .status() + .expect("failed to run `cargo build --bin prqlc`"); + assert!(status.success(), "failed to build prqlc binary"); + }); + } + + bin_path +} + +fn normalize_prqlc(cmd: &mut Command) -> &mut Command { + cmd + // We set `CLICOLOR_FORCE` in CI to force color output, but we don't want `prqlc` to + // output color for our snapshot tests. And it seems to override the + // `--color=never` flag. + .env_remove("CLICOLOR_FORCE") + .env("NO_COLOR", "1") + .args(["--color=never"]) + // We don't want the tests to be affected by the user's `RUST_BACKTRACE` setting. + .env_remove("RUST_BACKTRACE") + .env_remove("RUST_LOG") +} diff --git a/prqlc/prqlc/src/utils/mod.rs b/prqlc/prqlc/src/utils/mod.rs index 1b7e08be3941..dea8640356bb 100644 --- a/prqlc/prqlc/src/utils/mod.rs +++ b/prqlc/prqlc/src/utils/mod.rs @@ -129,12 +129,6 @@ pub fn maybe_strip_colors(s: &str) -> String { } } -/// When the `display` feature is disabled, return the string unchanged. -#[cfg(not(feature = "display"))] -pub fn maybe_strip_colors(s: &str) -> String { - s.to_string() -} - #[test] fn test_write_ident_part() { assert!(!valid_ident().is_match("")); From 0810cac0c3827a50c9b335d810ff2dcf8e7b2449 Mon Sep 17 00:00:00 2001 From: Tobias Brandt Date: Fri, 27 Mar 2026 17:46:24 +0200 Subject: [PATCH 10/12] fix(ci): pass --target and --target-dir in on-demand prqlc build When CI runs tests with --target=, cargo places artifacts in target//debug/ instead of target/debug/. The on-demand build in test_utils::prqlc_bin_path() was running `cargo build --bin prqlc` without --target, so the binary landed in the wrong directory and insta-cmd tests could not find it. Fix by: - Emitting PRQLC_BUILD_TARGET from build.rs (the TARGET env var is only available in build scripts) - Detecting the target triple from the test binary path and passing --target to the on-demand build when needed - Also passing --target-dir to handle custom target directories like those used by cargo-llvm-cov Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- prqlc/prqlc-cli/build.rs | 8 ++++++ prqlc/prqlc-cli/src/cli/test_utils.rs | 36 +++++++++++++++++++++++++-- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/prqlc/prqlc-cli/build.rs b/prqlc/prqlc-cli/build.rs index 10d868331a7d..c31480086a42 100644 --- a/prqlc/prqlc-cli/build.rs +++ b/prqlc/prqlc-cli/build.rs @@ -7,5 +7,13 @@ use vergen_gitcl::{Emitter, GitclBuilder as GitBuilder}; pub fn main() -> Result<(), Box> { let git = GitBuilder::default().describe(true, true, None).build()?; Emitter::default().add_instructions(&git)?.emit()?; + + // Expose the target triple to the main crate so test_utils can pass the + // correct --target flag when building the prqlc binary on demand. + println!( + "cargo:rustc-env=PRQLC_BUILD_TARGET={}", + std::env::var("TARGET").unwrap() + ); + Ok(()) } diff --git a/prqlc/prqlc-cli/src/cli/test_utils.rs b/prqlc/prqlc-cli/src/cli/test_utils.rs index 43f61df8ed89..c9f44b28b5bd 100644 --- a/prqlc/prqlc-cli/src/cli/test_utils.rs +++ b/prqlc/prqlc-cli/src/cli/test_utils.rs @@ -24,6 +24,8 @@ fn prqlc_bin_path() -> PathBuf { } // Locate the target directory from the test binary path. + // With --target: target//debug/deps/prqlc- + // Without: target/debug/deps/prqlc- let test_bin = std::env::current_exe().expect("cannot determine test binary path"); let mut dir = test_bin.parent().unwrap().to_path_buf(); if dir.ends_with("deps") { @@ -38,8 +40,38 @@ fn prqlc_bin_path() -> PathBuf { if !bin_path.exists() { BUILD_PRQLC.call_once(|| { let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_string()); - let status = Command::new(cargo) - .args(["build", "--bin", "prqlc"]) + let mut cmd = Command::new(cargo); + cmd.args(["build", "--bin", "prqlc"]); + + // When tests are compiled with an explicit --target flag, artifacts + // go into target//debug/ instead of target/debug/. Detect + // this and pass the same --target so the binary lands where we + // expect it. Also handle custom --target-dir (e.g. cargo-llvm-cov). + // + // Path layouts we handle: + // target/debug/ (default) + // target//debug/ (--target) + // /debug/ (--target-dir) + // //debug/ (--target + --target-dir) + let compile_target = env!("PRQLC_BUILD_TARGET"); + if let Some(parent) = dir.parent() { + let is_target_dir = + parent.file_name().and_then(|n| n.to_str()) == Some(compile_target); + + if is_target_dir { + cmd.args(["--target", compile_target]); + // The target-dir root is the grandparent. + if let Some(target_dir) = parent.parent() { + cmd.arg("--target-dir").arg(target_dir); + } + } else { + // No explicit --target, but may be a custom --target-dir + // (e.g. cargo-llvm-cov uses target/llvm-cov-target/). + cmd.arg("--target-dir").arg(parent); + } + } + + let status = cmd .status() .expect("failed to run `cargo build --bin prqlc`"); assert!(status.success(), "failed to build prqlc binary"); From 7cf16a33581d777b12e0fbd771b77b455d8f589f Mon Sep 17 00:00:00 2001 From: Tobias Brandt Date: Fri, 27 Mar 2026 17:58:10 +0200 Subject: [PATCH 11/12] docs: update installation instructions to reference prqlc-cli The CLI was extracted into the separate prqlc-cli crate. Update: - prqlc/prqlc/README.md: cargo install now references prqlc-cli, intro and CLI section clarify that the CLI is a separate crate - web/website/content/_index.md: crates.io link and cargo install command updated to prqlc-cli Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- prqlc/prqlc/README.md | 12 +++++++----- web/website/content/_index.md | 4 ++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/prqlc/prqlc/README.md b/prqlc/prqlc/README.md index 7ac0856553c4..0dc63c562f62 100644 --- a/prqlc/prqlc/README.md +++ b/prqlc/prqlc/README.md @@ -1,15 +1,17 @@ # PRQL compiler `prqlc` is the reference implementation of a compiler from PRQL to SQL, written -in Rust. It also serves as the CLI. +in Rust. The CLI is provided by the separate +[`prqlc-cli`](https://crates.io/crates/prqlc-cli) crate (binary name remains +`prqlc`). For more on PRQL, check out the [PRQL website](https://prql-lang.org) or the [PRQL repo](https://github.com/PRQL/prql). ## CLI -`prqlc` serves as a CLI for the PRQL compiler. It is a single, dependency-free -binary that compiles PRQL into SQL. +The CLI is provided by the [`prqlc-cli`](https://crates.io/crates/prqlc-cli) +crate. It is a single, dependency-free binary that compiles PRQL into SQL. ## Usage @@ -97,12 +99,12 @@ Precompiled binaries are available for Linux, macOS, and Windows on the ```sh # From crates.io -cargo install prqlc +cargo install prqlc-cli ``` ```sh # From a local PRQL repository -cargo install --path prqlc/prqlc +cargo install --path prqlc/prqlc-cli ``` ### Shell completions diff --git a/web/website/content/_index.md b/web/website/content/_index.md index 4af0e31dd520..2444db563c95 100644 --- a/web/website/content/_index.md +++ b/web/website/content/_index.md @@ -153,12 +153,12 @@ tools_section: `pip install pyprql` - - link: https://crates.io/crates/prqlc + - link: https://crates.io/crates/prqlc-cli label: "prqlc" text: | A CLI for PRQL compiler, written in Rust. - `cargo install prqlc` + `cargo install prqlc-cli` `brew install prqlc` From 979653b9b358e15976a2a6da83eb9869650c32e1 Mon Sep 17 00:00:00 2001 From: Tobias Brandt Date: Fri, 27 Mar 2026 20:38:01 +0200 Subject: [PATCH 12/12] fix(ci): resolve codecov/patch coverage failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove dead code in offset_to_line_col() — the trailing None was unreachable since the guard at the top ensures offset <= source.len() and the loop handles all positions < source.len() - Add test_utils.rs and build.rs to codecov ignore — test_utils.rs is #[cfg(test)] infrastructure with branches that depend on how cargo was invoked (impossible to fully cover in a single CI run), and build.rs runs at compile time so is never instrumented by cargo-llvm-cov Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/.codecov.yaml | 2 ++ prqlc/prqlc/src/error_message.rs | 9 ++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/.codecov.yaml b/.github/.codecov.yaml index 456a22e89fb6..b5d981df05fb 100644 --- a/.github/.codecov.yaml +++ b/.github/.codecov.yaml @@ -2,6 +2,8 @@ comment: false ignore: - "**/tests/**" + - "**/test_utils.rs" + - "**/build.rs" coverage: status: diff --git a/prqlc/prqlc/src/error_message.rs b/prqlc/prqlc/src/error_message.rs index 4e6ac0f0871b..c3dcecc1ef88 100644 --- a/prqlc/prqlc/src/error_message.rs +++ b/prqlc/prqlc/src/error_message.rs @@ -190,11 +190,10 @@ fn offset_to_line_col(source: &str, offset: usize) -> Option<(usize, usize)> { col += 1; } } - // offset == source.len() (one past the end) - if offset == source.len() { - return Some((line, col)); - } - None + // The guard above ensures offset <= source.len() and the loop handles + // all positions < source.len(), so only offset == source.len() remains. + debug_assert_eq!(offset, source.len()); + Some((line, col)) } /// Compute source location from a span without ariadne.