Skip to content

Commit

Permalink
Implement BIT_SHIFT_LEFT (<<) binary operation (gluesql#1286)
Browse files Browse the repository at this point in the history
Define a new type to support bit shift operation and added a logic to pass from sqlparesr to gluesql & ast builder.
Support binary operator `<<` for bitwise shift left operation.
  • Loading branch information
codernineteen committed Jul 22, 2023
1 parent 033d1e3 commit 7cd1c5d
Show file tree
Hide file tree
Showing 11 changed files with 369 additions and 3 deletions.
12 changes: 11 additions & 1 deletion core/src/ast/operator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ pub enum BinaryOperator {
And,
Or,
Xor,
BitwiseShiftLeft,
}

impl ToSql for BinaryOperator {
Expand All @@ -59,6 +60,7 @@ impl ToSql for BinaryOperator {
BinaryOperator::And => "AND".to_owned(),
BinaryOperator::Or => "OR".to_owned(),
BinaryOperator::Xor => "XOR".to_owned(),
BinaryOperator::BitwiseShiftLeft => "<<".to_owned(),
}
}
}
Expand Down Expand Up @@ -219,6 +221,15 @@ mod tests {
}
.to_sql()
);
assert_eq!(
"1 << 2",
&Expr::BinaryOp {
left: Box::new(Expr::Literal(AstLiteral::Number(BigDecimal::from(1)))),
op: BinaryOperator::BitwiseShiftLeft,
right: Box::new(Expr::Literal(AstLiteral::Number(BigDecimal::from(2))))
}
.to_sql()
);
assert_eq!(
r#""condition_0" AND "condition_1""#,
&Expr::BinaryOp {
Expand Down Expand Up @@ -246,7 +257,6 @@ mod tests {
}
.to_sql()
);

assert_eq!(
"+8",
Expr::UnaryOp {
Expand Down
8 changes: 8 additions & 0 deletions core/src/ast_builder/expr/binary_op.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ impl<'a> ExprNode<'a> {
pub fn or<T: Into<Self>>(self, other: T) -> Self {
self.binary_op(BinaryOperator::Or, other)
}

pub fn bitwise_shift_left<T: Into<Self>>(self, other: T) -> Self {
self.binary_op(BinaryOperator::BitwiseShiftLeft, other)
}
}

#[cfg(test)]
Expand Down Expand Up @@ -131,5 +135,9 @@ mod tests {
let actual = (col("id").gt(num(10))).or(col("id").lt(num(20)));
let expected = "id > 10 OR id < 20";
test_expr(actual, expected);

let actual = col("id").bitwise_shift_left(num(1));
let expected = "id << 1";
test_expr(actual, expected);
}
}
80 changes: 79 additions & 1 deletion core/src/data/literal.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use {
super::StringExt,
super::{BigDecimalExt, StringExt},
crate::{
ast::AstLiteral,
result::{Error, Result},
Expand All @@ -16,6 +16,18 @@ pub enum LiteralError {
#[error("unsupported literal binary arithmetic between {0} and {1}")]
UnsupportedBinaryArithmetic(String, String),

#[error("a operand '{0}' is not integer type")]
BitwiseNonIntegerOperand(String),

#[error("given operands is not Number literal type")]
BitwiseNonNumberLiteral,

#[error("overflow occured while running bitwise operation")]
BitwiseOperationOverflow,

#[error("impossible conversion from {0} to {1} type")]
ImpossibleConversion(String, String),

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

Expand Down Expand Up @@ -190,6 +202,29 @@ impl<'a> Literal<'a> {
}
}

pub fn bitwise_shift_left(&self, other: &Literal<'a>) -> Result<Literal<'static>> {
match (self, other) {
(Number(l), Number(r)) => {
let l = l
.to_i64()
.ok_or(LiteralError::BitwiseNonIntegerOperand(l.to_string()))?;
if !r.is_integer() {
return Err(LiteralError::BitwiseNonIntegerOperand(r.to_string()).into());
}
let r = r.to_u32().ok_or(LiteralError::ImpossibleConversion(
r.to_string(),
"u32".to_owned(),
))?;
let res = l
.checked_shl(r)
.ok_or(LiteralError::BitwiseOperationOverflow)?;
Ok(Number(Cow::Owned(BigDecimal::from(res))))
}
(Null, Number(_)) | (Number(_), Null) | (Null, Null) => Ok(Literal::Null),
_ => Err(LiteralError::BitwiseNonNumberLiteral.into()),
}
}

pub fn like(&self, other: &Literal<'a>, case_sensitive: bool) -> Result<Self> {
match (self, other) {
(Text(l), Text(r)) => l.like(r, case_sensitive).map(Boolean),
Expand Down Expand Up @@ -283,6 +318,49 @@ mod tests {
assert_eq!(Null.unary_minus(), Ok(Null));
}

#[test]
fn bitwise_shift_left() {
use crate::data::LiteralError;

let num = |n: i32| Number(Cow::Owned(BigDecimal::from(n)));
macro_rules! num {
($num: expr) => {
Number(Cow::Owned(BigDecimal::from_str($num).unwrap()))
};
}

assert_eq!(
num(1).bitwise_shift_left(&num(2)),
Ok(Number(Cow::Borrowed(&BigDecimal::from(4))))
);

assert_eq!(
num(1).bitwise_shift_left(&num(65)),
Err(LiteralError::BitwiseOperationOverflow.into())
);

assert_eq!(num(2).bitwise_shift_left(&Null), Ok(Null));
assert_eq!(Null.bitwise_shift_left(&num(2)), Ok(Null));
assert_eq!(Null.bitwise_shift_left(&Null), Ok(Null));

assert_eq!(
Boolean(true).bitwise_shift_left(&num(2)),
Err(LiteralError::BitwiseNonNumberLiteral.into())
);
assert_eq!(
num(1).bitwise_shift_left(&num(-1)),
Err(LiteralError::ImpossibleConversion("-1".to_owned(), "u32".to_owned()).into())
);
assert_eq!(
num!("1.1").bitwise_shift_left(&num(2)),
Err(LiteralError::BitwiseNonIntegerOperand("1.1".to_owned()).into())
);
assert_eq!(
num(1).bitwise_shift_left(&num!("2.1")),
Err(LiteralError::BitwiseNonIntegerOperand("2.1".to_owned()).into())
);
}

#[test]
fn concat() {
macro_rules! text {
Expand Down
2 changes: 2 additions & 0 deletions core/src/data/value/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,4 +233,6 @@ pub enum NumericBinaryOperator {
Divide,
#[strum(to_string = "%")]
Modulo,
#[strum(to_string = "<<")]
BitwiseShiftLeft,
}
196 changes: 195 additions & 1 deletion core/src/data/value/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,44 @@ impl Value {
}
}

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

if *rhs == Null {
return Ok(Null);
}
let rhs = u32::try_from(rhs)?;
match self {
I8(lhs) => lhs.checked_shl(rhs).map(I8),
I16(lhs) => lhs.checked_shl(rhs).map(I16),
I32(lhs) => lhs.checked_shl(rhs).map(I32),
I64(lhs) => lhs.checked_shl(rhs).map(I64),
I128(lhs) => lhs.checked_shl(rhs).map(I128),
U8(lhs) => lhs.checked_shl(rhs).map(U8),
U16(lhs) => lhs.checked_shl(rhs).map(U16),
U32(lhs) => lhs.checked_shl(rhs).map(U32),
U64(lhs) => lhs.checked_shl(rhs).map(U64),
U128(lhs) => lhs.checked_shl(rhs).map(U128),
Null => Some(Null),
_ => {
return Err(ValueError::NonNumericMathOperation {
lhs: self.clone(),
rhs: U32(rhs),
operator: NumericBinaryOperator::BitwiseShiftLeft,
}
.into());
}
}
.ok_or_else(|| {
ValueError::BinaryOperationOverflow {
lhs: self.clone(),
rhs: U32(rhs),
operator: NumericBinaryOperator::BitwiseShiftLeft,
}
.into()
})
}

pub fn is_null(&self) -> bool {
matches!(self, Value::Null)
}
Expand Down Expand Up @@ -750,7 +788,7 @@ fn str_position(from_str: &String, sub_str: &String) -> usize {
mod tests {
use {
super::{Interval, Value::*},
crate::data::{point::Point, value::uuid::parse_uuid, ValueError},
crate::data::{point::Point, value::uuid::parse_uuid, NumericBinaryOperator, ValueError},
chrono::{NaiveDate, NaiveTime},
rust_decimal::Decimal,
std::{net::IpAddr, str::FromStr},
Expand Down Expand Up @@ -1765,6 +1803,162 @@ mod tests {
null_test!(modulo Null, Null);
}

#[test]
fn bitwise_shift_left() {
macro_rules! test {
($op: ident $a: expr, $b: expr => $c: expr) => {
assert!($a.$op(&$b).unwrap().evaluate_eq(&$c));
};
}

macro_rules! mon {
($n: expr) => {
Interval(Interval::Month($n))
};
}

// operation result test
test!(bitwise_shift_left I8(1), I64(2) => I8(4));
test!(bitwise_shift_left I16(1), I64(2) => I16(4));
test!(bitwise_shift_left I32(1), I64(2) => I32(4));
test!(bitwise_shift_left I64(1), I64(2) => I64(4));
test!(bitwise_shift_left I128(1), I64(2) => I128(4));
test!(bitwise_shift_left U8(1), I64(2) => U8(4));
test!(bitwise_shift_left U16(1), I64(2) => U16(4));
test!(bitwise_shift_left U32(1), I64(2) => U32(4));
test!(bitwise_shift_left U64(1), I64(2) => U64(4));
test!(bitwise_shift_left U128(1), I64(2) => U128(4));
test!(bitwise_shift_left I8(1), U32(2) => I8(4));
test!(bitwise_shift_left I16(1), U32(2) => I16(4));
test!(bitwise_shift_left I32(1), U32(2) => I32(4));
test!(bitwise_shift_left I64(1), U32(2) => I64(4));
test!(bitwise_shift_left I128(1), U32(2) => I128(4));
test!(bitwise_shift_left U8(1), U32(2) => U8(4));
test!(bitwise_shift_left U16(1), U32(2) => U16(4));
test!(bitwise_shift_left U32(1), U32(2) => U32(4));
test!(bitwise_shift_left U64(1), U32(2) => U64(4));
test!(bitwise_shift_left U128(1), U32(2) => U128(4));

//overflow test
assert_eq!(
I8(1).bitwise_shift_left(&I64(100)),
Err(ValueError::BinaryOperationOverflow {
lhs: I8(1),
rhs: U32(100),
operator: NumericBinaryOperator::BitwiseShiftLeft
}
.into())
);
assert_eq!(
I16(1).bitwise_shift_left(&I64(100)),
Err(ValueError::BinaryOperationOverflow {
lhs: I16(1),
rhs: U32(100),
operator: NumericBinaryOperator::BitwiseShiftLeft
}
.into())
);
assert_eq!(
I32(1).bitwise_shift_left(&I64(100)),
Err(ValueError::BinaryOperationOverflow {
lhs: I32(1),
rhs: U32(100),
operator: NumericBinaryOperator::BitwiseShiftLeft
}
.into())
);
assert_eq!(
I64(1).bitwise_shift_left(&I64(100)),
Err(ValueError::BinaryOperationOverflow {
lhs: I64(1),
rhs: U32(100),
operator: NumericBinaryOperator::BitwiseShiftLeft
}
.into())
);
assert_eq!(
I128(1).bitwise_shift_left(&I64(150)),
Err(ValueError::BinaryOperationOverflow {
lhs: I128(1),
rhs: U32(150),
operator: NumericBinaryOperator::BitwiseShiftLeft
}
.into())
);
assert_eq!(
U8(1).bitwise_shift_left(&I64(100)),
Err(ValueError::BinaryOperationOverflow {
lhs: U8(1),
rhs: U32(100),
operator: NumericBinaryOperator::BitwiseShiftLeft
}
.into())
);
assert_eq!(
U16(1).bitwise_shift_left(&I64(100)),
Err(ValueError::BinaryOperationOverflow {
lhs: U16(1),
rhs: U32(100),
operator: NumericBinaryOperator::BitwiseShiftLeft
}
.into())
);
assert_eq!(
U32(1).bitwise_shift_left(&I64(100)),
Err(ValueError::BinaryOperationOverflow {
lhs: U32(1),
rhs: U32(100),
operator: NumericBinaryOperator::BitwiseShiftLeft
}
.into())
);
assert_eq!(
U64(1).bitwise_shift_left(&I64(100)),
Err(ValueError::BinaryOperationOverflow {
lhs: U64(1),
rhs: U32(100),
operator: NumericBinaryOperator::BitwiseShiftLeft
}
.into())
);
assert_eq!(
U128(1).bitwise_shift_left(&I64(150)),
Err(ValueError::BinaryOperationOverflow {
lhs: U128(1),
rhs: U32(150),
operator: NumericBinaryOperator::BitwiseShiftLeft
}
.into())
);

// cast error test
assert_eq!(
I64(1).bitwise_shift_left(&I64(-2)),
Err(ValueError::ImpossibleCast.into())
);

// non numeric test
assert_eq!(
mon!(3).bitwise_shift_left(&I64(2)),
Err(ValueError::NonNumericMathOperation {
lhs: mon!(3),
rhs: U32(2),
operator: NumericBinaryOperator::BitwiseShiftLeft,
}
.into())
);

// null test
macro_rules! null_test {
($op: ident $a: expr, $b: expr) => {
assert!($a.$op(&$b).unwrap().is_null());
};
}

null_test!(bitwise_shift_left I64(1), Null);
null_test!(bitwise_shift_left Null, I64(1));
}

#[test]
fn cast() {
use {
Expand Down

0 comments on commit 7cd1c5d

Please sign in to comment.