Merge 'Improve maths support' from Lauri Virtanen

- Add support for division in SQL expressions
- Fix issues with subtraction
- Support multiplication of integers and floats
- Support aggregate functions in mathematical expressions
- Add compatibility tests for mathematical operations, also with
aggregate functions

Reviewed-by: Jussi Saurio <jussi.saurio@gmail.com>

Closes #418
This commit is contained in:
jussisaurio
2024-11-25 16:36:45 +02:00
6 changed files with 460 additions and 34 deletions

View File

@@ -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 |
@@ -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 |
@@ -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 |

View File

@@ -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)

View File

@@ -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,

View File

@@ -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,
@@ -724,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);
}
@@ -778,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;
}
@@ -808,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),
}
}
@@ -817,6 +901,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 {

View File

@@ -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

216
testing/math.test Normal file
View File

@@ -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}