LCOV - code coverage report
Current view: top level - libs/postgres_ffi/src - relfile_utils.rs (source / functions) Coverage Total Hit
Test: 1e20c4f2b28aa592527961bb32170ebbd2c9172f.info Lines: 100.0 % 54 54
Test Date: 2025-07-16 12:29:03 Functions: 100.0 % 6 6

            Line data    Source code
       1              : //!
       2              : //! Common utilities for dealing with PostgreSQL relation files.
       3              : //!
       4              : use once_cell::sync::OnceCell;
       5              : use regex::Regex;
       6              : 
       7              : use postgres_ffi_types::forknum::*;
       8              : 
       9              : /// Parse a filename of a relation file. Returns (relfilenode, forknum, segno) tuple.
      10              : ///
      11              : /// Formats:
      12              : ///
      13              : /// ```text
      14              : /// <oid>
      15              : /// <oid>_<fork name>
      16              : /// <oid>.<segment number>
      17              : /// <oid>_<fork name>.<segment number>
      18              : /// ```
      19              : ///
      20              : /// See functions relpath() and _mdfd_segpath() in PostgreSQL sources.
      21              : ///
      22          963 : pub fn parse_relfilename(fname: &str) -> Result<(u32, u8, u32), FilePathError> {
      23              :     static RELFILE_RE: OnceCell<Regex> = OnceCell::new();
      24          963 :     RELFILE_RE.get_or_init(|| {
      25            4 :         Regex::new(r"^(?P<relnode>\d+)(_(?P<forkname>[a-z]+))?(\.(?P<segno>\d+))?$").unwrap()
      26            4 :     });
      27              : 
      28          963 :     let caps = RELFILE_RE
      29          963 :         .get()
      30          963 :         .unwrap()
      31          963 :         .captures(fname)
      32          963 :         .ok_or(FilePathError::InvalidFileName)?;
      33              : 
      34          959 :     let relnode_str = caps.name("relnode").unwrap().as_str();
      35          959 :     let relnode = relnode_str
      36          959 :         .parse::<u32>()
      37          959 :         .map_err(|_e| FilePathError::InvalidFileName)?;
      38              : 
      39          958 :     let forkname = caps.name("forkname").map(|f| f.as_str());
      40          958 :     let forknum = forkname_to_number(forkname)?;
      41              : 
      42          957 :     let segno_match = caps.name("segno");
      43          957 :     let segno = if segno_match.is_none() {
      44          952 :         0
      45              :     } else {
      46            5 :         segno_match
      47            5 :             .unwrap()
      48            5 :             .as_str()
      49            5 :             .parse::<u32>()
      50            5 :             .map_err(|_e| FilePathError::InvalidFileName)?
      51              :     };
      52              : 
      53          957 :     Ok((relnode, forknum, segno))
      54          963 : }
      55              : 
      56              : #[cfg(test)]
      57              : mod tests {
      58              :     use super::*;
      59              : 
      60              :     #[test]
      61            1 :     fn test_parse_valid_relfilenames() {
      62            1 :         assert_eq!(parse_relfilename("1234"), Ok((1234, 0, 0)));
      63            1 :         assert_eq!(parse_relfilename("1234_fsm"), Ok((1234, 1, 0)));
      64            1 :         assert_eq!(parse_relfilename("1234_vm"), Ok((1234, 2, 0)));
      65            1 :         assert_eq!(parse_relfilename("1234_init"), Ok((1234, 3, 0)));
      66              : 
      67            1 :         assert_eq!(parse_relfilename("1234.12"), Ok((1234, 0, 12)));
      68            1 :         assert_eq!(parse_relfilename("1234_fsm.12"), Ok((1234, 1, 12)));
      69            1 :         assert_eq!(parse_relfilename("1234_vm.12"), Ok((1234, 2, 12)));
      70            1 :         assert_eq!(parse_relfilename("1234_init.12"), Ok((1234, 3, 12)));
      71              : 
      72              :         // relfilenode is unsigned, so it can go up to 2^32-1
      73            1 :         assert_eq!(parse_relfilename("3147483648"), Ok((3147483648, 0, 0)));
      74            1 :     }
      75              : 
      76              :     #[test]
      77            1 :     fn test_parse_invalid_relfilenames() {
      78            1 :         assert_eq!(
      79            1 :             parse_relfilename("foo"),
      80              :             Err(FilePathError::InvalidFileName)
      81              :         );
      82            1 :         assert_eq!(
      83            1 :             parse_relfilename("1.2.3"),
      84              :             Err(FilePathError::InvalidFileName)
      85              :         );
      86            1 :         assert_eq!(
      87            1 :             parse_relfilename("1234_invalid"),
      88              :             Err(FilePathError::InvalidForkName)
      89              :         );
      90            1 :         assert_eq!(
      91            1 :             parse_relfilename("1234_"),
      92              :             Err(FilePathError::InvalidFileName)
      93              :         );
      94              : 
      95              :         // too large for u32
      96            1 :         assert_eq!(
      97            1 :             parse_relfilename("12345678901"),
      98              :             Err(FilePathError::InvalidFileName)
      99              :         );
     100            1 :         assert_eq!(
     101            1 :             parse_relfilename("-1234"),
     102              :             Err(FilePathError::InvalidFileName)
     103              :         );
     104            1 :     }
     105              : 
     106              :     #[test]
     107            1 :     fn test_parse_weird_relfilenames() {
     108              :         // we accept 0 for the relfilenode, but PostgreSQL should never do that.
     109            1 :         assert_eq!(parse_relfilename("0"), Ok((0, 0, 0)));
     110              : 
     111              :         // PostgreSQL has a limit of 2^32-2 blocks in a table. With 8k block size and
     112              :         // 1 GB segments, the max segment number is 32767. But we accept larger values
     113              :         // currently.
     114            1 :         assert_eq!(parse_relfilename("1.123456"), Ok((1, 0, 123456)));
     115            1 :     }
     116              : }
        

Generated by: LCOV version 2.1-beta