paimon 0.2.0

The rust implementation of Apache Paimon
Documentation
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

use crate::Error;

/// Zero-copy cursor over Avro binary-encoded data.
pub struct AvroCursor<'a> {
    data: &'a [u8],
    pos: usize,
}

impl<'a> AvroCursor<'a> {
    #[inline]
    pub fn new(data: &'a [u8]) -> Self {
        Self { data, pos: 0 }
    }

    #[inline]
    #[allow(dead_code)]
    pub fn position(&self) -> usize {
        self.pos
    }

    #[inline]
    pub fn remaining(&self) -> usize {
        self.data.len() - self.pos
    }

    #[inline]
    fn require(&self, n: usize) -> crate::Result<()> {
        if self.pos + n > self.data.len() {
            return Err(Error::UnexpectedError {
                message: format!(
                    "avro cursor: need {} bytes at offset {}, but only {} remain",
                    n,
                    self.pos,
                    self.remaining()
                ),
                source: None,
            });
        }
        Ok(())
    }

    /// Read a zigzag-encoded variable-length long (Avro int and long encoding).
    #[inline]
    pub fn read_long(&mut self) -> crate::Result<i64> {
        let raw = if self.data.len() - self.pos >= 10 {
            self.read_varint_fast()
        } else {
            self.read_varint_slow()
        }?;
        Ok(((raw >> 1) as i64) ^ -((raw & 1) as i64))
    }

    /// Fast path: no per-byte bounds check (caller guarantees >= 10 bytes remain).
    #[inline]
    fn read_varint_fast(&mut self) -> crate::Result<u64> {
        let buf = &self.data[self.pos..];
        let mut raw: u64 = 0;
        let mut shift: u32 = 0;
        let mut i = 0;
        loop {
            let b = buf[i] as u64;
            i += 1;
            raw |= (b & 0x7F) << shift;
            if b & 0x80 == 0 {
                self.pos += i;
                return Ok(raw);
            }
            shift += 7;
            if shift >= 64 {
                return Err(Error::UnexpectedError {
                    message: "avro cursor: varint overflow".into(),
                    source: None,
                });
            }
        }
    }

    /// Slow path: bounds check each byte (< 10 bytes remaining).
    #[inline]
    fn read_varint_slow(&mut self) -> crate::Result<u64> {
        let mut raw: u64 = 0;
        let mut shift: u32 = 0;
        loop {
            self.require(1)?;
            let b = self.data[self.pos] as u64;
            self.pos += 1;
            raw |= (b & 0x7F) << shift;
            if b & 0x80 == 0 {
                return Ok(raw);
            }
            shift += 7;
            if shift >= 64 {
                return Err(Error::UnexpectedError {
                    message: "avro cursor: varint overflow".into(),
                    source: None,
                });
            }
        }
    }

    #[inline]
    pub fn read_int(&mut self) -> crate::Result<i32> {
        let raw = if self.data.len() - self.pos >= 5 {
            self.read_varint_int_fast()
        } else {
            self.read_varint_slow()
        }?;
        let zigzag = ((raw >> 1) as i64) ^ -((raw & 1) as i64);
        Ok(zigzag as i32)
    }

    /// Fast path for int: no per-byte bounds check (caller guarantees >= 5 bytes remain).
    #[inline]
    fn read_varint_int_fast(&mut self) -> crate::Result<u64> {
        let buf = &self.data[self.pos..];
        let mut raw: u64 = 0;
        let mut shift: u32 = 0;
        let mut i = 0;
        loop {
            let b = buf[i] as u64;
            i += 1;
            raw |= (b & 0x7F) << shift;
            if b & 0x80 == 0 {
                self.pos += i;
                return Ok(raw);
            }
            shift += 7;
            if shift >= 35 {
                return Err(Error::UnexpectedError {
                    message: "avro cursor: int varint overflow".into(),
                    source: None,
                });
            }
        }
    }

    #[inline]
    #[allow(dead_code)]
    pub fn read_boolean(&mut self) -> crate::Result<bool> {
        self.require(1)?;
        let b = self.data[self.pos];
        self.pos += 1;
        Ok(b != 0)
    }

    #[inline]
    #[allow(dead_code)]
    pub fn read_float(&mut self) -> crate::Result<f32> {
        self.require(4)?;
        let bytes: [u8; 4] = self.data[self.pos..self.pos + 4].try_into().unwrap();
        self.pos += 4;
        Ok(f32::from_le_bytes(bytes))
    }

    #[inline]
    #[allow(dead_code)]
    pub fn read_double(&mut self) -> crate::Result<f64> {
        self.require(8)?;
        let bytes: [u8; 8] = self.data[self.pos..self.pos + 8].try_into().unwrap();
        self.pos += 8;
        Ok(f64::from_le_bytes(bytes))
    }

    /// Read Avro bytes: length-prefixed raw bytes, zero-copy.
    #[inline]
    pub fn read_bytes(&mut self) -> crate::Result<&'a [u8]> {
        let raw_len = self.read_long()?;
        if raw_len < 0 {
            return Err(Error::UnexpectedError {
                message: format!("avro cursor: negative bytes length: {raw_len}"),
                source: None,
            });
        }
        let len = raw_len as usize;
        self.require(len)?;
        let slice = &self.data[self.pos..self.pos + len];
        self.pos += len;
        Ok(slice)
    }

    /// Read Avro string: length-prefixed UTF-8, zero-copy.
    #[inline]
    pub fn read_string(&mut self) -> crate::Result<&'a str> {
        let bytes = self.read_bytes()?;
        std::str::from_utf8(bytes).map_err(|e| Error::UnexpectedError {
            message: format!("avro cursor: invalid UTF-8 string: {e}"),
            source: None,
        })
    }

    /// Read a union index (same encoding as long).
    #[inline]
    pub fn read_union_index(&mut self) -> crate::Result<i64> {
        self.read_long()
    }

    /// Skip `n` raw bytes.
    #[inline]
    pub fn skip_raw(&mut self, n: usize) -> crate::Result<()> {
        self.require(n)?;
        self.pos += n;
        Ok(())
    }

    /// Skip an Avro bytes or string value.
    #[inline]
    pub fn skip_bytes(&mut self) -> crate::Result<()> {
        let raw_len = self.read_long()?;
        if raw_len < 0 {
            return Err(Error::UnexpectedError {
                message: format!("avro cursor: negative bytes length: {raw_len}"),
                source: None,
            });
        }
        self.skip_raw(raw_len as usize)
    }

    /// Skip an Avro long/int value.
    #[inline]
    pub fn skip_long(&mut self) -> crate::Result<()> {
        self.read_long().map(|_| ())
    }

    /// Read Avro fixed bytes of known length, zero-copy.
    #[inline]
    pub fn read_fixed(&mut self, len: usize) -> crate::Result<&'a [u8]> {
        self.require(len)?;
        let slice = &self.data[self.pos..self.pos + len];
        self.pos += len;
        Ok(slice)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn zigzag_encode(n: i64) -> Vec<u8> {
        let mut encoded: u64 = ((n << 1) ^ (n >> 63)) as u64;
        let mut buf = Vec::new();
        loop {
            if encoded & !0x7F == 0 {
                buf.push(encoded as u8);
                break;
            }
            buf.push((encoded & 0x7F | 0x80) as u8);
            encoded >>= 7;
        }
        buf
    }

    #[test]
    fn test_read_long_values() {
        for val in [0i64, 1, -1, 42, -42, 127, -128, i64::MAX, i64::MIN] {
            let bytes = zigzag_encode(val);
            let mut cursor = AvroCursor::new(&bytes);
            assert_eq!(cursor.read_long().unwrap(), val, "failed for {val}");
            assert_eq!(cursor.remaining(), 0);
        }
    }

    #[test]
    fn test_read_int() {
        let bytes = zigzag_encode(100);
        let mut cursor = AvroCursor::new(&bytes);
        assert_eq!(cursor.read_int().unwrap(), 100);
    }

    #[test]
    fn test_read_boolean() {
        let mut cursor = AvroCursor::new(&[0, 1]);
        assert!(!cursor.read_boolean().unwrap());
        assert!(cursor.read_boolean().unwrap());
    }

    #[test]
    fn test_read_float_double() {
        let f_bytes = std::f32::consts::PI.to_le_bytes();
        let d_bytes = std::f64::consts::E.to_le_bytes();
        let mut data = Vec::new();
        data.extend_from_slice(&f_bytes);
        data.extend_from_slice(&d_bytes);

        let mut cursor = AvroCursor::new(&data);
        assert!((cursor.read_float().unwrap() - std::f32::consts::PI).abs() < 1e-6);
        assert!((cursor.read_double().unwrap() - std::f64::consts::E).abs() < 1e-10);
    }

    #[test]
    fn test_read_bytes_and_string() {
        let mut data = Vec::new();
        // bytes: length=5, content="hello"
        data.extend_from_slice(&zigzag_encode(5));
        data.extend_from_slice(b"hello");
        // string: length=5, content="world"
        data.extend_from_slice(&zigzag_encode(5));
        data.extend_from_slice(b"world");

        let mut cursor = AvroCursor::new(&data);
        assert_eq!(cursor.read_bytes().unwrap(), b"hello");
        assert_eq!(cursor.read_string().unwrap(), "world");
    }

    #[test]
    fn test_skip() {
        let mut data = Vec::new();
        data.extend_from_slice(&zigzag_encode(3));
        data.extend_from_slice(b"abc");
        data.extend_from_slice(&zigzag_encode(99));

        let mut cursor = AvroCursor::new(&data);
        cursor.skip_bytes().unwrap();
        assert_eq!(cursor.read_int().unwrap(), 99);
    }

    #[test]
    fn test_eof_error() {
        let mut cursor = AvroCursor::new(&[]);
        assert!(cursor.read_long().is_err());
    }
}