diff --git a/COMPAT.md b/COMPAT.md index 0faceccd9..6dabca9f0 100644 --- a/COMPAT.md +++ b/COMPAT.md @@ -446,7 +446,7 @@ Feature support of [sqlite expr syntax](https://www.sqlite.org/lang_expr.html). | Sequence | No | | SetCookie | No | | ShiftLeft | Yes | -| ShiftRight | No | +| ShiftRight | Yes | | SoftNull | Yes | | Sort | No | | SorterCompare | No | diff --git a/core/translate/expr.rs b/core/translate/expr.rs index a011b68ca..6e35c3da8 100644 --- a/core/translate/expr.rs +++ b/core/translate/expr.rs @@ -613,6 +613,13 @@ pub fn translate_expr( dest: target_register, }); } + ast::Operator::RightShift => { + program.emit_insn(Insn::ShiftRight { + lhs: e1_reg, + rhs: e2_reg, + dest: target_register, + }); + } ast::Operator::LeftShift => { program.emit_insn(Insn::ShiftLeft { lhs: e1_reg, diff --git a/core/vdbe/explain.rs b/core/vdbe/explain.rs index a8a61d657..7c3de8364 100644 --- a/core/vdbe/explain.rs +++ b/core/vdbe/explain.rs @@ -1044,6 +1044,15 @@ pub fn insn_to_str( 0, "".to_string(), ), + Insn::ShiftRight { lhs, rhs, dest } => ( + "ShiftRight", + *rhs as i32, + *lhs as i32, + *dest as i32, + OwnedValue::build_text(Rc::new("".to_string())), + 0, + format!("r[{}]=r[{}] >> r[{}]", dest, lhs, rhs), + ), Insn::ShiftLeft { lhs, rhs, dest } => ( "ShiftLeft", *rhs as i32, diff --git a/core/vdbe/insn.rs b/core/vdbe/insn.rs index 95c3214a0..225a9edaf 100644 --- a/core/vdbe/insn.rs +++ b/core/vdbe/insn.rs @@ -488,6 +488,12 @@ pub enum Insn { where_clause: String, }, // Place the result of lhs >> rhs in dest register. + ShiftRight { + lhs: usize, + rhs: usize, + dest: usize, + }, + // Place the result of lhs << rhs in dest register. ShiftLeft { lhs: usize, rhs: usize, @@ -774,3 +780,51 @@ fn compute_shl(lhs: i64, rhs: i64) -> i64 { lhs << rhs } } + +pub fn exec_shift_right(mut lhs: &OwnedValue, mut rhs: &OwnedValue) -> OwnedValue { + if let OwnedValue::Agg(agg) = lhs { + lhs = agg.final_value(); + } + if let OwnedValue::Agg(agg) = rhs { + rhs = agg.final_value(); + } + match (lhs, rhs) { + (OwnedValue::Null, _) | (_, OwnedValue::Null) => OwnedValue::Null, + (OwnedValue::Integer(lh), OwnedValue::Integer(rh)) => { + OwnedValue::Integer(compute_shr(*lh, *rh)) + } + (OwnedValue::Float(lh), OwnedValue::Integer(rh)) => { + OwnedValue::Integer(compute_shr(*lh as i64, *rh)) + } + (OwnedValue::Integer(lh), OwnedValue::Float(rh)) => { + OwnedValue::Integer(compute_shr(*lh, *rh as i64)) + } + (OwnedValue::Float(lh), OwnedValue::Float(rh)) => { + OwnedValue::Integer(compute_shr(*lh as i64, *rh as i64)) + } + (OwnedValue::Text(lhs), OwnedValue::Text(rhs)) => exec_shift_right( + &cast_text_to_numerical(&lhs.value), + &cast_text_to_numerical(&rhs.value), + ), + (OwnedValue::Text(text), other) => { + exec_shift_right(&cast_text_to_numerical(&text.value), other) + } + (other, OwnedValue::Text(text)) => { + exec_shift_right(other, &cast_text_to_numerical(&text.value)) + } + _ => todo!(), + } +} + +fn compute_shr(lhs: i64, rhs: i64) -> i64 { + if rhs == 0 { + lhs + } else if rhs >= 64 || rhs <= -64 { + 0 + } else if rhs < 0 { + // if negative do left shift + lhs << (-rhs) + } else { + lhs >> rhs + } +} diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index f96617b0b..3791b994e 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -45,7 +45,7 @@ use crate::{resolve_ext_path, Connection, Result, Rows, TransactionState, DATABA use datetime::{exec_date, exec_datetime_full, exec_julianday, exec_time, exec_unixepoch}; use insn::{ exec_add, exec_bit_and, exec_bit_not, exec_bit_or, exec_divide, exec_multiply, exec_remainder, - exec_shift_left, exec_subtract, + exec_shift_left, exec_shift_right, exec_subtract, }; use likeop::{construct_like_escape_arg, exec_glob, exec_like_with_escape}; use rand::distributions::{Distribution, Uniform}; @@ -2158,6 +2158,11 @@ impl Program { parse_schema_rows(Some(rows), &mut schema, conn.pager.io.clone())?; state.pc += 1; } + Insn::ShiftRight { lhs, rhs, dest } => { + state.registers[*dest] = + exec_shift_right(&state.registers[*lhs], &state.registers[*rhs]); + state.pc += 1; + } Insn::ShiftLeft { lhs, rhs, dest } => { state.registers[*dest] = exec_shift_left(&state.registers[*lhs], &state.registers[*rhs]); diff --git a/testing/math.test b/testing/math.test index 94d41f468..f404d9901 100644 --- a/testing/math.test +++ b/testing/math.test @@ -515,6 +515,62 @@ foreach {testname lhs rhs ans} { do_execsql_test shift-left-$testname "SELECT $lhs << $rhs" $::ans } +foreach {testname lhs rhs ans} { + int-int 8 2 2 + int-neg_int 8 -2 32 + int-float 8 1.0 4 + int-text 8 'a' 8 + int-text_float 8 '3.0' 1 + int-text_int 8 '1' 4 + int-null 8 NULL {} + int-int-overflow 8 64 0 + int-int-underflow 8 -64 0 + int-float-overflow 8 64.0 0 + int-float-underflow 8 -64.0 0 +} { + do_execsql_test shift-right-$testname "SELECT $lhs >> $rhs" $::ans +} + +foreach {testname lhs rhs ans} { + float-int 8.0 2 2 + float-neg_int 8.0 -2 32 + float-float 8.0 1.0 4 + float-text 8.0 'a' 8 + float-text_float 8.0 '3.0' 1 + float-text_int 8.0 '1' 4 + float-null 8.0 NULL {} + float-int-overflow 8.0 64 0 + float-int-underflow 8.0 -64 0 + float-float-overflow 8.0 64.0 0 + float-float-underflow 8.0 -64.0 0 +} { + do_execsql_test shift-right-$testname "SELECT $lhs >> $rhs" $::ans +} + +foreach {testname lhs rhs ans} { + text-int 'a' 2 0 + text-float 'a' 4.0 0 + text-text 'a' 'a' 0 + text_int-text_int '8' '1' 4 + text_int-text_float '8' '3.0' 1 + text_int-text '8' 'a' 8 + text_float-text_int '8.0' '1' 4 + text_float-text_float '8.0' '3.0' 1 + text_float-text '8.0' 'a' 8 + text-null '8' NULL {} +} { + do_execsql_test shift-right-$testname "SELECT $lhs >> $rhs" $::ans +} + +foreach {testname lhs rhs ans} { + null-int NULL 2 {} + null-float NULL 4.0 {} + null-text NULL 'a' {} + null-null NULL NULL {} +} { + do_execsql_test shift-right-$testname "SELECT $lhs >> $rhs" $::ans +} + do_execsql_test bitwise-not-null { SELECT ~NULL } {}