Skip to content

Commit

Permalink
Implement binary operator modulo % (gluesql#319)
Browse files Browse the repository at this point in the history
  • Loading branch information
MRGRAVITY817 authored Aug 24, 2021
1 parent f1cf8c4 commit 762c940
Show file tree
Hide file tree
Showing 8 changed files with 92 additions and 7 deletions.
1 change: 1 addition & 0 deletions src/ast/operator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub enum BinaryOperator {
Minus,
Multiply,
Divide,
Modulo,
StringConcat,
Gt,
Lt,
Expand Down
39 changes: 38 additions & 1 deletion src/data/literal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,34 @@ impl<'a> Literal<'a> {
}
}

pub fn modulo<'b>(&self, other: &Literal<'a>) -> Result<Literal<'b>> {
match (self, other) {
(Number(l), Number(r)) => {
if let (Ok(l), Ok(r)) = (l.parse::<i64>(), r.parse::<i64>()) {
if r == 0 {
Err(LiteralError::DivisorShouldNotBeZero.into())
} else {
Ok(Number(Cow::Owned((l % r).to_string())))
}
} else if let (Ok(l), Ok(r)) = (l.parse::<f64>(), r.parse::<f64>()) {
if r == 0.0 {
Err(LiteralError::DivisorShouldNotBeZero.into())
} else {
Ok(Number(Cow::Owned((l % r).to_string())))
}
} else {
Err(LiteralError::UnreachableBinaryArithmetic.into())
}
}
(Null, Number(_)) | (Number(_), Null) | (Null, Null) => Ok(Literal::Null),
_ => Err(LiteralError::UnsupportedBinaryArithmetic(
format!("{:?}", self),
format!("{:?}", other),
)
.into()),
}
}

pub fn like(&self, other: &Literal<'a>) -> Result<Self> {
match (self, other) {
(Text(l), Text(r)) => l.like(r).map(Boolean),
Expand Down Expand Up @@ -328,7 +356,7 @@ mod tests {
}

#[test]
fn divide() {
fn div_mod() {
use crate::data::interval::Interval as I;

macro_rules! num {
Expand All @@ -345,6 +373,7 @@ mod tests {

let num_divisor = |x: &str| Number(Cow::Owned(x.to_owned()));

// Divide Test
assert_eq!(num!("12").divide(&num_divisor("2")).unwrap(), num!("6"));
assert_eq!(num!("12").divide(&num_divisor("2.0")).unwrap(), num!("6"));
assert_eq!(num!("12.0").divide(&num_divisor("2")).unwrap(), num!("6"));
Expand All @@ -355,5 +384,13 @@ mod tests {
matches!(itv!(12).divide(&Null).unwrap(), Null);
matches!(Null.divide(&num_divisor("2")).unwrap(), Null);
matches!(Null.divide(&Null).unwrap(), Null);
// Modulo Test
assert_eq!(num!("12").modulo(&num_divisor("2")).unwrap(), num!("0"));
assert_eq!(num!("12").modulo(&num_divisor("2.0")).unwrap(), num!("0"));
assert_eq!(num!("12.0").modulo(&num_divisor("2")).unwrap(), num!("0"));
assert_eq!(num!("12.0").modulo(&num_divisor("2.0")).unwrap(), num!("0"));
matches!(num!("12").modulo(&Null).unwrap(), Null);
matches!(Null.modulo(&num_divisor("2")).unwrap(), Null);
matches!(Null.modulo(&Null).unwrap(), Null);
}
}
3 changes: 3 additions & 0 deletions src/data/value/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ pub enum ValueError {
#[error("the divisor should not be zero")]
DivisorShouldNotBeZero,

#[error("modulo on non-numeric values: {0:?} % {1:?}")]
ModuloOnNonNumeric(Value, Value),

#[error("floating numbers cannot be grouped by")]
FloatCannotBeGroupedBy,

Expand Down
29 changes: 29 additions & 0 deletions src/data/value/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,25 @@ impl Value {
}
}

pub fn modulo(&self, other: &Value) -> Result<Value> {
use Value::*;

if (other == &I64(0)) | (other == &F64(0.0)) {
return Err(ValueError::DivisorShouldNotBeZero.into());
}

match (self, other) {
(I64(a), I64(b)) => Ok(I64(a % b)),
(I64(a), F64(b)) => Ok(F64(*a as f64 % b)),
(F64(a), I64(b)) => Ok(F64(a % *b as f64)),
(F64(a), F64(b)) => Ok(F64(a % b)),
(Null, I64(_)) | (Null, F64(_)) | (I64(_), Null) | (F64(_), Null) | (Null, Null) => {
Ok(Null)
}
_ => Err(ValueError::ModuloOnNonNumeric(self.clone(), other.clone()).into()),
}
}

pub fn is_null(&self) -> bool {
matches!(self, Value::Null)
}
Expand Down Expand Up @@ -464,6 +483,11 @@ mod tests {
test!(divide mon!(6), I64(2) => mon!(3));
test!(divide mon!(6), F64(2.0) => mon!(3));

test!(modulo I64(6), I64(2) => I64(0));
test!(modulo I64(6), F64(2.0) => F64(0.0));
test!(modulo F64(6.0), I64(2) => F64(0.0));
test!(modulo F64(6.0), F64(2.0) => F64(0.0));

macro_rules! null_test {
($op: ident $a: expr, $b: expr) => {
matches!($a.$op(&$b), Ok(Null));
Expand Down Expand Up @@ -492,6 +516,8 @@ mod tests {
null_test!(divide I64(1), Null);
null_test!(divide F64(1.0), Null);
null_test!(divide mon!(1), Null);
null_test!(modulo I64(1), Null);
null_test!(modulo F64(1.0), Null);

null_test!(add Null, I64(1));
null_test!(add Null, F64(1.0));
Expand All @@ -508,11 +534,14 @@ mod tests {
null_test!(multiply Null, F64(1.0));
null_test!(divide Null, I64(1));
null_test!(divide Null, F64(1.0));
null_test!(modulo Null, I64(1));
null_test!(modulo Null, F64(1.0));

null_test!(add Null, Null);
null_test!(subtract Null, Null);
null_test!(multiply Null, Null);
null_test!(divide Null, Null);
null_test!(modulo Null, Null);
}

#[test]
Expand Down
4 changes: 4 additions & 0 deletions src/executor/evaluate/evaluated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ impl<'a> Evaluated<'a> {
binary_op(self, other, |l, r| l.divide(r), |l, r| l.divide(r))
}

pub fn modulo<'b>(&'a self, other: &Evaluated<'b>) -> Result<Evaluated<'b>> {
binary_op(self, other, |l, r| l.modulo(r), |l, r| l.modulo(r))
}

pub fn unary_plus(&self) -> Result<Evaluated<'a>> {
match self {
Evaluated::Literal(v) => v.unary_plus().map(Evaluated::Literal),
Expand Down
1 change: 1 addition & 0 deletions src/executor/evaluate/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ pub fn binary_op<'a>(
BinaryOperator::Minus => l.subtract(&r),
BinaryOperator::Multiply => l.multiply(&r),
BinaryOperator::Divide => l.divide(&r),
BinaryOperator::Modulo => l.modulo(&r),
BinaryOperator::StringConcat => l.concat(r),
BinaryOperator::Eq => cmp!(l == r),
BinaryOperator::NotEq => cmp!(l != r),
Expand Down
21 changes: 15 additions & 6 deletions src/tests/arithmetic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ test_case!(arithmetic, async move {
(5, "SELECT * FROM Arith WHERE id > id / 2;"),
(3, "SELECT * FROM Arith WHERE id > num / id;"),
(2, "SELECT * FROM Arith WHERE 10 / id = 2;"),
// modulo on WHERE
(1, "SELECT * FROM Arith WHERE id = 5 % 2;"),
(5, "SELECT * FROM Arith WHERE id > num % id;"),
(1, "SELECT * FROM Arith WHERE num % id > 2;"),
(2, "SELECT * FROM Arith WHERE num % 3 < 2 % id;"),
// etc
(1, "SELECT * FROM Arith WHERE 1 + 1 = id;"),
(5, "UPDATE Arith SET id = id + 1;"),
Expand Down Expand Up @@ -77,12 +82,8 @@ test_case!(arithmetic, async move {
"SELECT * FROM Arith WHERE name / id < 1",
),
(
ValueError::DivisorShouldNotBeZero.into(),
"SELECT * FROM Arith WHERE name / 0 < 1",
),
(
ValueError::DivisorShouldNotBeZero.into(),
"SELECT * FROM Arith WHERE name / 0.0 < 1",
ValueError::ModuloOnNonNumeric(Value::Str("A".to_owned()), Value::I64(1)).into(),
"SELECT * FROM Arith WHERE name % id < 1",
),
(
UpdateError::ColumnNotFound("aaa".to_owned()).into(),
Expand Down Expand Up @@ -112,6 +113,14 @@ test_case!(arithmetic, async move {
LiteralError::DivisorShouldNotBeZero.into(),
"SELECT * FROM Arith WHERE id = INTERVAL '2' HOUR / 0.0",
),
(
LiteralError::DivisorShouldNotBeZero.into(),
"SELECT * FROM Arith WHERE id = 2 % 0",
),
(
LiteralError::DivisorShouldNotBeZero.into(),
"SELECT * FROM Arith WHERE id = 2 % 0.0",
),
(
EvaluateError::BooleanTypeRequired(format!(
"{:?}",
Expand Down
1 change: 1 addition & 0 deletions src/translate/operator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pub fn translate_binary_operator(
SqlBinaryOperator::Minus => Ok(BinaryOperator::Minus),
SqlBinaryOperator::Multiply => Ok(BinaryOperator::Multiply),
SqlBinaryOperator::Divide => Ok(BinaryOperator::Divide),
SqlBinaryOperator::Modulus => Ok(BinaryOperator::Modulo),
SqlBinaryOperator::StringConcat => Ok(BinaryOperator::StringConcat),
SqlBinaryOperator::Gt => Ok(BinaryOperator::Gt),
SqlBinaryOperator::Lt => Ok(BinaryOperator::Lt),
Expand Down

0 comments on commit 762c940

Please sign in to comment.