Skip to content

Commit 0b24e4f

Browse files
authored
Merge pull request #58 from Embucket/snowflake-decimal-bounds
Fix Snowflake decimal bounds in Iceberg metadata
2 parents f8ba337 + f348135 commit 0b24e4f

3 files changed

Lines changed: 233 additions & 105 deletions

File tree

catalogs/iceberg-rest-catalog/src/catalog.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,7 @@ impl Catalog for RestCatalog {
393393
self.name.as_deref(),
394394
&identifier.namespace().to_string(),
395395
create_table,
396-
None,
396+
Some("vended-credentials"),
397397
)
398398
.map_err(Into::<Error>::into)
399399
.await?;

iceberg-rust-spec/src/spec/values.rs

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -121,12 +121,7 @@ impl From<Value> for ByteBuf {
121121
Value::UUID(val) => ByteBuf::from(val.as_u128().to_be_bytes()),
122122
Value::Fixed(_, val) => ByteBuf::from(val),
123123
Value::Binary(val) => ByteBuf::from(val),
124-
Value::Decimal(val) => {
125-
// rust_decimal mantissa is 96 bits
126-
// so we can remove the first 32 bits of the i128 representation
127-
let bytes = val.mantissa().to_be_bytes()[4..].to_vec();
128-
ByteBuf::from(bytes)
129-
}
124+
Value::Decimal(val) => ByteBuf::from(i128_to_minimal_signed_be_bytes(val.mantissa())),
130125
_ => todo!(),
131126
}
132127
}
@@ -755,6 +750,24 @@ pub fn sign_extend_be<const N: usize>(b: &[u8]) -> [u8; N] {
755750
result
756751
}
757752

753+
/// Encodes a signed integer using the minimal big-endian two's-complement
754+
/// representation required for Iceberg decimal values.
755+
pub fn i128_to_minimal_signed_be_bytes(value: i128) -> Vec<u8> {
756+
let bytes = value.to_be_bytes();
757+
let sign_extension = if value < 0 { 0xff } else { 0x00 };
758+
let sign_bit = sign_extension & 0x80;
759+
let mut start = 0;
760+
761+
while start < bytes.len() - 1
762+
&& bytes[start] == sign_extension
763+
&& (bytes[start + 1] & 0x80) == sign_bit
764+
{
765+
start += 1;
766+
}
767+
768+
bytes[start..].to_vec()
769+
}
770+
758771
impl From<&Value> for JsonValue {
759772
fn from(value: &Value) -> Self {
760773
match value {
@@ -1364,10 +1377,7 @@ mod tests {
13641377
// Test serialization
13651378
let byte_buf: ByteBuf = value.clone().into();
13661379
let bytes: Vec<u8> = byte_buf.into_vec();
1367-
assert_eq!(
1368-
bytes,
1369-
vec![0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 160u8, 16u8, 94u8]
1370-
);
1380+
assert_eq!(bytes, vec![0u8, 160u8, 16u8, 94u8]);
13711381

13721382
// Test deserialization
13731383
check_avro_bytes_serde(
@@ -1380,6 +1390,16 @@ mod tests {
13801390
);
13811391
}
13821392

1393+
#[test]
1394+
fn decimal_bytes_are_minimal_signed_big_endian() {
1395+
assert_eq!(i128_to_minimal_signed_be_bytes(0), vec![0]);
1396+
assert_eq!(i128_to_minimal_signed_be_bytes(2), vec![2]);
1397+
assert_eq!(i128_to_minimal_signed_be_bytes(127), vec![127]);
1398+
assert_eq!(i128_to_minimal_signed_be_bytes(128), vec![0, 128]);
1399+
assert_eq!(i128_to_minimal_signed_be_bytes(-1), vec![255]);
1400+
assert_eq!(i128_to_minimal_signed_be_bytes(-129), vec![255, 127]);
1401+
}
1402+
13831403
#[test]
13841404
fn test_transform_identity() {
13851405
let value = Value::Int(42);

0 commit comments

Comments
 (0)