From cafbf5499fc9b6945ce44abb1854be26093d8504 Mon Sep 17 00:00:00 2001 From: Lauri Virtanen Date: Sat, 23 Nov 2024 22:28:37 +0200 Subject: [PATCH 1/4] Support divide operator in expressions --- COMPAT.md | 2 +- core/translate/expr.rs | 7 +++ core/vdbe/explain.rs | 9 ++++ core/vdbe/mod.rs | 115 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 132 insertions(+), 1 deletion(-) diff --git a/COMPAT.md b/COMPAT.md index 770d3de52..ee61022a9 100644 --- a/COMPAT.md +++ b/COMPAT.md @@ -236,7 +236,7 @@ This document describes the SQLite compatibility status of Limbo: | DecrJumpZero | Yes | | Delete | No | | Destroy | No | -| Divide | No | +| Divide | Yes | | DropIndex | No | | DropTable | No | | DropTrigger | No | diff --git a/core/translate/expr.rs b/core/translate/expr.rs index 8dba3d3ec..fca31b0ae 100644 --- a/core/translate/expr.rs +++ b/core/translate/expr.rs @@ -720,6 +720,13 @@ pub fn translate_expr( dest: target_register, }); } + ast::Operator::Divide => { + program.emit_insn(Insn::Divide { + lhs: e1_reg, + rhs: e2_reg, + dest: target_register, + }); + } other_unimplemented => todo!("{:?}", other_unimplemented), } Ok(target_register) diff --git a/core/vdbe/explain.rs b/core/vdbe/explain.rs index 3e0aaabc7..9b704fcf6 100644 --- a/core/vdbe/explain.rs +++ b/core/vdbe/explain.rs @@ -46,6 +46,15 @@ pub fn insn_to_str( 0, format!("r[{}]=r[{}]*r[{}]", dest, lhs, rhs), ), + Insn::Divide { lhs, rhs, dest } => ( + "Divide", + *lhs as i32, + *rhs as i32, + *dest as i32, + OwnedValue::Text(Rc::new("".to_string())), + 0, + format!("r[{}]=r[{}]/r[{}]", dest, lhs, rhs), + ), Insn::Null { dest, dest_end } => ( "Null", 0, diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index 2b2033405..c24d227c2 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -107,6 +107,12 @@ pub enum Insn { rhs: usize, dest: usize, }, + // Divide lhs by rhs and store the result in a third register. + Divide { + lhs: usize, + rhs: usize, + dest: usize, + }, // Compare two vectors of registers in reg(P1)..reg(P1+P3-1) (call this vector "A") and in reg(P2)..reg(P2+P3-1) ("B"). Save the result of the comparison for use by the next Jump instruct. Compare { start_reg_a: usize, @@ -817,6 +823,115 @@ impl Program { } state.pc += 1; } + Insn::Divide { lhs, rhs, dest } => { + let lhs = *lhs; + let rhs = *rhs; + let dest = *dest; + match (&state.registers[lhs], &state.registers[rhs]) { + // If the divisor is zero, the result is NULL. + (_, OwnedValue::Integer(0)) | (_, OwnedValue::Float(0.0)) => { + state.registers[dest] = OwnedValue::Null; + } + (OwnedValue::Integer(lhs), OwnedValue::Integer(rhs)) => { + state.registers[dest] = OwnedValue::Integer(lhs / rhs); + } + (OwnedValue::Float(lhs), OwnedValue::Float(rhs)) => { + state.registers[dest] = OwnedValue::Float(lhs / rhs); + } + (OwnedValue::Float(lhs), OwnedValue::Integer(rhs)) => { + state.registers[dest] = OwnedValue::Float(lhs / *rhs as f64); + } + (OwnedValue::Integer(lhs), OwnedValue::Float(rhs)) => { + state.registers[dest] = OwnedValue::Float(*lhs as f64 / rhs); + } + (OwnedValue::Null, _) | (_, OwnedValue::Null) => { + state.registers[dest] = OwnedValue::Null; + } + (OwnedValue::Agg(aggctx), rhs) => match rhs { + OwnedValue::Null => { + state.registers[dest] = OwnedValue::Null; + } + OwnedValue::Integer(i) => match aggctx.final_value() { + OwnedValue::Float(acc) => { + state.registers[dest] = OwnedValue::Float(acc / *i as f64); + } + OwnedValue::Integer(acc) => { + state.registers[dest] = OwnedValue::Integer(acc / i); + } + _ => { + todo!("{:?}", aggctx); + } + }, + OwnedValue::Float(f) => match aggctx.final_value() { + OwnedValue::Float(acc) => { + state.registers[dest] = OwnedValue::Float(acc / f); + } + OwnedValue::Integer(acc) => { + state.registers[dest] = OwnedValue::Float(*acc as f64 / f); + } + _ => { + todo!("{:?}", aggctx); + } + }, + OwnedValue::Agg(aggctx2) => { + let acc = aggctx.final_value(); + let acc2 = aggctx2.final_value(); + match (acc, acc2) { + (OwnedValue::Integer(acc), OwnedValue::Integer(acc2)) => { + state.registers[dest] = OwnedValue::Integer(acc / acc2); + } + (OwnedValue::Float(acc), OwnedValue::Float(acc2)) => { + state.registers[dest] = OwnedValue::Float(acc / acc2); + } + (OwnedValue::Integer(acc), OwnedValue::Float(acc2)) => { + state.registers[dest] = + OwnedValue::Float(*acc as f64 / acc2); + } + (OwnedValue::Float(acc), OwnedValue::Integer(acc2)) => { + state.registers[dest] = + OwnedValue::Float(acc / *acc2 as f64); + } + _ => { + todo!("{:?} {:?}", acc, acc2); + } + } + } + rest => unimplemented!("{:?}", rest), + }, + (lhs, OwnedValue::Agg(aggctx)) => match lhs { + OwnedValue::Null => { + state.registers[dest] = OwnedValue::Null; + } + OwnedValue::Integer(i) => match aggctx.final_value() { + OwnedValue::Float(acc) => { + state.registers[dest] = OwnedValue::Float(*i as f64 / acc); + } + OwnedValue::Integer(acc) => { + state.registers[dest] = OwnedValue::Integer(i / acc); + } + _ => { + todo!("{:?}", aggctx); + } + }, + OwnedValue::Float(f) => match aggctx.final_value() { + OwnedValue::Float(acc) => { + state.registers[dest] = OwnedValue::Float(f / acc); + } + OwnedValue::Integer(acc) => { + state.registers[dest] = OwnedValue::Float(f / *acc as f64); + } + _ => { + todo!("{:?}", aggctx); + } + }, + rest => unimplemented!("{:?}", rest), + }, + others => { + todo!("{:?}", others); + } + } + state.pc += 1; + } Insn::Null { dest, dest_end } => { if let Some(dest_end) = dest_end { for i in *dest..=*dest_end { From af9d407dee30d22a544e9f53e092311f61c64252 Mon Sep 17 00:00:00 2001 From: Lauri Virtanen Date: Sun, 24 Nov 2024 14:43:33 +0200 Subject: [PATCH 2/4] Fix issues with subtraction of different type combinations --- COMPAT.md | 2 +- core/vdbe/mod.rs | 113 ++++++++++++++++++++++++++++++++++------------- 2 files changed, 83 insertions(+), 32 deletions(-) diff --git a/COMPAT.md b/COMPAT.md index ee61022a9..040f14b3b 100644 --- a/COMPAT.md +++ b/COMPAT.md @@ -355,7 +355,7 @@ This document describes the SQLite compatibility status of Limbo: | SorterSort | Yes | | String | No | | String8 | Yes | -| Subtract | No | +| Subtract | Yes | | TableLock | No | | ToBlob | No | | ToInt | No | diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index c24d227c2..0c1a979ea 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -730,43 +730,94 @@ impl Program { (OwnedValue::Float(lhs), OwnedValue::Float(rhs)) => { state.registers[dest] = OwnedValue::Float(lhs - rhs); } - (OwnedValue::Float(lhs), OwnedValue::Integer(rhs)) - | (OwnedValue::Integer(rhs), OwnedValue::Float(lhs)) => { + (OwnedValue::Float(lhs), OwnedValue::Integer(rhs)) => { state.registers[dest] = OwnedValue::Float(lhs - *rhs as f64); } + (OwnedValue::Integer(lhs), OwnedValue::Float(rhs)) => { + state.registers[dest] = OwnedValue::Float(*lhs as f64 - rhs); + } (OwnedValue::Null, _) | (_, OwnedValue::Null) => { state.registers[dest] = OwnedValue::Null; } - (OwnedValue::Agg(aggctx), other) | (other, OwnedValue::Agg(aggctx)) => { - match other { - OwnedValue::Null => { - state.registers[dest] = OwnedValue::Null; - } - OwnedValue::Integer(i) => match aggctx.final_value() { - OwnedValue::Float(acc) => { - state.registers[dest] = OwnedValue::Float(acc - *i as f64); - } - OwnedValue::Integer(acc) => { - state.registers[dest] = OwnedValue::Integer(acc - i); - } - _ => { - todo!("{:?}", aggctx); - } - }, - OwnedValue::Float(f) => match aggctx.final_value() { - OwnedValue::Float(acc) => { - state.registers[dest] = OwnedValue::Float(acc - f); - } - OwnedValue::Integer(acc) => { - state.registers[dest] = OwnedValue::Float(*acc as f64 - f); - } - _ => { - todo!("{:?}", aggctx); - } - }, - rest => unimplemented!("{:?}", rest), + (OwnedValue::Agg(aggctx), rhs) => match rhs { + OwnedValue::Null => { + state.registers[dest] = OwnedValue::Null; } - } + OwnedValue::Integer(i) => match aggctx.final_value() { + OwnedValue::Float(acc) => { + state.registers[dest] = OwnedValue::Float(acc - *i as f64); + } + OwnedValue::Integer(acc) => { + state.registers[dest] = OwnedValue::Integer(acc - i); + } + _ => { + todo!("{:?}", aggctx); + } + }, + OwnedValue::Float(f) => match aggctx.final_value() { + OwnedValue::Float(acc) => { + state.registers[dest] = OwnedValue::Float(acc - f); + } + OwnedValue::Integer(acc) => { + state.registers[dest] = OwnedValue::Float(*acc as f64 - f); + } + _ => { + todo!("{:?}", aggctx); + } + }, + OwnedValue::Agg(aggctx2) => { + let acc = aggctx.final_value(); + let acc2 = aggctx2.final_value(); + match (acc, acc2) { + (OwnedValue::Integer(acc), OwnedValue::Integer(acc2)) => { + state.registers[dest] = OwnedValue::Integer(acc - acc2); + } + (OwnedValue::Float(acc), OwnedValue::Float(acc2)) => { + state.registers[dest] = OwnedValue::Float(acc - acc2); + } + (OwnedValue::Integer(acc), OwnedValue::Float(acc2)) => { + state.registers[dest] = + OwnedValue::Float(*acc as f64 - acc2); + } + (OwnedValue::Float(acc), OwnedValue::Integer(acc2)) => { + state.registers[dest] = + OwnedValue::Float(acc - *acc2 as f64); + } + _ => { + todo!("{:?} {:?}", acc, acc2); + } + } + } + rest => unimplemented!("{:?}", rest), + }, + (lhs, OwnedValue::Agg(aggctx)) => match lhs { + OwnedValue::Null => { + state.registers[dest] = OwnedValue::Null; + } + OwnedValue::Integer(i) => match aggctx.final_value() { + OwnedValue::Float(acc) => { + state.registers[dest] = OwnedValue::Float(*i as f64 - acc); + } + OwnedValue::Integer(acc) => { + state.registers[dest] = OwnedValue::Integer(i - acc); + } + _ => { + todo!("{:?}", aggctx); + } + }, + OwnedValue::Float(f) => match aggctx.final_value() { + OwnedValue::Float(acc) => { + state.registers[dest] = OwnedValue::Float(f - acc); + } + OwnedValue::Integer(acc) => { + state.registers[dest] = OwnedValue::Float(f - *acc as f64); + } + _ => { + todo!("{:?}", aggctx); + } + }, + rest => unimplemented!("{:?}", rest), + }, others => { todo!("{:?}", others); } From 70c4d6b36088557e6c7d2d88a876e4e201399eb9 Mon Sep 17 00:00:00 2001 From: Lauri Virtanen Date: Sun, 24 Nov 2024 14:51:59 +0200 Subject: [PATCH 3/4] Support multiplying combinations of different types --- COMPAT.md | 2 +- core/vdbe/mod.rs | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/COMPAT.md b/COMPAT.md index 040f14b3b..e909c794e 100644 --- a/COMPAT.md +++ b/COMPAT.md @@ -286,7 +286,7 @@ This document describes the SQLite compatibility status of Limbo: | MaxPgcnt | No | | MemMax | No | | Move | No | -| Multiply | No | +| Multiply | Yes | | MustBeInt | Yes | | Ne | Yes | | NewRowid | Yes | diff --git a/core/vdbe/mod.rs b/core/vdbe/mod.rs index 0c1a979ea..ef8fd6146 100644 --- a/core/vdbe/mod.rs +++ b/core/vdbe/mod.rs @@ -835,6 +835,10 @@ impl Program { (OwnedValue::Float(lhs), OwnedValue::Float(rhs)) => { state.registers[dest] = OwnedValue::Float(lhs * rhs); } + (OwnedValue::Integer(i), OwnedValue::Float(f)) + | (OwnedValue::Float(f), OwnedValue::Integer(i)) => { + state.registers[dest] = OwnedValue::Float(*i as f64 * *f as f64); + } (OwnedValue::Null, _) | (_, OwnedValue::Null) => { state.registers[dest] = OwnedValue::Null; } @@ -865,6 +869,29 @@ impl Program { todo!("{:?}", aggctx); } }, + OwnedValue::Agg(aggctx2) => { + let acc = aggctx.final_value(); + let acc2 = aggctx2.final_value(); + match (acc, acc2) { + (OwnedValue::Integer(acc), OwnedValue::Integer(acc2)) => { + state.registers[dest] = OwnedValue::Integer(acc * acc2); + } + (OwnedValue::Float(acc), OwnedValue::Float(acc2)) => { + state.registers[dest] = OwnedValue::Float(acc * acc2); + } + (OwnedValue::Integer(acc), OwnedValue::Float(acc2)) => { + state.registers[dest] = + OwnedValue::Float(*acc as f64 * acc2); + } + (OwnedValue::Float(acc), OwnedValue::Integer(acc2)) => { + state.registers[dest] = + OwnedValue::Float(acc * *acc2 as f64); + } + _ => { + todo!("{:?} {:?}", acc, acc2); + } + } + } rest => unimplemented!("{:?}", rest), } } From 1b2835b316a366d0444df50d2496f6fe3ce3e103 Mon Sep 17 00:00:00 2001 From: Lauri Virtanen Date: Sun, 24 Nov 2024 14:18:21 +0200 Subject: [PATCH 4/4] Add math operator compatibility tests --- testing/all.test | 1 + testing/math.test | 216 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 217 insertions(+) create mode 100644 testing/math.test diff --git a/testing/all.test b/testing/all.test index 966fbce69..62fb8ee90 100755 --- a/testing/all.test +++ b/testing/all.test @@ -10,6 +10,7 @@ source $testdir/join.test source $testdir/insert.test source $testdir/json.test source $testdir/like.test +source $testdir/math.test source $testdir/orderby.test source $testdir/groupby.test source $testdir/pragma.test diff --git a/testing/math.test b/testing/math.test new file mode 100644 index 000000000..9d6e359b4 --- /dev/null +++ b/testing/math.test @@ -0,0 +1,216 @@ +#!/usr/bin/env tclsh + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +do_execsql_test add-int { + SELECT 10 + 1 +} {11} + +do_execsql_test add-float { + SELECT 10.1 + 0.3 +} {10.4} + +do_execsql_test add-int-float { + SELECT 10 + 0.1 +} {10.1} + +do_execsql_test subtract-int { + SELECT 10 - 1 +} {9} + +do_execsql_test subtract-float { + SELECT 10.2 - 0.1 +} {10.1} + +do_execsql_test subtract-int-float { + SELECT 10 - 0.1 +} {9.9} + +do_execsql_test multiply-int { + SELECT 10 * 2 +} {20} + +do_execsql_test multiply-float { + SELECT 10.2 * 2.2 +} {22.44} + +do_execsql_test multiply-int-float { + SELECT 10 * 1.45 +} {14.5} + +do_execsql_test multiply-float-int { + SELECT 1.45 * 10 +} {14.5} + +do_execsql_test divide-int { + SELECT 10 / 2 +} {5} + +do_execsql_test divide-int-no-fraction { + SELECT 10 / 3 +} {3} + +do_execsql_test divide-float { + SELECT 10.6 / 2.5 +} {4.24} + +do_execsql_test divide-int-float { + SELECT 10 / 4.0 +} {2.5} + +do_execsql_test divide-float-int { + SELECT 10.0 / 4 +} {2.5} + +do_execsql_test divide-by-zero { + SELECT 10 / 0 +} {} + +do_execsql_test divide-int-null { + SELECT 10 / null +} {} + +do_execsql_test divide-null-int { + SELECT null / 10 +} {} + +do_execsql_test divide-null { + SELECT null / null +} {} + + + +do_execsql_test add-agg-int { + SELECT sum(id) + 10 from products +} {76} + +do_execsql_test add-int-agg { + SELECT 10 + sum(id) from products +} {76} + +do_execsql_test add-agg-float { + SELECT sum(id) + 10.1 from products +} {76.1} + +do_execsql_test add-float-agg { + SELECT 10.1 + sum(id) from products +} {76.1} + +do_execsql_test add-agg-int-agg-int { + SELECT sum(id) + sum(id) from products +} {132} + +do_execsql_test add-agg-float-agg-float { + SELECT sum(price) + sum(price) from products +} {1246.0} + +do_execsql_test add-agg-int-agg-float { + SELECT sum(id) + sum(price) from products +} {689.0} + +do_execsql_test add-agg-int-agg-float { + SELECT sum(id) + sum(price) from products +} {689.0} + + + +do_execsql_test subtract-agg-int { + SELECT sum(id) - 10 from products +} {56} + +do_execsql_test subtract-int-agg { + SELECT 10 - sum(id) from products +} {-56} + +do_execsql_test subtract-agg-float { + SELECT sum(id) - 10.1 from products +} {55.9} + +do_execsql_test subtract-float-agg { + SELECT 10.1 - sum(id) from products +} {-55.9} + +do_execsql_test subtract-agg-int-agg-int { + SELECT sum(id) - sum(id) from products +} {0} + +do_execsql_test subtract-agg-float-agg-float { + SELECT sum(price) - sum(price) from products +} {0.0} + +do_execsql_test subtract-agg-int-agg-float { + SELECT sum(id) - sum(price) from products +} {-557.0} + +do_execsql_test subtract-agg-float-agg-int { + SELECT sum(price) - sum(id) from products +} {557.0} + + + +do_execsql_test multiply-agg-int { + SELECT sum(id) * 10 from products +} {660} + +do_execsql_test multiply-int-agg { + SELECT 10 * sum(id) from products +} {660} + +do_execsql_test multiply-agg-float { + SELECT sum(id) * 10.1 from products +} {666.6} + +do_execsql_test multiply-float-agg { + SELECT 10.1 * sum(id) from products +} {666.6} + +do_execsql_test multiply-agg-int-agg-int { + SELECT sum(id) * sum(id) from products +} {4356} + +do_execsql_test multiply-agg-float-agg-float { + SELECT sum(price) * sum(price) from products +} {388129.0} + +do_execsql_test multiply-agg-int-agg-float { + SELECT sum(id) * sum(price) from products +} {41118.0} + +do_execsql_test multiply-agg-float-agg-int { + SELECT sum(price) * sum(id) from products +} {41118.0} + + + +do_execsql_test divide-agg-int { + SELECT sum(id) / 10 from products +} {6} + +do_execsql_test divide-int-agg { + SELECT 660 / sum(id) from products +} {10} + +do_execsql_test divide-agg-float { + SELECT sum(id) / 1.5 from products +} {44.0} + +do_execsql_test divide-float-agg { + SELECT 66.0 / sum(id) from products +} {1.0} + +do_execsql_test divide-agg-int-agg-int { + SELECT sum(id) / sum(id) from products +} {1} + +do_execsql_test divide-agg-float-agg-float { + SELECT sum(price) / sum(price) from products +} {1.0} + +do_execsql_test divide-agg-int-agg-float { + SELECT sum(id) / min(price) from products +} {66.0} + +do_execsql_test divide-agg-float-agg-int { + SELECT min(price) / min(id) from products +} {1.0}