Skip to content

Commit

Permalink
Support DivisorShouldNotBeZero error (gluesql#309)
Browse files Browse the repository at this point in the history
Add LiteralError & ValueError for situation when divisor is zero
Remove some `{number} / {interval}` codes
  • Loading branch information
MRGRAVITY817 committed Aug 23, 2021
1 parent abeb122 commit ddef417
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 27 deletions.
76 changes: 56 additions & 20 deletions src/data/literal.rs
Expand Up @@ -15,6 +15,9 @@ pub enum LiteralError {
#[error("unsupported literal binary arithmetic between {0} and {1}")]
UnsupportedBinaryArithmetic(String, String),

#[error("the divisor should not be zero")]
DivisorShouldNotBeZero,

#[error("literal unary operation on non-numeric")]
UnaryOperationOnNonNumeric,

Expand Down Expand Up @@ -233,36 +236,41 @@ impl<'a> Literal<'a> {
match (self, other) {
(Number(l), Number(r)) => {
if let (Ok(l), Ok(r)) = (l.parse::<i64>(), r.parse::<i64>()) {
Ok(Number(Cow::Owned((l / r).to_string())))
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>()) {
Ok(Number(Cow::Owned((l / r).to_string())))
} else {
Err(LiteralError::UnreachableBinaryArithmetic.into())
}
}
(Number(l), Interval(r)) => {
if let Ok(l) = l.parse::<i64>() {
Ok(Interval(l / *r))
} else if let Ok(l) = l.parse::<f64>() {
Ok(Interval(l / *r))
if r == 0.0 {
Err(LiteralError::DivisorShouldNotBeZero.into())
} else {
Ok(Number(Cow::Owned((l / r).to_string())))
}
} else {
Err(LiteralError::UnreachableBinaryArithmetic.into())
}
}
(Interval(l), Number(r)) => {
if let Ok(r) = r.parse::<i64>() {
Ok(Interval(*l / r))
if r == 0 {
Err(LiteralError::DivisorShouldNotBeZero.into())
} else {
Ok(Interval(*l / r))
}
} else if let Ok(r) = r.parse::<f64>() {
Ok(Interval(*l / r))
if r == 0.0 {
Err(LiteralError::DivisorShouldNotBeZero.into())
} else {
Ok(Interval(*l / r))
}
} else {
Err(LiteralError::UnreachableBinaryArithmetic.into())
}
}
(Null, Number(_))
| (Null, Interval(_))
| (Number(_), Null)
| (Interval(_), Null)
| (Null, Null) => Ok(Literal::Null),
(Null, Number(_)) | (Number(_), Null) | (Interval(_), Null) | (Null, Null) => {
Ok(Literal::Null)
}
_ => Err(LiteralError::UnsupportedBinaryArithmetic(
format!("{:?}", self),
format!("{:?}", other),
Expand Down Expand Up @@ -297,8 +305,6 @@ mod tests {
assert_eq!(mon(3).subtract(&mon(1)), Ok(mon(2)));
assert_eq!(mon(3).multiply(&num(-4)), Ok(mon(-12)));
assert_eq!(num(9).multiply(&mon(2)), Ok(mon(18)));
assert_eq!(mon(14).divide(&num(3)), Ok(mon(4)));
assert_eq!(num(27).divide(&mon(9)), Ok(mon(3)));
}

#[test]
Expand All @@ -320,4 +326,34 @@ mod tests {
matches!(Null.concat(Boolean(true)), Null);
matches!(Null.concat(Null), Null);
}

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

macro_rules! num {
($num: expr) => {
Number(Cow::Owned($num.to_owned()))
};
}

macro_rules! itv {
($itv: expr) => {
Interval(I::Microsecond($itv))
};
}

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

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"));
assert_eq!(num!("12.0").divide(&num_divisor("2.0")).unwrap(), num!("6"));
assert_eq!(itv!(12).divide(&num_divisor("2")).unwrap(), itv!(6));
assert_eq!(itv!(12).divide(&num_divisor("2.0")).unwrap(), itv!(6));
matches!(num!("12").divide(&Null).unwrap(), Null);
matches!(itv!(12).divide(&Null).unwrap(), Null);
matches!(Null.divide(&num_divisor("2")).unwrap(), Null);
matches!(Null.divide(&Null).unwrap(), Null);
}
}
3 changes: 3 additions & 0 deletions src/data/value/error.rs
Expand Up @@ -43,6 +43,9 @@ pub enum ValueError {
#[error("divide on non-numeric values: {0:?} / {1:?}")]
DivideOnNonNumeric(Value, Value),

#[error("the divisor should not be zero")]
DivisorShouldNotBeZero,

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

Expand Down
11 changes: 4 additions & 7 deletions src/data/value/mod.rs
Expand Up @@ -239,18 +239,19 @@ impl Value {
pub fn divide(&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)),
(I64(a), Interval(b)) => Ok(Interval(*a / *b)),
(F64(a), I64(b)) => Ok(F64(a / *b as f64)),
(F64(a), Interval(b)) => Ok(Interval(*a / *b)),
(F64(a), F64(b)) => Ok(F64(a / b)),
(Interval(a), I64(b)) => Ok(Interval(*a / *b)),
(Interval(a), F64(b)) => Ok(Interval(*a / *b)),
(Null, I64(_))
| (Null, F64(_))
| (Null, Interval(_))
| (I64(_), Null)
| (F64(_), Null)
| (Interval(_), Null)
Expand Down Expand Up @@ -458,10 +459,8 @@ mod tests {

test!(divide I64(6), I64(2) => I64(3));
test!(divide I64(6), F64(2.0) => F64(3.0));
test!(divide I64(6), mon!(2) => mon!(3));
test!(divide F64(6.0), I64(2) => F64(3.0));
test!(divide F64(6.0), F64(2.0) => F64(3.0));
test!(divide F64(6.0), mon!(2) => mon!(3));
test!(divide mon!(6), I64(2) => mon!(3));
test!(divide mon!(6), F64(2.0) => mon!(3));

Expand Down Expand Up @@ -507,10 +506,8 @@ mod tests {
null_test!(subtract Null, mon!(1));
null_test!(multiply Null, I64(1));
null_test!(multiply Null, F64(1.0));
null_test!(multiply Null, mon!(1));
null_test!(divide Null, I64(1));
null_test!(divide Null, F64(1.0));
null_test!(divide Null, mon!(1));

null_test!(add Null, Null);
null_test!(subtract Null, Null);
Expand Down
24 changes: 24 additions & 0 deletions src/tests/arithmetic.rs
Expand Up @@ -76,6 +76,14 @@ test_case!(arithmetic, async move {
ValueError::DivideOnNonNumeric(Value::Str("A".to_owned()), Value::I64(1)).into(),
"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",
),
(
UpdateError::ColumnNotFound("aaa".to_owned()).into(),
"UPDATE Arith SET aaa = 1",
Expand All @@ -88,6 +96,22 @@ test_case!(arithmetic, async move {
.into(),
"SELECT * FROM Arith WHERE TRUE + 1 = 1",
),
(
LiteralError::DivisorShouldNotBeZero.into(),
"SELECT * FROM Arith WHERE id = 2 / 0",
),
(
LiteralError::DivisorShouldNotBeZero.into(),
"SELECT * FROM Arith WHERE id = 2 / 0.0",
),
(
LiteralError::DivisorShouldNotBeZero.into(),
"SELECT * FROM Arith WHERE id = INTERVAL '2' HOUR / 0",
),
(
LiteralError::DivisorShouldNotBeZero.into(),
"SELECT * FROM Arith WHERE id = INTERVAL '2' HOUR / 0.0",
),
(
EvaluateError::BooleanTypeRequired(format!(
"{:?}",
Expand Down

0 comments on commit ddef417

Please sign in to comment.