Repository: spaceandtimelabs/sxt-proof-of-sql
Pull request #694: feat: support additional numeric types
Author: login: stuarttimwhite
Labels: name: released
Created: 2025-04-01T13:59:23Z | Merged: 2025-04-02T14:49:35Z

Please be sure to look over the pull request guidelines here: https://github.com/spaceandtimelabs/sxt-proof-of-sql/blob/main/CONTRIBUTING.md#submit-pr.

# Please go through the following checklist
- [ ] The PR title and commit messages adhere to guidelines here: https://github.com/spaceandtimelabs/sxt-proof-of-sql/blob/main/CONTRIBUTING.md. In particular `!` is used if and only if at least one breaking change has been introduced.
- [ ] I have run the ci check script with `source scripts/run_ci_checks.sh`.
- [ ] I have run the clean commit check script with `source scripts/check_commits.sh`, and the commit history is certified to follow clean commit guidelines as described here: https://github.com/spaceandtimelabs/sxt-proof-of-sql/blob/main/COMMIT_GUIDELINES.md
- [ ] The latest changes from `main` have been incorporated to this PR by simple rebase if possible, if not, then conflicts are resolved appropriately.

# Rationale for this change

<!--
 Why are you proposing this change? If this is already explained clearly in the linked issue then this section is not needed.
 Explaining clearly why changes are proposed helps reviewers understand your changes and offer better suggestions for fixes.

 Example:
 Add `NestedLoopJoinExec`.
 Closes #345.

 Since we added `HashJoinExec` in #323 it has been possible to do provable inner joins. However performance is not satisfactory in some cases. Hence we need to fix the problem by implement `NestedLoopJoinExec` and speed up the code
 for `HashJoinExec`.
-->

# What changes are included in this PR?

<!--
There is no need to duplicate the description in the ticket here but it is sometimes worth providing a summary of the individual changes in this PR.

Example:
- Add `NestedLoopJoinExec`.
- Speed up `HashJoinExec`.
- Route joins to `NestedLoopJoinExec` if the outer input is sufficiently small.
-->

# Are these changes tested?
<!--
We typically require tests for all PRs in order to:
1. Prevent the code from being accidentally broken by subsequent changes
2. Serve as another way to document the expected behavior of the code

If tests are not included in your PR, please explain why (for example, are they covered by existing tests)?

Example:
Yes.
-->



Diff:
diff --git a/crates/proof-of-sql/src/base/database/column_type_operation.rs b/crates/proof-of-sql/src/base/database/column_type_operation.rs
index 5cc680647..3d8ea2d4a 100644
--- a/crates/proof-of-sql/src/base/database/column_type_operation.rs
+++ b/crates/proof-of-sql/src/base/database/column_type_operation.rs
@@ -189,6 +189,7 @@ pub fn try_divide_column_types(
 }
 
 /// Verifies that `from` can be cast to `to`. For now, this supports a limited number of casts.
+#[expect(clippy::missing_panics_doc)]
 pub fn try_cast_types(from: ColumnType, to: ColumnType) -> ColumnOperationResult<()> {
     match (from, to) {
         (
@@ -199,12 +200,32 @@ pub fn try_cast_types(from: ColumnType, to: ColumnType) -> ColumnOperationResult
             | ColumnType::Int128
             | ColumnType::BigInt,
         )
-        | (ColumnType::TimestampTZ(_, _), ColumnType::BigInt) => Ok(()),
-        _ => Err(ColumnOperationError::CastingError {
-            left_type: from,
-            right_type: to,
-        }),
+        | (ColumnType::TimestampTZ(_, _), ColumnType::BigInt)
+        | (ColumnType::Uint8, ColumnType::Uint8)
+        | (ColumnType::TinyInt, ColumnType::TinyInt) => true,
+        (
+            ColumnType::TinyInt
+            | ColumnType::Uint8
+            | ColumnType::SmallInt
+            | ColumnType::Int
+            | ColumnType::Int128
+            | ColumnType::BigInt,
+            ColumnType::Decimal75(_, 0)
+            | ColumnType::SmallInt
+            | ColumnType::Int
+            | ColumnType::BigInt
+            | ColumnType::Int128,
+        )
+        | (ColumnType::Decimal75(_, 0), ColumnType::Decimal75(_, 0)) => {
+            to.precision_value().unwrap() >= from.precision_value().unwrap()
+        }
+        _ => false,
     }
+    .then_some(())
+    .ok_or(ColumnOperationError::CastingError {
+        left_type: from,
+        right_type: to,
+    })
 }
 
 /// Verifies that `from` can be cast to `to`.
@@ -860,7 +881,7 @@ mod test {
     }
 
     #[test]
-    fn we_can_cast_all_castable_types() {
+    fn we_can_cast_bool_to_signed_ints() {
         for to in [
             ColumnType::TinyInt,
             ColumnType::SmallInt,
@@ -870,6 +891,66 @@ mod test {
         ] {
             try_cast_types(ColumnType::Boolean, to).unwrap();
         }
+    }
+
+    #[test]
+    fn we_can_cast_integers_and_decimal_to_decimal() {
+        for from in [
+            ColumnType::TinyInt,
+            ColumnType::Uint8,
+            ColumnType::SmallInt,
+            ColumnType::Int,
+            ColumnType::BigInt,
+            ColumnType::Int128,
+            ColumnType::Decimal75(Precision::new(60).unwrap(), 0),
+        ] {
+            try_cast_types(from, ColumnType::Decimal75(Precision::new(60).unwrap(), 0)).unwrap();
+            try_cast_types(
+                from,
+                ColumnType::Decimal75(Precision::new(from.precision_value().unwrap()).unwrap(), 0),
+            )
+            .unwrap();
+            try_cast_types(from, ColumnType::Decimal75(Precision::new(2).unwrap(), 0)).unwrap_err();
+        }
+    }
+
+    #[test]
+    fn we_can_cast_integers_to_signed_integers() {
+        try_cast_types(ColumnType::Uint8, ColumnType::Uint8).unwrap();
+        try_cast_types(ColumnType::Uint8, ColumnType::TinyInt).unwrap_err();
+        try_cast_types(ColumnType::Uint8, ColumnType::SmallInt).unwrap();
+        try_cast_types(ColumnType::Uint8, ColumnType::Int).unwrap();
+        try_cast_types(ColumnType::Uint8, ColumnType::BigInt).unwrap();
+        try_cast_types(ColumnType::Uint8, ColumnType::Int128).unwrap();
+        try_cast_types(ColumnType::TinyInt, ColumnType::TinyInt).unwrap();
+        try_cast_types(ColumnType::TinyInt, ColumnType::SmallInt).unwrap();
+        try_cast_types(ColumnType::TinyInt, ColumnType::Int).unwrap();
+        try_cast_types(ColumnType::TinyInt, ColumnType::BigInt).unwrap();
+        try_cast_types(ColumnType::TinyInt, ColumnType::Int128).unwrap();
+        try_cast_types(ColumnType::SmallInt, ColumnType::TinyInt).unwrap_err();
+        try_cast_types(ColumnType::SmallInt, ColumnType::SmallInt).unwrap();
+        try_cast_types(ColumnType::SmallInt, ColumnType::Int).unwrap();
+        try_cast_types(ColumnType::SmallInt, ColumnType::BigInt).unwrap();
+        try_cast_types(ColumnType::SmallInt, ColumnType::Int128).unwrap();
+        try_cast_types(ColumnType::Int, ColumnType::TinyInt).unwrap_err();
+        try_cast_types(ColumnType::Int, ColumnType::SmallInt).unwrap_err();
+        try_cast_types(ColumnType::Int, ColumnType::Int).unwrap();
+        try_cast_types(ColumnType::Int, ColumnType::BigInt).unwrap();
+        try_cast_types(ColumnType::Int, ColumnType::Int128).unwrap();
+        try_cast_types(ColumnType::BigInt, ColumnType::TinyInt).unwrap_err();
+        try_cast_types(ColumnType::BigInt, ColumnType::SmallInt).unwrap_err();
+        try_cast_types(ColumnType::BigInt, ColumnType::Int).unwrap_err();
+        try_cast_types(ColumnType::BigInt, ColumnType::BigInt).unwrap();
+        try_cast_types(ColumnType::BigInt, ColumnType::Int128).unwrap();
+        try_cast_types(ColumnType::Int128, ColumnType::TinyInt).unwrap_err();
+        try_cast_types(ColumnType::Int128, ColumnType::SmallInt).unwrap_err();
+        try_cast_types(ColumnType::Int128, ColumnType::Int).unwrap_err();
+        try_cast_types(ColumnType::Int128, ColumnType::BigInt).unwrap_err();
+        try_cast_types(ColumnType::Int128, ColumnType::Int128).unwrap();
+    }
+
+    #[test]
+    fn we_can_cast_timestamp_to_big_int() {
         try_cast_types(
             ColumnType::TimestampTZ(PoSQLTimeUnit::Millisecond, PoSQLTimeZone::new(1)),
             ColumnType::BigInt,
diff --git a/crates/proof-of-sql/src/sql/proof_exprs/cast_expr_test.rs b/crates/proof-of-sql/src/sql/proof_exprs/cast_expr_test.rs
index 584203231..dc0dc55bd 100644
--- a/crates/proof-of-sql/src/sql/proof_exprs/cast_expr_test.rs
+++ b/crates/proof-of-sql/src/sql/proof_exprs/cast_expr_test.rs
@@ -7,10 +7,11 @@ use crate::{
         database::{
             owned_table_utility::{
                 bigint, boolean, decimal75, int, int128, owned_table, smallint, timestamptz,
-                tinyint,
+                tinyint, uint8,
             },
             ColumnType, LiteralValue, OwnedTableTestAccessor, TableRef,
         },
+        math::decimal::Precision,
         posql_time::{PoSQLTimeUnit, PoSQLTimeZone},
     },
     sql::{
@@ -84,6 +85,81 @@ fn we_can_prove_a_simple_cast_expr() {
     assert_eq!(res, expected_res);
 }
 
+#[test]
+fn we_can_prove_a_simple_cast_expr_from_int_to_other_numeric_type() {
+    let data = owned_table([
+        tinyint("a", [1]),
+        uint8("b", [1]),
+        smallint("c", [1i16]),
+        int("d", [1i32]),
+        bigint("e", [1i64]),
+        int128("f", [1i128]),
+        decimal75("g", 2, 0, [1]),
+    ]);
+    let t = TableRef::new("sxt", "t");
+    let accessor =
+        OwnedTableTestAccessor::<InnerProductProof>::new_from_table(t.clone(), data, 0, ());
+    let ast = filter(
+        vec![
+            aliased_plan(
+                cast(column(&t, "a", &accessor), ColumnType::SmallInt),
+                "a_cast",
+            ),
+            aliased_plan(
+                cast(column(&t, "b", &accessor), ColumnType::Uint8),
+                "b_cast",
+            ),
+            aliased_plan(
+                cast(column(&t, "c", &accessor), ColumnType::BigInt),
+                "c_cast",
+            ),
+            aliased_plan(
+                cast(column(&t, "d", &accessor), ColumnType::Int128),
+                "d_cast",
+            ),
+            aliased_plan(
+                cast(
+                    column(&t, "e", &accessor),
+                    ColumnType::Decimal75(Precision::new(42).unwrap(), 0),
+                ),
+                "e_cast",
+            ),
+            aliased_plan(
+                cast(
+                    column(&t, "f", &accessor),
+                    ColumnType::Decimal75(Precision::new(42).unwrap(), 0),
+                ),
+                "f_cast",
+            ),
+            aliased_plan(
+                cast(
+                    column(&t, "g", &accessor),
+                    ColumnType::Decimal75(Precision::new(42).unwrap(), 0),
+                ),
+                "g_cast",
+            ),
+        ],
+        tab(&t),
+        super::DynProofExpr::Literal(LiteralExpr::new(LiteralValue::Boolean(true))),
+    );
+    let verifiable_res = VerifiableQueryResult::new(&ast, &accessor, &(), &[]).unwrap();
+    exercise_verification(&verifiable_res, &ast, &accessor, &t);
+    let res = verifiable_res
+        .verify(&ast, &accessor, &(), &[])
+        .unwrap()
+        .table;
+    let expected_res = owned_table([
+        smallint("a_cast", [1i16]),
+        uint8("b_cast", [1u8]),
+        bigint("c_cast", [1i64]),
+        int128("d_cast", [1i128]),
+        decimal75("e_cast", 42, 0, [1]),
+        decimal75("f_cast", 42, 0, [1]),
+        decimal75("g_cast", 42, 0, [1]),
+    ]);
+    assert_eq!(res, expected_res);
+}
+
 #[test]
 fn we_get_error_if_we_cast_uncastable_type() {
     let data = owned_table([decimal75("a", 57, 2, [1_i16, 2, 3, 4])]);
diff --git a/crates/proof-of-sql/src/sql/proof_exprs/numerical_util.rs b/crates/proof-of-sql/src/sql/proof_exprs/numerical_util.rs
index ca938c4cc..cd4d62eb0 100644
--- a/crates/proof-of-sql/src/sql/proof_exprs/numerical_util.rs
+++ b/crates/proof-of-sql/src/sql/proof_exprs/numerical_util.rs
@@ -1,11 +1,12 @@
 use crate::base::{
     database::{
-        try_scale_cast_types, Column, ColumnOperationResult, ColumnType, ColumnarValue,
-        LiteralValue,
+        try_cast_types, try_scale_cast_types, Column, ColumnOperationResult, ColumnType,
+        ColumnarValue, LiteralValue,
     },
     math::decimal::Precision,
     scalar::{Scalar, ScalarExt},
 };
+use alloc::format;
 use bnum::types::U256;
 use bumpalo::Bump;
 use core::{cmp::Ordering, ops::Neg};
@@ -532,6 +533,57 @@ fn cast_bool_column_to_signed_int_column<'a, S: Scalar>(
     }
 }
 
+/// # Panics
+/// Panics if `I` cannot be cast to `O`
+fn cast_int_slice_to_int_slice<'a, I: NumCast + PrimInt, O: NumCast + PrimInt>(
+    alloc: &'a Bump,
+    column: &[I],
+) -> &'a [O] {
+    alloc.alloc_slice_fill_iter(column.iter().copied().map(|i| NumCast::from(i).unwrap()))
+}
+
+/// # Panics
+/// Panics if the to type is not supported
+fn cast_int_slice_to_int_column<'a, S: Scalar, I: NumCast + PrimInt>(
+    alloc: &'a Bump,
+    column: &[I],
+    to_type: ColumnType,
+) -> Column<'a, S> {
+    match to_type {
+        ColumnType::Uint8 => Column::Uint8(cast_int_slice_to_int_slice(alloc, column)),
+        ColumnType::TinyInt => Column::TinyInt(cast_int_slice_to_int_slice(alloc, column)),
+        ColumnType::SmallInt => Column::SmallInt(cast_int_slice_to_int_slice(alloc, column)),
+        ColumnType::Int => Column::Int(cast_int_slice_to_int_slice(alloc, column)),
+        ColumnType::BigInt => Column::BigInt(cast_int_slice_to_int_slice(alloc, column)),
+        ColumnType::Int128 => Column::Int128(cast_int_slice_to_int_slice(alloc, column)),
+        _ => panic!("Unsupported cast from int type to {to_type}"),
+    }
+}
+
+/// # Panics
+/// Panics if the from type is not supported
+fn cast_int_column_to_int_column<'a, S: Scalar>(
+    alloc: &'a Bump,
+    from_column: Column<'a, S>,
+    to_type: ColumnType,
+) -> Column<'a, S> {
+    match from_column {
+        Column::Uint8(column) => cast_int_slice_to_int_column(alloc, column, to_type),
+        Column::TinyInt(column) => cast_int_slice_to_int_column(alloc, column, to_type),
+        Column::SmallInt(column) => cast_int_slice_to_int_column(alloc, column, to_type),
+        Column::Int(column) => cast_int_slice_to_int_column(alloc, column, to_type),
+        Column::BigInt(column) => cast_int_slice_to_int_column(alloc, column, to_type),
+        Column::Int128(column) => cast_int_slice_to_int_column(alloc, column, to_type),
+        _ => panic!(
+            "{}",
+            format!(
+                "Unsupported cast from {} to {to_type}",
+                from_column.column_type()
+            )
+        ),
+    }
+}
+
 /// Handles the casting of one column to another
 ///
 /// # Panics
@@ -541,6 +593,9 @@ pub fn cast_column<'a, S: Scalar>(
     from_column: Column<'a, S>,
     to_type: ColumnType,
 ) -> Column<'a, S> {
+    let from_type = from_column.column_type();
+    try_cast_types(from_type, to_type)
+        .unwrap_or_else(|_| panic!("Unable to cast between types {from_type} and {to_type}"));
     match (from_column, to_type) {
         (
             Column::Boolean(vals),
@@ -550,12 +605,39 @@ pub fn cast_column<'a, S: Scalar>(
             | ColumnType::BigInt
             | ColumnType::Int128,
         ) => cast_bool_column_to_signed_int_column(alloc, vals, to_type),
-        (Column::TimestampTZ(_, _, vals), ColumnType::BigInt) => Column::BigInt(vals),
-        _ => panic!(
-            "Casting not supported between {} and {}",
-            ColumnType::Boolean,
-            to_type
+        (
+            Column::TinyInt(_)
+            | Column::Uint8(_)
+            | Column::SmallInt(_)
+            | Column::Int(_)
+            | Column::BigInt(_)
+            | Column::Int128(_),
+            ColumnType::Decimal75(precision, 0),
+        ) => Column::Decimal75(
+            precision,
+            0,
+            alloc.alloc_slice_fill_with(from_column.len(), |i| from_column.scalar_at(i).unwrap())
+                as &[_],
         ),
+        (Column::Decimal75(_, 0, scalars), ColumnType::Decimal75(precision, 0)) => {
+            Column::Decimal75(precision, 0, scalars)
+        }
+        (
+            Column::TinyInt(_)
+            | Column::Uint8(_)
+            | Column::SmallInt(_)
+            | Column::Int(_)
+            | Column::BigInt(_)
+            | Column::Int128(_),
+            ColumnType::TinyInt
+            | ColumnType::Uint8
+            | ColumnType::SmallInt
+            | ColumnType::Int
+            | ColumnType::BigInt
+            | ColumnType::Int128,
+        ) => cast_int_column_to_int_column(alloc, from_column, to_type),
+        (Column::TimestampTZ(_, _, vals), ColumnType::BigInt) => Column::BigInt(vals),
+        _ => panic!("Casting not supported between {from_type} and {to_type}"),
     }
 }
 
@@ -606,11 +688,12 @@ pub fn scale_cast_column<'a, S: Scalar>(
 #[cfg(test)]
 mod tests {
     use super::{
-        cast_bool_column_to_signed_int_column, cast_column, divide_columns, divide_integer_columns,
+        cast_bool_column_to_signed_int_column, cast_column, cast_int_slice_to_int_column,
+        divide_columns, divide_integer_columns,
     };
     use crate::{
         base::{
-            database::{Column, ColumnType},
+            database::{try_cast_types, Column, ColumnType},
             math::decimal::Precision,
             posql_time::{PoSQLTimeUnit, PoSQLTimeZone},
             scalar::{test_scalar::TestScalar, Scalar, ScalarExt},
@@ -812,10 +895,8 @@ mod tests {
     }
 
     #[test]
-    fn we_can_cast_columns_of_all_castable_types() {
+    fn we_can_cast_bool_columns_to_signed_int_column() {
         let alloc = Bump::new();
-
-        // bool to signed casting
         let bool_column = Column::<TestScalar>::Boolean(&[true, false, true]);
         let expected_tiny_int_column = Column::<TestScalar>::TinyInt(&[1i8, 0, 1]);
         let expected_small_int_column = Column::<TestScalar>::SmallInt(&[1i16, 0, 1]);
@@ -833,8 +914,51 @@ mod tests {
                 cast_column(&alloc, bool_column, expected_signed_column.column_type());
             assert_eq!(signed_column, expected_signed_column);
         }
+    }
 
-        // timestamp to big int
+    #[test]
+    fn we_can_cast_numeric_types_to_numeric_types_when_castable() {
+        let alloc = Bump::new();
+        let small_decimal_column =
+            Column::<TestScalar>::Decimal75(Precision::new(2).unwrap(), 0, &[TestScalar::ONE]);
+        let uint8_column = Column::<TestScalar>::Uint8(&[1u8]);
+        let tiny_int_column = Column::<TestScalar>::TinyInt(&[1i8]);
+        let small_int_column = Column::<TestScalar>::SmallInt(&[1i16]);
+        let int_column = Column::<TestScalar>::Int(&[1i32]);
+        let big_int_column = Column::<TestScalar>::BigInt(&[1i64]);
+        let int_128_column = Column::<TestScalar>::Int128(&[1i128]);
+        let big_decimal_column =
+            Column::<TestScalar>::Decimal75(Precision::new(2).unwrap(), 0, &[TestScalar::ONE]);
+        for (from_column, to_column) in iproduct!(
+            [
+                small_decimal_column,
+                uint8_column,
+                tiny_int_column,
+                small_int_column,
+                int_column,
+                big_int_column,
+                int_128_column
+            ],
+            [
+                uint8_column,
+                tiny_int_column,
+                small_int_column,
+                int_column,
+                big_int_column,
+                int_128_column,
+                big_decimal_column
+            ]
+        ) {
+            let to_type = to_column.column_type();
+            if let Ok(()) = try_cast_types(from_column.column_type(), to_type) {
+                assert_eq!(cast_column(&alloc, from_column, to_type), to_column);
+            }
+        }
+    }
+
+    #[test]
+    fn we_can_cast_timestamp_column_to_bigint_column() {
+        let alloc = Bump::new();
         let timestamp_column = Column::<TestScalar>::TimestampTZ(
             PoSQLTimeUnit::Microsecond,
             PoSQLTimeZone::new(1),
@@ -845,7 +969,7 @@ mod tests {
         assert_eq!(big_int_column, expected_big_int_column);
     }
 
-    #[should_panic(expected = "Casting not supported between BOOLEAN and BINARY")]
+    #[should_panic(expected = "Unable to cast between types BOOLEAN and BINARY")]
     #[test]
     fn we_cannot_cast_column_of_uncastable_type() {
         let alloc = Bump::new();
@@ -865,6 +989,14 @@ mod tests {
         );
     }
 
+    #[should_panic(expected = "Unsupported cast from int type to BINARY")]
+    #[test]
+    fn we_cannot_cast_int_slice_to_uncastable_type() {
+        let alloc = Bump::new();
+        let int_column = &[1];
+        cast_int_slice_to_int_column::<TestScalar, _>(&alloc, int_column, ColumnType::VarBinary);
+    }
+
     #[test]
     fn we_can_properly_determine_scaling_factors_for_ints() {
         for from in [


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR introduces support for additional numeric types by expanding casting functionality between various integer types and decimals. Key changes include:
- New utility functions in numerical_util.rs to cast integer slices and columns to signed integer columns.
- Updates to cast_column to handle conversions from numeric types to Decimal75 and other signed integer types.
- Expanded tests and modifications in try_cast_types to reflect the enhanced numeric type-casting rules.

### Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

| File | Description |
| ---- | ----------- |
| crates/proof-of-sql/src/sql/proof_exprs/numerical_util.rs | Added new functions and updated cast_column for additional numeric type support |
| crates/proof-of-sql/src/sql/proof_exprs/cast_expr_test.rs | Introduced tests for casting between various numeric types |
| crates/proof-of-sql/src/base/database/column_type_operation.rs | Updated try_cast_types and tests to include new numeric casting rules |


<details>
<summary>Comments suppressed due to low confidence (1)</summary>

**crates/proof-of-sql/src/sql/proof_exprs/numerical_util.rs:616**
* The use of 'as &[_]' for casting the allocated slice might lead to an ambiguous or incorrect type conversion. Consider explicitly converting to the expected slice type to ensure type safety.
```
                as &[_],
```
</details>


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T13:37:19Z
user: login: iajoiner
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T13:59:29Z
user: login: iajoiner
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T14:16:32Z
user: login: iajoiner
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-02T14:34:00Z

Review comments:
diff_hunk: @@ -532,6 +533,57 @@ fn cast_bool_column_to_signed_int_column<'a, S: Scalar>(
     }
 }
 
+/// # Panics
+/// Panics if `I` cannot be cast to `O`
+fn cast_int_slice_to_signed_int_slice<'a, I: NumCast + PrimInt, O: NumCast + PrimInt>(
+    alloc: &'a Bump,
+    column: &[I],
+) -> &'a [O] {
+    alloc.alloc_slice_fill_iter(column.iter().copied().map(|i| NumCast::from(i).unwrap()))
+}
+
+/// # Panics
+/// Panics if the to type is not supported
+fn cast_int_slice_to_signed_int_column<'a, S: Scalar, I: NumCast + PrimInt>(
+    alloc: &'a Bump,
+    column: &[I],
+    to_type: ColumnType,
+) -> Column<'a, S> {
+    match to_type {
+        ColumnType::Uint8 => Column::Uint8(cast_int_slice_to_signed_int_slice(alloc, column)),
path: crates/proof-of-sql/src/sql/proof_exprs/numerical_util.rs
user: login: iajoiner
body: Since to_type supports Uint8 the func name is not exactly accurate
created_at: 2025-04-02T13:58:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -532,6 +533,57 @@ fn cast_bool_column_to_signed_int_column<'a, S: Scalar>(
     }
 }
 
+/// # Panics
+/// Panics if `I` cannot be cast to `O`
+fn cast_int_slice_to_signed_int_slice<'a, I: NumCast + PrimInt, O: NumCast + PrimInt>(
path: crates/proof-of-sql/src/sql/proof_exprs/numerical_util.rs
user: login: iajoiner
body: cast_int_slice_to_int_slice
created_at: 2025-04-02T14:16:32Z
author_association: CONTRIBUTOR

PR comments:
user: login: codecov[bot]
created_at: 2025-04-01T14:30:40Z
body: ## [Codecov](https://app.codecov.io/gh/spaceandtimelabs/sxt-proof-of-sql/pull/694?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spaceandtimelabs) Report
Attention: Patch coverage is `96.61017%` with `8 lines` in your changes missing coverage. Please review.
> Project coverage is 96.08%. Comparing base [(`1740f0b`)](https://app.codecov.io/gh/spaceandtimelabs/sxt-proof-of-sql/commit/1740f0bc5f23f6549906671df9f59a9918dc52da?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spaceandtimelabs) to head [(`8daf28c`)](https://app.codecov.io/gh/spaceandtimelabs/sxt-proof-of-sql/commit/8daf28c56ef4684ef08fc70b089c08c75ef9ee6b?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spaceandtimelabs).
> Report is 5 commits behind head on main.

| [Files with missing lines](https://app.codecov.io/gh/spaceandtimelabs/sxt-proof-of-sql/pull/694?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spaceandtimelabs) | Patch % | Lines |
|---|---|---|
| [...proof-of-sql/src/sql/proof\_exprs/numerical\_util.rs](https://app.codecov.io/gh/spaceandtimelabs/sxt-proof-of-sql/pull/694?src=pr&el=tree&filepath=crates%2Fproof-of-sql%2Fsrc%2Fsql%2Fproof_exprs%2Fnumerical_util.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spaceandtimelabs#diff-Y3JhdGVzL3Byb29mLW9mLXNxbC9zcmMvc3FsL3Byb29mX2V4cHJzL251bWVyaWNhbF91dGlsLnJz) | 92.00% | [8 Missing :warning: ](https://app.codecov.io/gh/spaceandtimelabs/sxt-proof-of-sql/pull/694?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spaceandtimelabs) |

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff            @@
##             main     #694    +/-   ##
========================================
  Coverage   96.07%   96.08%            
========================================
  Files         328      328            
  Lines       57391    57613   +222     
========================================
+ Hits        55140    55357   +217     
- Misses       2251     2256     +5     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/spaceandtimelabs/sxt-proof-of-sql/pull/694?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spaceandtimelabs).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=spaceandtimelabs).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-02T15:24:33Z
body: :tada: This PR is included in version 0.90.0 :tada:

The release is available on [GitHub release](https://github.com/spaceandtimelabs/sxt-proof-of-sql/releases/tag/v0.90.0)

Your **[semantic-release](https://github.com/semantic-release/semantic-release)** bot :package::rocket:
author_association: NONE