Repository: tursodatabase/limbo (rust)

Issue:
`SELECT 0.0 = 0` incorrectly returns false
The current derived `PartialEq` trait doesn't handle this case, the two variants of an `enum` are always considered distinct. We should manually implement that trait to handle the edge case.

https://github.com/tursodatabase/limbo/blob/669317c11e24b56ab256d88c81161c9e69252ee8/core/types.rs#L64

Found while fuzzing in #1116. (`left` is sqlite)

```
thread '<unnamed>' panicked at fuzz_targets/expression.rs:209:5:
assertion `left == right` failed: with expression Output { query: "(?) = (?)", parameters: [Real(0.0), Integer(0)], depth: 1 }
  left: Integer(1)
 right: Integer(0)
```

Pull request description:
Fix `SELECT 0.0 = 0` returning false
Fixes #1155

Patch:
diff --git a/core/types.rs b/core/types.rs
index 593c99ef..9295b80d 100644
--- a/core/types.rs
+++ b/core/types.rs
@@ -61,7 +61,7 @@ impl Text {
     }
 }
 
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone)]
 pub enum OwnedValue {
     Null,
     Integer(i64),
@@ -249,6 +249,36 @@ impl AggContext {
     }
 }
 
+impl PartialEq<OwnedValue> for OwnedValue {
+    fn eq(&self, other: &OwnedValue) -> bool {
+        match (self, other) {
+            (Self::Integer(int_left), Self::Integer(int_right)) => int_left == int_right,
+            (Self::Integer(int_left), Self::Float(float_right)) => {
+                (*int_left as f64) == (*float_right)
+            }
+            (Self::Float(float_left), Self::Integer(int_right)) => {
+                float_left == (&(*int_right as f64))
+            }
+            (Self::Float(float_left), Self::Float(float_right)) => float_left == float_right,
+            (Self::Integer(_) | Self::Float(_), Self::Text(_) | Self::Blob(_)) => false,
+            (Self::Text(_) | Self::Blob(_), Self::Integer(_) | Self::Float(_)) => false,
+            (Self::Text(text_left), Self::Text(text_right)) => {
+                text_left.value.eq(&text_right.value)
+            }
+            (Self::Blob(blob_left), Self::Blob(blob_right)) => blob_left.eq(blob_right),
+            (Self::Null, Self::Null) => true,
+            (Self::Agg(a), Self::Agg(b)) => a.eq(b),
+            (Self::Agg(a), other) => a.final_value().eq(other),
+            (other, Self::Agg(b)) => other.eq(b.final_value()),
+            _ => false,
+        }
+    }
+
+    fn ne(&self, other: &OwnedValue) -> bool {
+        !self.eq(other)
+    }
+}
+
 #[allow(clippy::non_canonical_partial_ord_impl)]
 impl PartialOrd<OwnedValue> for OwnedValue {
     fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {


Test patch:
diff --git a/testing/math.test b/testing/math.test
old mode 100644
new mode 100755
index 5774be93..847f94f9
--- a/testing/math.test
+++ b/testing/math.test
@@ -1352,3 +1352,35 @@ do_execsql_test mod-agg-int {
 do_execsql_test mod-agg-float {
     SELECT count(*) % 2.43 from users 
 } { 0.0 }
+
+do_execsql_test comp-float-float {
+    SELECT 0.0 = 0.0
+} { 1 }
+
+do_execsql_test comp-int-float {
+    SELECT 0 = 0.0
+} { 1 }
+
+do_execsql_test comp-float-int {
+    SELECT 0.0 = 0
+} { 1 }
+
+do_execsql_test comp-int-string {
+    SELECT 0 = '0'
+} { 0 }
+
+do_execsql_test comp-string-int {
+    SELECT '0' = 0
+} { 0 }
+
+do_execsql_test comp-string-blog {
+    SELECT '0' = cast('0' as BLOB)
+} { 0 }
+
+do_execsql_test comp-blog-string {
+    SELECT cast('0' as BLOB) = '0'
+} { 0 }
+
+do_execsql_test comp-blog-blog {
+    SELECT cast('0' as BLOB) = cast('0' as BLOB)
+} { 1 }
\ No newline at end of file