Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions prqlc/bindings/prqlc-python/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,15 +232,15 @@ mod test {
fn debug_prql_lineage() {
assert_snapshot!(
debug::prql_lineage(r#"from a | select { beta, gamma }"#).unwrap(),
@r#"{"frames":[["1:9-31",{"columns":[{"Single":{"name":["a","beta"],"target_id":120,"target_name":null}},{"Single":{"name":["a","gamma"],"target_id":121,"target_name":null}}],"inputs":[{"id":118,"name":"a","table":["default_db","a"]}]}]],"nodes":[{"id":118,"kind":"Ident","span":"1:0-6","ident":{"Ident":["default_db","a"]},"parent":123},{"id":120,"kind":"Ident","span":"1:18-22","ident":{"Ident":["this","a","beta"]},"targets":[118],"parent":122},{"id":121,"kind":"Ident","span":"1:24-29","ident":{"Ident":["this","a","gamma"]},"targets":[118],"parent":122},{"id":122,"kind":"Tuple","span":"1:16-31","children":[120,121],"parent":123},{"id":123,"kind":"TransformCall: Select","span":"1:9-31","children":[118,122]}],"ast":{"name":"Project","stmts":[{"VarDef":{"kind":"Main","name":"main","value":{"Pipeline":{"exprs":[{"FuncCall":{"name":{"Ident":["from"],"span":"1:0-4"},"args":[{"Ident":["a"],"span":"1:5-6"}]},"span":"1:0-6"},{"FuncCall":{"name":{"Ident":["select"],"span":"1:9-15"},"args":[{"Tuple":[{"Ident":["beta"],"span":"1:18-22"},{"Ident":["gamma"],"span":"1:24-29"}],"span":"1:16-31"}]},"span":"1:9-31"}]},"span":"1:0-31"}},"span":"1:0-31"}]}}"#
@r#"{"frames":[["1:9-31",{"columns":[{"Single":{"name":["a","beta"],"target_id":121,"target_name":null}},{"Single":{"name":["a","gamma"],"target_id":122,"target_name":null}}],"inputs":[{"id":119,"name":"a","table":["default_db","a"]}]}]],"nodes":[{"id":119,"kind":"Ident","span":"1:0-6","ident":{"Ident":["default_db","a"]},"parent":124},{"id":121,"kind":"Ident","span":"1:18-22","ident":{"Ident":["this","a","beta"]},"targets":[119],"parent":123},{"id":122,"kind":"Ident","span":"1:24-29","ident":{"Ident":["this","a","gamma"]},"targets":[119],"parent":123},{"id":123,"kind":"Tuple","span":"1:16-31","children":[121,122],"parent":124},{"id":124,"kind":"TransformCall: Select","span":"1:9-31","children":[119,123]}],"ast":{"name":"Project","stmts":[{"VarDef":{"kind":"Main","name":"main","value":{"Pipeline":{"exprs":[{"FuncCall":{"name":{"Ident":["from"],"span":"1:0-4"},"args":[{"Ident":["a"],"span":"1:5-6"}]},"span":"1:0-6"},{"FuncCall":{"name":{"Ident":["select"],"span":"1:9-15"},"args":[{"Tuple":[{"Ident":["beta"],"span":"1:18-22"},{"Ident":["gamma"],"span":"1:24-29"}],"span":"1:16-31"}]},"span":"1:9-31"}]},"span":"1:0-31"}},"span":"1:0-31"}]}}"#
);
}

#[test]
fn debug_pl_to_lineage() {
assert_snapshot!(
prql_to_pl(r#"from a | select { beta, gamma }"#).and_then(|x| debug::pl_to_lineage(&x)).unwrap(),
@r#"{"frames":[["1:9-31",{"columns":[{"Single":{"name":["a","beta"],"target_id":120,"target_name":null}},{"Single":{"name":["a","gamma"],"target_id":121,"target_name":null}}],"inputs":[{"id":118,"name":"a","table":["default_db","a"]}]}]],"nodes":[{"id":118,"kind":"Ident","span":"1:0-6","ident":{"Ident":["default_db","a"]},"parent":123},{"id":120,"kind":"Ident","span":"1:18-22","ident":{"Ident":["this","a","beta"]},"targets":[118],"parent":122},{"id":121,"kind":"Ident","span":"1:24-29","ident":{"Ident":["this","a","gamma"]},"targets":[118],"parent":122},{"id":122,"kind":"Tuple","span":"1:16-31","children":[120,121],"parent":123},{"id":123,"kind":"TransformCall: Select","span":"1:9-31","children":[118,122]}],"ast":{"name":"Project","stmts":[{"VarDef":{"kind":"Main","name":"main","value":{"Pipeline":{"exprs":[{"FuncCall":{"name":{"Ident":["from"],"span":"1:0-4"},"args":[{"Ident":["a"],"span":"1:5-6"}]},"span":"1:0-6"},{"FuncCall":{"name":{"Ident":["select"],"span":"1:9-15"},"args":[{"Tuple":[{"Ident":["beta"],"span":"1:18-22"},{"Ident":["gamma"],"span":"1:24-29"}],"span":"1:16-31"}]},"span":"1:9-31"}]},"span":"1:0-31"}},"span":"1:0-31"}]}}"#
@r#"{"frames":[["1:9-31",{"columns":[{"Single":{"name":["a","beta"],"target_id":121,"target_name":null}},{"Single":{"name":["a","gamma"],"target_id":122,"target_name":null}}],"inputs":[{"id":119,"name":"a","table":["default_db","a"]}]}]],"nodes":[{"id":119,"kind":"Ident","span":"1:0-6","ident":{"Ident":["default_db","a"]},"parent":124},{"id":121,"kind":"Ident","span":"1:18-22","ident":{"Ident":["this","a","beta"]},"targets":[119],"parent":123},{"id":122,"kind":"Ident","span":"1:24-29","ident":{"Ident":["this","a","gamma"]},"targets":[119],"parent":123},{"id":123,"kind":"Tuple","span":"1:16-31","children":[121,122],"parent":124},{"id":124,"kind":"TransformCall: Select","span":"1:9-31","children":[119,123]}],"ast":{"name":"Project","stmts":[{"VarDef":{"kind":"Main","name":"main","value":{"Pipeline":{"exprs":[{"FuncCall":{"name":{"Ident":["from"],"span":"1:0-4"},"args":[{"Ident":["a"],"span":"1:5-6"}]},"span":"1:0-6"},{"FuncCall":{"name":{"Ident":["select"],"span":"1:9-15"},"args":[{"Tuple":[{"Ident":["beta"],"span":"1:18-22"},{"Ident":["gamma"],"span":"1:24-29"}],"span":"1:16-31"}]},"span":"1:9-31"}]},"span":"1:0-31"}},"span":"1:0-31"}]}}"#
);
}
}
34 changes: 17 additions & 17 deletions prqlc/prqlc/src/cli/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -434,61 +434,61 @@ fn debug() {
name:
- tracks
- artist
target_id: 120
target_id: 121
target_name: null
- !Single
name:
- tracks
- album
target_id: 121
target_id: 122
target_name: null
inputs:
- id: 118
- id: 119
name: tracks
table:
- default_db
- tracks
nodes:
- id: 118
- id: 119
kind: Ident
span: 1:0-11
ident: !Ident
- default_db
- tracks
parent: 123
- id: 120
parent: 124
- id: 121
kind: Ident
span: 1:22-28
ident: !Ident
- this
- tracks
- artist
targets:
- 118
parent: 122
- id: 121
- 119
parent: 123
- id: 122
kind: Ident
span: 1:30-35
ident: !Ident
- this
- tracks
- album
targets:
- 118
parent: 122
- id: 122
- 119
parent: 123
- id: 123
kind: Tuple
span: 1:21-36
children:
- 120
- 121
parent: 123
- id: 123
- 122
parent: 124
- id: 124
kind: 'TransformCall: Select'
span: 1:14-36
children:
- 118
- 122
- 119
- 123
ast:
name: Project
stmts:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,21 @@ columns:
name:
- employees
- name
target_id: 134
target_id: 135
target_name: ~
- Single:
name:
- employees
- salary
target_id: 135
target_id: 136
target_name: ~
inputs:
- id: 132
- id: 133
name: employees
table:
- default_db
- employees
- id: 121
- id: 122
name: managers
table:
- default_db
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
---
source: prqlc/prqlc/src/semantic/resolver/mod.rs
assertion_line: 190
expression: "resolve_lineage(r#\"\n from table_1\n join customers (==customer_no)\n \"#).unwrap()"
---
columns:
- All:
input_id: 119
input_id: 120
except: []
- All:
input_id: 116
input_id: 117
except: []
inputs:
- id: 119
- id: 120
name: table_1
table:
- default_db
- table_1
- id: 116
- id: 117
name: customers
table:
- default_db
Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,33 @@
---
source: prqlc/prqlc/src/semantic/resolver/mod.rs
assertion_line: 198
expression: "resolve_lineage(r#\"\n from e = employees\n join salaries (==emp_no)\n group {e.emp_no, e.gender} (\n aggregate {\n emp_salary = average salaries.salary\n }\n )\n \"#).unwrap()"
---
columns:
- Single:
name:
- e
- emp_no
target_id: 129
target_id: 130
target_name: ~
- Single:
name:
- e
- gender
target_id: 130
target_id: 131
target_name: ~
- Single:
name:
- emp_salary
target_id: 148
target_id: 149
target_name: ~
inputs:
- id: 122
- id: 123
name: e
table:
- default_db
- employees
- id: 119
- id: 120
name: salaries
table:
- default_db
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,26 @@ columns:
name:
- orders
- customer_no
target_id: 123
target_id: 124
target_name: ~
- Single:
name:
- orders
- gross
target_id: 124
target_id: 125
target_name: ~
- Single:
name:
- orders
- tax
target_id: 125
target_id: 126
target_name: ~
- Single:
name: ~
target_id: 126
target_id: 127
target_name: ~
inputs:
- id: 121
- id: 122
name: orders
table:
- default_db
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ TransformCall:
lineage:
columns:
- All:
input_id: 118
input_id: 119
except: []
inputs:
- id: 118
- id: 119
name: c_invoice
table:
- default_db
Expand Down Expand Up @@ -94,14 +94,14 @@ lineage:
name:
- c_invoice
- issued_at
target_id: 120
target_id: 121
target_name: ~
- Single:
name: ~
target_id: 136
target_id: 137
target_name: ~
inputs:
- id: 118
- id: 119
name: c_invoice
table:
- default_db
Expand Down
1 change: 1 addition & 0 deletions prqlc/prqlc/src/semantic/std.prql
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ module date {
let now = -> <timestamp> internal std.date.now
let to_text = format<text> column -> <text> internal std.date.to_text
let diff = `noresolve.unit` start end -> <int> internal std.date.diff
let trunc = unit<text> column -> internal std.date.trunc
}

## File-reading functions, primarily for DuckDB
Expand Down
87 changes: 87 additions & 0 deletions prqlc/prqlc/src/sql/gen_expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ pub(super) fn translate_expr(expr: rq::Expr, ctx: &mut Context) -> Result<ExprOr
"std.date.to_text" => {
return Ok(process_date_to_text(&expr, name, args, ctx)?.into())
}
"std.date.trunc" => return Ok(process_date_trunc(&expr, name, args, ctx)?.into()),
_ => match try_into_between(expr.clone(), ctx)? {
Some(between_expr) => return Ok(between_expr.into()),
None => {
Expand Down Expand Up @@ -208,6 +209,92 @@ fn process_array_in(
}
}

/// Translates PRQL date truncation to dialect-specific SQL.
/// BigQuery uses unquoted uppercase date_part after the column: DATE_TRUNC(col, DAY)
/// MSSQL uses unquoted lowercase datepart before the column: DATETRUNC(day, col)
/// All other dialects use: DATE_TRUNC('unit', col)
fn process_date_trunc(
expr: &rq::Expr,
op_name: &str,
args: &[rq::Expr],
ctx: &mut Context,
) -> Result<sql_ast::Expr> {
if let [unit_expr @ rq::Expr {
kind: rq::ExprKind::Literal(Literal::String(unit)),
..
}, col_expr] = args
{
use crate::sql::dialect::{BigQueryDialect, MsSqlDialect};

if ctx.dialect.is::<BigQueryDialect>() {
// BigQuery: DATE_TRUNC(col, DAY) — unit is an unquoted uppercase keyword
let col = translate_expr(col_expr.clone(), ctx)?.into_ast();
let unit_upper = unit.to_uppercase();
return Ok(sql_ast::Expr::Function(Function {
name: ObjectName(vec![sqlparser::ast::ObjectNamePart::Identifier(
sql_ast::Ident::new("DATE_TRUNC"),
)]),
args: sql_ast::FunctionArguments::List(FunctionArgumentList {
args: vec![
FunctionArg::Unnamed(FunctionArgExpr::Expr(col)),
FunctionArg::Unnamed(FunctionArgExpr::Expr(sql_ast::Expr::Identifier(
sql_ast::Ident::new(unit_upper),
))),
],
clauses: vec![],
duplicate_treatment: None,
}),
over: None,
filter: None,
null_treatment: None,
within_group: vec![],
parameters: sql_ast::FunctionArguments::None,
uses_odbc_syntax: false,
}));
} else if ctx.dialect.is::<MsSqlDialect>() {
// MSSQL: DATETRUNC(day, col) — unit is an unquoted lowercase keyword (SQL Server 2022+)
let col = translate_expr(col_expr.clone(), ctx)?.into_ast();
let unit_lower = unit.to_lowercase();
return Ok(sql_ast::Expr::Function(Function {
name: ObjectName(vec![sqlparser::ast::ObjectNamePart::Identifier(
sql_ast::Ident::new("DATETRUNC"),
)]),
args: sql_ast::FunctionArguments::List(FunctionArgumentList {
args: vec![
FunctionArg::Unnamed(FunctionArgExpr::Expr(sql_ast::Expr::Identifier(
sql_ast::Ident::new(unit_lower),
))),
FunctionArg::Unnamed(FunctionArgExpr::Expr(col)),
],
clauses: vec![],
duplicate_treatment: None,
}),
over: None,
filter: None,
null_treatment: None,
within_group: vec![],
parameters: sql_ast::FunctionArguments::None,
uses_odbc_syntax: false,
}));
}

// All other dialects: DATE_TRUNC('unit', col) via the s-string in std.sql.prql
let expr = rq::Expr {
kind: rq::ExprKind::Operator {
name: op_name.to_string(),
args: vec![unit_expr.clone(), col_expr.clone()],
},
..expr.clone()
};
Ok(super::operators::translate_operator_expr(expr, ctx)?.into_ast())
} else {
Err(
Error::new_simple("`std.date.trunc` only supports a string literal as unit")
.with_span(expr.span),
)
}
}

/// Translates PRQL date format (based on `chrono` crate) to dialect specific date format
/// For now only date format as string literal is supported
fn process_date_to_text(
Expand Down
13 changes: 12 additions & 1 deletion prqlc/prqlc/src/sql/std.sql.prql
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,18 @@ module date {
# Snowflake: https://docs.snowflake.com/en/sql-reference/functions/datediff
# Redshift: https://docs.aws.amazon.com/redshift/latest/dg/r_DATEDIFF_function.html
let diff = unit start end -> s"DATEDIFF({unit:0}, {start:0}, {end:0})"

# CURRENT_TIMESTAMP is ANSI SQL standard; dialects may override.
let now = -> s"CURRENT_TIMESTAMP"
# Clickhouse: https://clickhouse.com/docs/en/sql-reference/functions/date-time-functions#date_trunc
# DuckDB: https://duckdb.org/docs/sql/functions/timestamp#date_truncdate_part-timestamp
# MariaDB: https://mariadb.com/kb/en/date_format/
# MySQL: https://dev.mysql.com/doc/refman/8.4/en/date-and-time-functions.html#function_date-trunc
# Postgres: https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-TRUNC
# SQLite: https://www.sqlite.org/lang_datefunc.html
# MSSQL: https://learn.microsoft.com/en-us/sql/t-sql/functions/datetrunc-transact-sql
# BigQuery: https://cloud.google.com/bigquery/docs/reference/standard-sql/date_functions#date_trunc
# Snowflake: https://docs.snowflake.com/en/sql-reference/functions/date_trunc
let trunc = unit column -> s"DATE_TRUNC({unit:0}, {column:0})"
}

# Source-reading functions, primarily for DuckDB
Expand Down Expand Up @@ -323,6 +332,8 @@ module mysql {
let diff = unit start end -> s"TIMESTAMPDIFF({unit:0}, {start:0}, {end:0})"
# MySQL uses NOW() function
let now = -> s"NOW()"
# https://dev.mysql.com/doc/refman/8.4/en/date-and-time-functions.html#function_date-trunc
let trunc = unit column -> s"DATE_TRUNC({unit:0}, {column:0})"
}

# 'c' for case-sensitive
Expand Down
Loading
Loading