Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add main mathematical functions #3909

Merged
merged 17 commits into from
Dec 10, 2023
Merged
Show file tree
Hide file tree
Changes from 16 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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
**Features**:

- The `std.in` function now supports a list of values (@PrettyWood, #3883)
- Most standard mathematical functions are now supported: `abs`, `floor`,
`ceil`, `pi`, `exp`, `ln`, `log10`, `log`, `sqrt`, `degrees`, `radians`,
`cos`, `acos`, `sin`, `asin`, `tan`, `atan` and `pow` (@PrettyWood, #3909)

**Fixes**:

Expand Down
1 change: 1 addition & 0 deletions prqlc/prql-compiler/src/codegen/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ fn binding_strength(expr: &ExprKind) -> u8 {
ExprKind::Range(_) => 19,

ExprKind::Binary(BinaryExpr { op, .. }) => match op {
BinOp::Pow => 19,
BinOp::Mul | BinOp::DivInt | BinOp::DivFloat | BinOp::Mod => 18,
BinOp::Add | BinOp::Sub => 17,
BinOp::Eq
Expand Down
1 change: 1 addition & 0 deletions prqlc/prql-compiler/src/semantic/ast_expand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ fn expand_binary(BinaryExpr { op, left, right }: BinaryExpr) -> Result<pl::ExprK
BinOp::DivInt => ["std", "div_i"],
BinOp::DivFloat => ["std", "div_f"],
BinOp::Mod => ["std", "mod"],
BinOp::Pow => ["std", "pow"],
BinOp::Add => ["std", "add"],
BinOp::Sub => ["std", "sub"],
BinOp::Eq => ["std", "eq"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@ expression: "resolve_lineage(r#\"\n from table_1\n join cu
---
columns:
- All:
input_id: 86
input_id: 104
except: []
- All:
input_id: 83
input_id: 101
except: []
inputs:
- id: 86
- id: 104
name: table_1
table:
- default_db
- table_1
- id: 83
- id: 101
name: customers
table:
- default_db
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,26 @@ columns:
name:
- e
- emp_no
target_id: 95
target_id: 113
target_name: ~
- Single:
name:
- e
- gender
target_id: 96
target_id: 114
target_name: ~
- Single:
name:
- emp_salary
target_id: 114
target_id: 132
target_name: ~
inputs:
- id: 89
- id: 107
name: e
table:
- default_db
- employees
- id: 86
- id: 104
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: 89
target_id: 107
target_name: ~
- Single:
name:
- orders
- gross
target_id: 90
target_id: 108
target_name: ~
- Single:
name:
- orders
- tax
target_id: 91
target_id: 109
target_name: ~
- Single:
name: ~
target_id: 92
target_id: 110
target_name: ~
inputs:
- id: 88
- id: 106
name: orders
table:
- default_db
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ TransformCall:
lineage:
columns:
- All:
input_id: 85
input_id: 103
except: []
inputs:
- id: 85
- id: 103
name: c_invoice
table:
- default_db
Expand Down Expand Up @@ -304,14 +304,14 @@ lineage:
name:
- c_invoice
- issued_at
target_id: 86
target_id: 104
target_name: ~
- Single:
name: ~
target_id: 102
target_id: 120
target_name: ~
inputs:
- id: 85
- id: 103
name: c_invoice
table:
- default_db
Expand Down
20 changes: 20 additions & 0 deletions prqlc/prql-compiler/src/semantic/std.prql
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,26 @@ let rank = column <array> -> internal std.rank
let rank_dense = column <array> -> internal std.rank_dense
let row_number = column <array> -> internal row_number

# Mathematical functions
let abs = column -> <int || float> internal std.abs
let floor = column -> <int> internal std.floor
let ceil = column -> <int> internal std.ceil
let pi = -> <float> internal std.pi
let exp = column -> <int || float> internal std.exp
let ln = column -> <int || float> internal std.ln
let log10 = column -> <int || float> internal std.log10
let log = base<int || float> column -> <int || float> internal std.log
let sqrt = column -> <int || float> internal std.sqrt
let degrees = column -> <int || float> internal std.degrees
let radians = column -> <int || float> internal std.radians
let cos = column -> <int || float> internal std.cos
let acos = column -> <int || float> internal std.acos
let sin = column -> <int || float> internal std.sin
let asin = column -> <int || float> internal std.asin
let tan = column -> <int || float> internal std.tan
let atan = column -> <int || float> internal std.atan
let pow = column exponent<int || float> -> <int || float> internal std.pow

## Misc functions
let round = n_digits column -> <scalar> internal std.round
let as = `noresolve.type` column -> <scalar> internal std.as
Expand Down
39 changes: 39 additions & 0 deletions prqlc/prql-compiler/src/sql/std.sql.prql
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,34 @@ let rank_dense = -> s"DENSE_RANK()"

let row_number = -> s"ROW_NUMBER()"

# Mathematical functions
# Clickhouse: https://clickhouse.com/docs/en/sql-reference/functions
# DuckDB: https://duckdb.org/docs/test/functions/math.html
# MySQL: https://dev.mysql.com/doc/refman/8.0/en/mathematical-functions.html#function_truncate
# Postgres: https://www.postgresql.org/docs/7.4/functions-math.html
# SQLite: https://www.sqlite.org/lang_mathfunc.html
# MSSQL: https://learn.microsoft.com/en-us/sql/t-sql/functions/mathematical-functions-transact-sql
# BigQuery: https://cloud.google.com/bigquery/docs/reference/standard-sql/mathematical_functions
# Snowflake: https://docs.snowflake.com/en/sql-reference/functions-math.html
let abs = column -> s"ABS({column:0})"
let floor = column -> s"FLOOR({column:0})"
let ceil = column -> s"CEIL({column:0})"
let pi = -> s"PI()"
let exp = column -> s"EXP({column:0})"
let ln = column -> s"LN({column:0})"
let log10 = column -> s"LOG10({column:0})"
let log = base column -> s"LOG10({column:0}) / LOG10({base:0})"
let sqrt = column -> s"SQRT({column:0})"
let degrees = column -> s"DEGREES({column:0})"
let radians = column -> s"RADIANS({column:0})"
let cos = column -> s"COS({column:0})"
let acos = column -> s"ACOS({column:0})"
let sin = column -> s"SIN({column:0})"
let asin = column -> s"ASIN({column:0})"
let tan = column -> s"TAN({column:0})"
let atan = column -> s"ATAN({column:0})"
let pow = column exponent -> s"POW({column:0}, {exponent:0})"

# Other functions
let round = n_digits column -> s"ROUND({column:0}, {n_digits:0})"
let as = `type` column -> s"CAST({column:0} AS {type:0})"
Expand Down Expand Up @@ -134,6 +162,11 @@ module bigquery {
@{binding_strength=11}
let div_f = l r -> s"({l} * 1.0 / {r})"

# Mathematical functions
# BigQuery: https://cloud.google.com/bigquery/docs/reference/standard-sql/mathematical_functions
let degrees = column -> s"({column:0} * 180 / PI())"
let radians = column -> s"({column:0} * PI() / 180)"

let regex_search = text pattern -> s"REGEXP_CONTAINS({text:0}, {pattern:0})"
}

Expand Down Expand Up @@ -168,6 +201,12 @@ module mssql {
@{binding_strength=11}
let div_f = l r -> s"({l} * 1.0 / {r})"

# Mathematical functions
# https://learn.microsoft.com/en-us/sql/t-sql/functions/mathematical-functions-transact-sql
let ceil = column -> s"CEILING({column:0})"
let ln = column -> s"LOG({column:0})"
let pow = column exponent -> s"POWER({column:0}, {exponent:0})"
max-sixty marked this conversation as resolved.
Show resolved Hide resolved

let regex_search = text pattern -> null
}

Expand Down
68 changes: 68 additions & 0 deletions prqlc/prql-compiler/tests/integration/sql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,56 @@ fn test_stdlib() {
);
}

#[test]
fn test_stdlib_math() {
assert_snapshot!(compile(r#"
from employees
select {
salary_abs = abs salary,
salary_floor = floor salary,
salary_ceil = ceil salary,
salary_pi = -> pi,
salary_exp = exp salary,
salary_ln = ln salary,
salary_log10 = log10 salary,
salary_log = log 2 salary,
salary_sqrt = sqrt salary,
salary_degrees = degrees salary,
salary_radians = radians salary,
salary_cos = cos salary,
salary_acos = acos salary,
salary_sin = sin salary,
salary_asin = asin salary,
salary_tan = tan salary,
salary_atan = atan salary,
salary_pow = pow salary 2,
}
"#).unwrap(), @r#"
SELECT
ABS(salary) AS salary_abs,
FLOOR(salary) AS salary_floor,
CEIL(salary) AS salary_ceil,
PI() AS salary_pi,
EXP(salary) AS salary_exp,
LN(salary) AS salary_ln,
LOG10(salary) AS salary_log10,
LOG10(salary) / LOG10(2) AS salary_log,
SQRT(salary) AS salary_sqrt,
DEGREES(salary) AS salary_degrees,
RADIANS(salary) AS salary_radians,
COS(salary) AS salary_cos,
ACOS(salary) AS salary_acos,
SIN(salary) AS salary_sin,
ASIN(salary) AS salary_asin,
TAN(salary) AS salary_tan,
ATAN(salary) AS salary_atan,
POW(salary, 2) AS salary_pow
FROM
employees
"#
PrettyWood marked this conversation as resolved.
Show resolved Hide resolved
);
}

#[test]
fn json_of_test() {
let json = prql_compiler::prql_to_pl("from employees | take 10")
Expand Down Expand Up @@ -167,6 +217,24 @@ fn test_precedence() {
);
}

#[test]
#[ignore]
// FIXME: right associativity of `pow` is not implemented yet
fn test_pow_is_right_associative() {
assert_display_snapshot!(compile(r#"
from numbers
select {
c ** a ** b
}
"#).unwrap(), @r#"
SELECT
POW(c, POW(a, b))
FROM
numbers
"#
);
}

#[test]
fn test_append() {
assert_display_snapshot!(compile(r###"
Expand Down
2 changes: 2 additions & 0 deletions prqlc/prqlc-ast/src/expr/ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ pub enum BinOp {
DivFloat,
#[strum(to_string = "%")]
Mod,
#[strum(to_string = "**")]
Pow,
#[strum(to_string = "+")]
Add,
#[strum(to_string = "-")]
Expand Down
1 change: 1 addition & 0 deletions prqlc/prqlc-parser/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ impl std::fmt::Display for DisplayToken<'_> {
Token::Or => f.write_str("||"),
Token::Coalesce => f.write_str("??"),
Token::DivInt => f.write_str("//"),
Token::Pow => f.write_str("**"),
Token::Annotate => f.write_str("@{"),

Token::Param(id) => write!(f, "${id}"),
Expand Down
9 changes: 7 additions & 2 deletions prqlc/prqlc-parser/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,8 @@ pub fn expr() -> impl Parser<Token, Expr, Error = PError> + Clone {

// Binary operators
let expr = term;
// TODO: for `operator_pow` we need to do right-associative parsing
// let expr = binary_op_parser_right(expr, operator_pow());
let expr = binary_op_parser(expr, operator_mul());
let expr = binary_op_parser(expr, operator_add());
let expr = binary_op_parser(expr, operator_compare());
Expand Down Expand Up @@ -385,9 +387,12 @@ fn operator_unary() -> impl Parser<Token, UnOp, Error = PError> {
.or(ctrl('!').to(UnOp::Not))
.or(just(Token::Eq).to(UnOp::EqSelf))
}
// fn operator_pow() -> impl Parser<Token, BinOp, Error = PError> {
// just(Token::Pow).to(BinOp::Pow)
// }
fn operator_mul() -> impl Parser<Token, BinOp, Error = PError> {
(ctrl('*').to(BinOp::Mul))
.or(just(Token::DivInt).to(BinOp::DivInt))
(just(Token::DivInt).to(BinOp::DivInt))
.or(ctrl('*').to(BinOp::Mul))
.or(ctrl('/').to(BinOp::DivFloat))
.or(ctrl('%').to(BinOp::Mod))
}
Expand Down
5 changes: 4 additions & 1 deletion prqlc/prqlc-parser/src/lexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ pub enum Token {
Or, // ||
Coalesce, // ??
DivInt, // //
Annotate, // @
// Pow, // **
Annotate, // @
}

pub fn lexer() -> impl Parser<char, Vec<(Token, std::ops::Range<usize>)>, Error = Cheap<char>> {
Expand All @@ -56,6 +57,7 @@ pub fn lexer() -> impl Parser<char, Vec<(Token, std::ops::Range<usize>)>, Error
just("||").then_ignore(end_expr()).to(Token::Or),
just("??").to(Token::Coalesce),
just("//").to(Token::DivInt),
// just("**").to(Token::Pow),
just("@").then(digits(1).not().rewind()).to(Token::Annotate),
));

Expand Down Expand Up @@ -483,6 +485,7 @@ impl std::fmt::Display for Token {
Token::Or => f.write_str("||"),
Token::Coalesce => f.write_str("??"),
Token::DivInt => f.write_str("//"),
// Token::Pow => f.write_str("**"),
Token::Annotate => f.write_str("@{"),

Token::Param(id) => write!(f, "${id}"),
Expand Down
Loading