LCOV - differential code coverage report
Current view: top level - libs/postgres_ffi/src - relfile_utils.rs (source / functions) Coverage Total Hit UBC CBC
Current: f6946e90941b557c917ac98cd5a7e9506d180f3e.info Lines: 98.8 % 85 84 1 84
Current Date: 2023-10-19 02:04:12 Functions: 81.2 % 16 13 3 13
Baseline: c8637f37369098875162f194f92736355783b050.info
Baseline Date: 2023-10-18 20:25:20

           TLA  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                 : //
       8                 : // Fork numbers, from relpath.h
       9                 : //
      10                 : pub const MAIN_FORKNUM: u8 = 0;
      11                 : pub const FSM_FORKNUM: u8 = 1;
      12                 : pub const VISIBILITYMAP_FORKNUM: u8 = 2;
      13                 : pub const INIT_FORKNUM: u8 = 3;
      14                 : 
      15 CBC           6 : #[derive(Debug, Clone, thiserror::Error, PartialEq, Eq)]
      16                 : pub enum FilePathError {
      17                 :     #[error("invalid relation fork name")]
      18                 :     InvalidForkName,
      19                 :     #[error("invalid relation data file name")]
      20                 :     InvalidFileName,
      21                 : }
      22                 : 
      23                 : impl From<core::num::ParseIntError> for FilePathError {
      24               1 :     fn from(_e: core::num::ParseIntError) -> Self {
      25               1 :         FilePathError::InvalidFileName
      26               1 :     }
      27                 : }
      28                 : 
      29                 : /// Convert Postgres relation file's fork suffix to fork number.
      30          538142 : pub fn forkname_to_number(forkname: Option<&str>) -> Result<u8, FilePathError> {
      31          538142 :     match forkname {
      32                 :         // "main" is not in filenames, it's implicit if the fork name is not present
      33          399415 :         None => Ok(MAIN_FORKNUM),
      34          138727 :         Some("fsm") => Ok(FSM_FORKNUM),
      35           67631 :         Some("vm") => Ok(VISIBILITYMAP_FORKNUM),
      36               3 :         Some("init") => Ok(INIT_FORKNUM),
      37               1 :         Some(_) => Err(FilePathError::InvalidForkName),
      38                 :     }
      39          538142 : }
      40                 : 
      41                 : /// Convert Postgres fork number to the right suffix of the relation data file.
      42         3787496 : pub fn forknumber_to_name(forknum: u8) -> Option<&'static str> {
      43         3787496 :     match forknum {
      44         3701971 :         MAIN_FORKNUM => None,
      45           67994 :         FSM_FORKNUM => Some("fsm"),
      46           17511 :         VISIBILITYMAP_FORKNUM => Some("vm"),
      47              20 :         INIT_FORKNUM => Some("init"),
      48 UBC           0 :         _ => Some("UNKNOWN FORKNUM"),
      49                 :     }
      50 CBC     3787496 : }
      51                 : 
      52                 : /// Parse a filename of a relation file. Returns (relfilenode, forknum, segno) tuple.
      53                 : ///
      54                 : /// Formats:
      55                 : ///
      56                 : /// ```text
      57                 : /// <oid>
      58                 : /// <oid>_<fork name>
      59                 : /// <oid>.<segment number>
      60                 : /// <oid>_<fork name>.<segment number>
      61                 : /// ```
      62                 : ///
      63                 : /// See functions relpath() and _mdfd_segpath() in PostgreSQL sources.
      64                 : ///
      65          538147 : pub fn parse_relfilename(fname: &str) -> Result<(u32, u8, u32), FilePathError> {
      66          538147 :     static RELFILE_RE: OnceCell<Regex> = OnceCell::new();
      67          538147 :     RELFILE_RE.get_or_init(|| {
      68             354 :         Regex::new(r"^(?P<relnode>\d+)(_(?P<forkname>[a-z]+))?(\.(?P<segno>\d+))?$").unwrap()
      69          538147 :     });
      70                 : 
      71          538147 :     let caps = RELFILE_RE
      72          538147 :         .get()
      73          538147 :         .unwrap()
      74          538147 :         .captures(fname)
      75          538147 :         .ok_or(FilePathError::InvalidFileName)?;
      76                 : 
      77          538143 :     let relnode_str = caps.name("relnode").unwrap().as_str();
      78          538143 :     let relnode = relnode_str.parse::<u32>()?;
      79                 : 
      80          538142 :     let forkname = caps.name("forkname").map(|f| f.as_str());
      81          538142 :     let forknum = forkname_to_number(forkname)?;
      82                 : 
      83          538141 :     let segno_match = caps.name("segno");
      84          538141 :     let segno = if segno_match.is_none() {
      85          538136 :         0
      86                 :     } else {
      87               5 :         segno_match.unwrap().as_str().parse::<u32>()?
      88                 :     };
      89                 : 
      90          538141 :     Ok((relnode, forknum, segno))
      91          538147 : }
      92                 : 
      93                 : #[cfg(test)]
      94                 : mod tests {
      95                 :     use super::*;
      96                 : 
      97               1 :     #[test]
      98               1 :     fn test_parse_valid_relfilenames() {
      99               1 :         assert_eq!(parse_relfilename("1234"), Ok((1234, 0, 0)));
     100               1 :         assert_eq!(parse_relfilename("1234_fsm"), Ok((1234, 1, 0)));
     101               1 :         assert_eq!(parse_relfilename("1234_vm"), Ok((1234, 2, 0)));
     102               1 :         assert_eq!(parse_relfilename("1234_init"), Ok((1234, 3, 0)));
     103                 : 
     104               1 :         assert_eq!(parse_relfilename("1234.12"), Ok((1234, 0, 12)));
     105               1 :         assert_eq!(parse_relfilename("1234_fsm.12"), Ok((1234, 1, 12)));
     106               1 :         assert_eq!(parse_relfilename("1234_vm.12"), Ok((1234, 2, 12)));
     107               1 :         assert_eq!(parse_relfilename("1234_init.12"), Ok((1234, 3, 12)));
     108                 : 
     109                 :         // relfilenode is unsigned, so it can go up to 2^32-1
     110               1 :         assert_eq!(parse_relfilename("3147483648"), Ok((3147483648, 0, 0)));
     111               1 :     }
     112                 : 
     113               1 :     #[test]
     114               1 :     fn test_parse_invalid_relfilenames() {
     115               1 :         assert_eq!(
     116               1 :             parse_relfilename("foo"),
     117               1 :             Err(FilePathError::InvalidFileName)
     118               1 :         );
     119               1 :         assert_eq!(
     120               1 :             parse_relfilename("1.2.3"),
     121               1 :             Err(FilePathError::InvalidFileName)
     122               1 :         );
     123               1 :         assert_eq!(
     124               1 :             parse_relfilename("1234_invalid"),
     125               1 :             Err(FilePathError::InvalidForkName)
     126               1 :         );
     127               1 :         assert_eq!(
     128               1 :             parse_relfilename("1234_"),
     129               1 :             Err(FilePathError::InvalidFileName)
     130               1 :         );
     131                 : 
     132                 :         // too large for u32
     133               1 :         assert_eq!(
     134               1 :             parse_relfilename("12345678901"),
     135               1 :             Err(FilePathError::InvalidFileName)
     136               1 :         );
     137               1 :         assert_eq!(
     138               1 :             parse_relfilename("-1234"),
     139               1 :             Err(FilePathError::InvalidFileName)
     140               1 :         );
     141               1 :     }
     142                 : 
     143               1 :     #[test]
     144               1 :     fn test_parse_weird_relfilenames() {
     145               1 :         // we accept 0 for the relfilenode, but PostgreSQL should never do that.
     146               1 :         assert_eq!(parse_relfilename("0"), Ok((0, 0, 0)));
     147                 : 
     148                 :         // PostgreSQL has a limit of 2^32-2 blocks in a table. With 8k block size and
     149                 :         // 1 GB segments, the max segment number is 32767. But we accept larger values
     150                 :         // currently.
     151               1 :         assert_eq!(parse_relfilename("1.123456"), Ok((1, 0, 123456)));
     152               1 :     }
     153                 : }
        

Generated by: LCOV version 2.1-beta