LCOV - code coverage report
Current view: top level - pageserver/src/tenant/remote_timeline_client - index.rs (source / functions) Coverage Total Hit
Test: f2bfe5dc5ab550768e936d6bc7b94d9b2e2d4cc9.info Lines: 90.9 % 872 793
Test Date: 2025-01-27 20:39:28 Functions: 34.4 % 93 32

            Line data    Source code
       1              : //! In-memory index to track the tenant files on the remote storage.
       2              : //!
       3              : //! Able to restore itself from the storage index parts, that are located in every timeline's remote directory and contain all data about
       4              : //! remote timeline layers and its metadata.
       5              : 
       6              : use std::collections::HashMap;
       7              : 
       8              : use chrono::NaiveDateTime;
       9              : use pageserver_api::models::AuxFilePolicy;
      10              : use serde::{Deserialize, Serialize};
      11              : 
      12              : use super::is_same_remote_layer_path;
      13              : use crate::tenant::metadata::TimelineMetadata;
      14              : use crate::tenant::storage_layer::LayerName;
      15              : use crate::tenant::timeline::import_pgdata;
      16              : use crate::tenant::Generation;
      17              : use pageserver_api::shard::ShardIndex;
      18              : use utils::id::TimelineId;
      19              : use utils::lsn::Lsn;
      20              : 
      21              : /// In-memory representation of an `index_part.json` file
      22              : ///
      23              : /// Contains the data about all files in the timeline, present remotely and its metadata.
      24              : ///
      25              : /// This type needs to be backwards and forwards compatible. When changing the fields,
      26              : /// remember to add a test case for the changed version.
      27         6060 : #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
      28              : pub struct IndexPart {
      29              :     /// Debugging aid describing the version of this type.
      30              :     #[serde(default)]
      31              :     version: usize,
      32              : 
      33              :     #[serde(default)]
      34              :     #[serde(skip_serializing_if = "Option::is_none")]
      35              :     pub deleted_at: Option<NaiveDateTime>,
      36              : 
      37              :     #[serde(default)]
      38              :     #[serde(skip_serializing_if = "Option::is_none")]
      39              :     pub archived_at: Option<NaiveDateTime>,
      40              : 
      41              :     /// This field supports import-from-pgdata ("fast imports" platform feature).
      42              :     /// We don't currently use fast imports, so, this field is None for all production timelines.
      43              :     /// See <https://github.com/neondatabase/neon/pull/9218> for more information.
      44              :     #[serde(default)]
      45              :     #[serde(skip_serializing_if = "Option::is_none")]
      46              :     pub import_pgdata: Option<import_pgdata::index_part_format::Root>,
      47              : 
      48              :     /// Layer filenames and metadata. For an index persisted in remote storage, all layers must
      49              :     /// exist in remote storage.
      50              :     pub layer_metadata: HashMap<LayerName, LayerFileMetadata>,
      51              : 
      52              :     /// Because of the trouble of eyeballing the legacy "metadata" field, we copied the
      53              :     /// "disk_consistent_lsn" out. After version 7 this is no longer needed, but the name cannot be
      54              :     /// reused.
      55              :     pub(super) disk_consistent_lsn: Lsn,
      56              : 
      57              :     // TODO: rename as "metadata" next week, keep the alias = "metadata_bytes", bump version Adding
      58              :     // the "alias = metadata" was forgotten in #7693, so we have to use "rewrite = metadata_bytes"
      59              :     // for backwards compatibility.
      60              :     #[serde(
      61              :         rename = "metadata_bytes",
      62              :         alias = "metadata",
      63              :         with = "crate::tenant::metadata::modern_serde"
      64              :     )]
      65              :     pub metadata: TimelineMetadata,
      66              : 
      67              :     #[serde(default)]
      68              :     pub(crate) lineage: Lineage,
      69              : 
      70              :     #[serde(skip_serializing_if = "Option::is_none", default)]
      71              :     pub(crate) gc_blocking: Option<GcBlocking>,
      72              : 
      73              :     /// Describes the kind of aux files stored in the timeline.
      74              :     ///
      75              :     /// The value is modified during file ingestion when the latest wanted value communicated via tenant config is applied if it is acceptable.
      76              :     /// A V1 setting after V2 files have been committed is not accepted.
      77              :     ///
      78              :     /// None means no aux files have been written to the storage before the point
      79              :     /// when this flag is introduced.
      80              :     ///
      81              :     /// This flag is not used any more as all tenants have been transitioned to the new aux file policy.
      82              :     #[serde(skip_serializing_if = "Option::is_none", default)]
      83              :     pub(crate) last_aux_file_policy: Option<AuxFilePolicy>,
      84              : 
      85              :     #[serde(skip_serializing_if = "Option::is_none", default)]
      86              :     pub(crate) rel_size_migration: Option<RelSizeMigration>,
      87              : 
      88              :     /// The LSN of gc-compaction horizon. Once gc-compaction is finished for all layer files below an LSN, this LSN will be updated.
      89              :     #[serde(skip_serializing_if = "Option::is_none", default)]
      90              :     pub(crate) l2_lsn: Option<Lsn>,
      91              : }
      92              : 
      93            8 : #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
      94              : #[serde(rename_all = "camelCase")]
      95              : pub enum RelSizeMigration {
      96              :     /// The tenant is using the old rel_size format.
      97              :     /// Note that this enum is persisted as `Option<RelSizeMigration>` in the index part, so
      98              :     /// `None` is the same as `Some(RelSizeMigration::Legacy)`.
      99              :     Legacy,
     100              :     /// The tenant is migrating to the new rel_size format. Both old and new rel_size format are
     101              :     /// persisted in the index part. The read path will read both formats and merge them.
     102              :     Migrating,
     103              :     /// The tenant has migrated to the new rel_size format. Only the new rel_size format is persisted
     104              :     /// in the index part, and the read path will not read the old format.
     105              :     Migrated,
     106              : }
     107              : 
     108              : impl IndexPart {
     109              :     /// When adding or modifying any parts of `IndexPart`, increment the version so that it can be
     110              :     /// used to understand later versions.
     111              :     ///
     112              :     /// Version is currently informative only.
     113              :     /// Version history
     114              :     /// - 2: added `deleted_at`
     115              :     /// - 3: no longer deserialize `timeline_layers` (serialized format is the same, but timeline_layers
     116              :     ///      is always generated from the keys of `layer_metadata`)
     117              :     /// - 4: timeline_layers is fully removed.
     118              :     /// - 5: lineage was added
     119              :     /// - 6: last_aux_file_policy is added.
     120              :     /// - 7: metadata_bytes is no longer written, but still read
     121              :     /// - 8: added `archived_at`
     122              :     /// - 9: +gc_blocking
     123              :     /// - 10: +import_pgdata
     124              :     /// - 11: +rel_size_migration
     125              :     /// - 12: +l2_lsn
     126              :     const LATEST_VERSION: usize = 12;
     127              : 
     128              :     // Versions we may see when reading from a bucket.
     129              :     pub const KNOWN_VERSIONS: &'static [usize] = &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
     130              : 
     131              :     pub const FILE_NAME: &'static str = "index_part.json";
     132              : 
     133         1048 :     pub fn empty(metadata: TimelineMetadata) -> Self {
     134         1048 :         IndexPart {
     135         1048 :             version: Self::LATEST_VERSION,
     136         1048 :             layer_metadata: Default::default(),
     137         1048 :             disk_consistent_lsn: metadata.disk_consistent_lsn(),
     138         1048 :             metadata,
     139         1048 :             deleted_at: None,
     140         1048 :             archived_at: None,
     141         1048 :             lineage: Default::default(),
     142         1048 :             gc_blocking: None,
     143         1048 :             last_aux_file_policy: None,
     144         1048 :             import_pgdata: None,
     145         1048 :             rel_size_migration: None,
     146         1048 :             l2_lsn: None,
     147         1048 :         }
     148         1048 :     }
     149              : 
     150            0 :     pub fn version(&self) -> usize {
     151            0 :         self.version
     152            0 :     }
     153              : 
     154              :     /// If you want this under normal operations, read it from self.metadata:
     155              :     /// this method is just for the scrubber to use when validating an index.
     156            0 :     pub fn duplicated_disk_consistent_lsn(&self) -> Lsn {
     157            0 :         self.disk_consistent_lsn
     158            0 :     }
     159              : 
     160           52 :     pub fn from_json_bytes(bytes: &[u8]) -> Result<Self, serde_json::Error> {
     161           52 :         serde_json::from_slice::<IndexPart>(bytes)
     162           52 :     }
     163              : 
     164         2956 :     pub fn to_json_bytes(&self) -> serde_json::Result<Vec<u8>> {
     165         2956 :         serde_json::to_vec(self)
     166         2956 :     }
     167              : 
     168              :     #[cfg(test)]
     169           56 :     pub(crate) fn example() -> Self {
     170           56 :         Self::empty(TimelineMetadata::example())
     171           56 :     }
     172              : 
     173              :     /// Returns true if the index contains a reference to the given layer (i.e. file path).
     174              :     ///
     175              :     /// TODO: there should be a variant of LayerName for the physical remote path that contains
     176              :     /// information about the shard and generation, to avoid passing in metadata.
     177        69218 :     pub fn references(&self, name: &LayerName, metadata: &LayerFileMetadata) -> bool {
     178        69218 :         let Some(index_metadata) = self.layer_metadata.get(name) else {
     179        34379 :             return false;
     180              :         };
     181        34839 :         is_same_remote_layer_path(name, metadata, name, index_metadata)
     182        69218 :     }
     183              : 
     184              :     /// Check for invariants in the index: this is useful when uploading an index to ensure that if
     185              :     /// we encounter a bug, we do not persist buggy metadata.
     186         5400 :     pub(crate) fn validate(&self) -> Result<(), String> {
     187         5400 :         if self.import_pgdata.is_none()
     188         5400 :             && self.metadata.ancestor_timeline().is_none()
     189         4003 :             && self.layer_metadata.is_empty()
     190              :         {
     191              :             // Unless we're in the middle of a raw pgdata import, or this is a child timeline,the index must
     192              :             // always have at least one layer.
     193            0 :             return Err("Index has no ancestor and no layers".to_string());
     194         5400 :         }
     195         5400 : 
     196         5400 :         Ok(())
     197         5400 :     }
     198              : }
     199              : 
     200              : /// Metadata gathered for each of the layer files.
     201              : ///
     202              : /// Fields have to be `Option`s because remote [`IndexPart`]'s can be from different version, which
     203              : /// might have less or more metadata depending if upgrading or rolling back an upgrade.
     204        18968 : #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
     205              : pub struct LayerFileMetadata {
     206              :     pub file_size: u64,
     207              : 
     208              :     #[serde(default = "Generation::none")]
     209              :     #[serde(skip_serializing_if = "Generation::is_none")]
     210              :     pub generation: Generation,
     211              : 
     212              :     #[serde(default = "ShardIndex::unsharded")]
     213              :     #[serde(skip_serializing_if = "ShardIndex::is_unsharded")]
     214              :     pub shard: ShardIndex,
     215              : }
     216              : 
     217              : impl LayerFileMetadata {
     218         5752 :     pub fn new(file_size: u64, generation: Generation, shard: ShardIndex) -> Self {
     219         5752 :         LayerFileMetadata {
     220         5752 :             file_size,
     221         5752 :             generation,
     222         5752 :             shard,
     223         5752 :         }
     224         5752 :     }
     225              : }
     226              : 
     227              : /// Limited history of earlier ancestors.
     228              : ///
     229              : /// A timeline can have more than 1 earlier ancestor, in the rare case that it was repeatedly
     230              : /// reparented by having an later timeline be detached from it's ancestor.
     231           16 : #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, Default)]
     232              : pub(crate) struct Lineage {
     233              :     /// Has the `reparenting_history` been truncated to [`Lineage::REMEMBER_AT_MOST`].
     234              :     #[serde(skip_serializing_if = "is_false", default)]
     235              :     reparenting_history_truncated: bool,
     236              : 
     237              :     /// Earlier ancestors, truncated when [`Self::reparenting_history_truncated`]
     238              :     ///
     239              :     /// These are stored in case we want to support WAL based DR on the timeline. There can be many
     240              :     /// of these and at most one [`Self::original_ancestor`]. There cannot be more reparentings
     241              :     /// after [`Self::original_ancestor`] has been set.
     242              :     #[serde(skip_serializing_if = "Vec::is_empty", default)]
     243              :     reparenting_history: Vec<TimelineId>,
     244              : 
     245              :     /// The ancestor from which this timeline has been detached from and when.
     246              :     ///
     247              :     /// If you are adding support for detaching from a hierarchy, consider changing the ancestry
     248              :     /// into a `Vec<(TimelineId, Lsn)>` to be a path instead.
     249              :     // FIXME: this is insufficient even for path of two timelines for future wal recovery
     250              :     // purposes:
     251              :     //
     252              :     // assuming a "old main" which has received most of the WAL, and has a branch "new main",
     253              :     // starting a bit before "old main" last_record_lsn. the current version works fine,
     254              :     // because we will know to replay wal and branch at the recorded Lsn to do wal recovery.
     255              :     //
     256              :     // then assuming "new main" would similarly receive a branch right before its last_record_lsn,
     257              :     // "new new main". the current implementation would just store ("new main", ancestor_lsn, _)
     258              :     // here. however, we cannot recover from WAL using only that information, we would need the
     259              :     // whole ancestry here:
     260              :     //
     261              :     // ```json
     262              :     // [
     263              :     //   ["old main", ancestor_lsn("new main"), _],
     264              :     //   ["new main", ancestor_lsn("new new main"), _]
     265              :     // ]
     266              :     // ```
     267              :     #[serde(skip_serializing_if = "Option::is_none", default)]
     268              :     original_ancestor: Option<(TimelineId, Lsn, NaiveDateTime)>,
     269              : }
     270              : 
     271        12120 : fn is_false(b: &bool) -> bool {
     272        12120 :     !b
     273        12120 : }
     274              : 
     275              : impl Lineage {
     276              :     const REMEMBER_AT_MOST: usize = 100;
     277              : 
     278            0 :     pub(crate) fn record_previous_ancestor(&mut self, old_ancestor: &TimelineId) -> bool {
     279            0 :         if self.reparenting_history.last() == Some(old_ancestor) {
     280              :             // do not re-record it
     281            0 :             false
     282              :         } else {
     283              :             #[cfg(feature = "testing")]
     284              :             {
     285            0 :                 let existing = self
     286            0 :                     .reparenting_history
     287            0 :                     .iter()
     288            0 :                     .position(|x| x == old_ancestor);
     289            0 :                 assert_eq!(
     290              :                     existing, None,
     291            0 :                     "we cannot reparent onto and off and onto the same timeline twice"
     292              :                 );
     293              :             }
     294            0 :             let drop_oldest = self.reparenting_history.len() + 1 >= Self::REMEMBER_AT_MOST;
     295            0 : 
     296            0 :             self.reparenting_history_truncated |= drop_oldest;
     297            0 :             if drop_oldest {
     298            0 :                 self.reparenting_history.remove(0);
     299            0 :             }
     300            0 :             self.reparenting_history.push(*old_ancestor);
     301            0 :             true
     302              :         }
     303            0 :     }
     304              : 
     305              :     /// Returns true if anything changed.
     306            0 :     pub(crate) fn record_detaching(&mut self, branchpoint: &(TimelineId, Lsn)) -> bool {
     307            0 :         if let Some((id, lsn, _)) = self.original_ancestor {
     308            0 :             assert_eq!(
     309            0 :                 &(id, lsn),
     310              :                 branchpoint,
     311            0 :                 "detaching attempt has to be for the same ancestor we are already detached from"
     312              :             );
     313            0 :             false
     314              :         } else {
     315            0 :             self.original_ancestor =
     316            0 :                 Some((branchpoint.0, branchpoint.1, chrono::Utc::now().naive_utc()));
     317            0 :             true
     318              :         }
     319            0 :     }
     320              : 
     321              :     /// The queried lsn is most likely the basebackup lsn, and this answers question "is it allowed
     322              :     /// to start a read/write primary at this lsn".
     323              :     ///
     324              :     /// Returns true if the Lsn was previously our branch point.
     325            0 :     pub(crate) fn is_previous_ancestor_lsn(&self, lsn: Lsn) -> bool {
     326            0 :         self.original_ancestor
     327            0 :             .is_some_and(|(_, ancestor_lsn, _)| ancestor_lsn == lsn)
     328            0 :     }
     329              : 
     330              :     /// Returns true if the timeline originally had an ancestor, and no longer has one.
     331            0 :     pub(crate) fn is_detached_from_ancestor(&self) -> bool {
     332            0 :         self.original_ancestor.is_some()
     333            0 :     }
     334              : 
     335              :     /// Returns original ancestor timeline id and lsn that this timeline has been detached from.
     336            0 :     pub(crate) fn detached_previous_ancestor(&self) -> Option<(TimelineId, Lsn)> {
     337            0 :         self.original_ancestor.map(|(id, lsn, _)| (id, lsn))
     338            0 :     }
     339              : 
     340            0 :     pub(crate) fn is_reparented(&self) -> bool {
     341            0 :         !self.reparenting_history.is_empty()
     342            0 :     }
     343              : }
     344              : 
     345           32 : #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
     346              : pub(crate) struct GcBlocking {
     347              :     pub(crate) started_at: NaiveDateTime,
     348              :     pub(crate) reasons: enumset::EnumSet<GcBlockingReason>,
     349              : }
     350              : 
     351           16 : #[derive(Debug, enumset::EnumSetType, serde::Serialize, serde::Deserialize)]
     352              : #[enumset(serialize_repr = "list")]
     353              : pub(crate) enum GcBlockingReason {
     354              :     Manual,
     355              :     DetachAncestor,
     356              : }
     357              : 
     358              : impl GcBlocking {
     359            0 :     pub(super) fn started_now_for(reason: GcBlockingReason) -> Self {
     360            0 :         GcBlocking {
     361            0 :             started_at: chrono::Utc::now().naive_utc(),
     362            0 :             reasons: enumset::EnumSet::only(reason),
     363            0 :         }
     364            0 :     }
     365              : 
     366              :     /// Returns true if the given reason is one of the reasons why the gc is blocked.
     367            0 :     pub(crate) fn blocked_by(&self, reason: GcBlockingReason) -> bool {
     368            0 :         self.reasons.contains(reason)
     369            0 :     }
     370              : 
     371              :     /// Returns a version of self with the given reason.
     372            0 :     pub(super) fn with_reason(&self, reason: GcBlockingReason) -> Self {
     373            0 :         assert!(!self.blocked_by(reason));
     374            0 :         let mut reasons = self.reasons;
     375            0 :         reasons.insert(reason);
     376            0 : 
     377            0 :         Self {
     378            0 :             started_at: self.started_at,
     379            0 :             reasons,
     380            0 :         }
     381            0 :     }
     382              : 
     383              :     /// Returns a version of self without the given reason. Assumption is that if
     384              :     /// there are no more reasons, we can unblock the gc by returning `None`.
     385            0 :     pub(super) fn without_reason(&self, reason: GcBlockingReason) -> Option<Self> {
     386            0 :         assert!(self.blocked_by(reason));
     387              : 
     388            0 :         if self.reasons.len() == 1 {
     389            0 :             None
     390              :         } else {
     391            0 :             let mut reasons = self.reasons;
     392            0 :             assert!(reasons.remove(reason));
     393            0 :             assert!(!reasons.is_empty());
     394              : 
     395            0 :             Some(Self {
     396            0 :                 started_at: self.started_at,
     397            0 :                 reasons,
     398            0 :             })
     399              :         }
     400            0 :     }
     401              : }
     402              : 
     403              : #[cfg(test)]
     404              : mod tests {
     405              :     use super::*;
     406              :     use std::str::FromStr;
     407              :     use utils::id::TimelineId;
     408              : 
     409              :     #[test]
     410            4 :     fn v1_indexpart_is_parsed() {
     411            4 :         let example = r#"{
     412            4 :             "version":1,
     413            4 :             "timeline_layers":["000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9"],
     414            4 :             "layer_metadata":{
     415            4 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9": { "file_size": 25600000 },
     416            4 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51": { "file_size": 9007199254741001 }
     417            4 :             },
     418            4 :             "disk_consistent_lsn":"0/16960E8",
     419            4 :             "metadata_bytes":[113,11,159,210,0,54,0,4,0,0,0,0,1,105,96,232,1,0,0,0,0,1,105,96,112,0,0,0,0,0,0,0,0,0,0,0,0,0,1,105,96,112,0,0,0,0,1,105,96,112,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
     420            4 :         }"#;
     421            4 : 
     422            4 :         let expected = IndexPart {
     423            4 :             // note this is not verified, could be anything, but exists for humans debugging.. could be the git version instead?
     424            4 :             version: 1,
     425            4 :             layer_metadata: HashMap::from([
     426            4 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9".parse().unwrap(), LayerFileMetadata {
     427            4 :                     file_size: 25600000,
     428            4 :                     generation: Generation::none(),
     429            4 :                     shard: ShardIndex::unsharded()
     430            4 :                 }),
     431            4 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51".parse().unwrap(), LayerFileMetadata {
     432            4 :                     // serde_json should always parse this but this might be a double with jq for
     433            4 :                     // example.
     434            4 :                     file_size: 9007199254741001,
     435            4 :                     generation: Generation::none(),
     436            4 :                     shard: ShardIndex::unsharded()
     437            4 :                 })
     438            4 :             ]),
     439            4 :             disk_consistent_lsn: "0/16960E8".parse::<Lsn>().unwrap(),
     440            4 :             metadata: TimelineMetadata::from_bytes(&[113,11,159,210,0,54,0,4,0,0,0,0,1,105,96,232,1,0,0,0,0,1,105,96,112,0,0,0,0,0,0,0,0,0,0,0,0,0,1,105,96,112,0,0,0,0,1,105,96,112,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]).unwrap(),
     441            4 :             deleted_at: None,
     442            4 :             archived_at: None,
     443            4 :             lineage: Lineage::default(),
     444            4 :             gc_blocking: None,
     445            4 :             last_aux_file_policy: None,
     446            4 :             import_pgdata: None,
     447            4 :             rel_size_migration: None,
     448            4 :             l2_lsn: None,
     449            4 :         };
     450            4 : 
     451            4 :         let part = IndexPart::from_json_bytes(example.as_bytes()).unwrap();
     452            4 :         assert_eq!(part, expected);
     453            4 :     }
     454              : 
     455              :     #[test]
     456            4 :     fn v1_indexpart_is_parsed_with_optional_missing_layers() {
     457            4 :         let example = r#"{
     458            4 :             "version":1,
     459            4 :             "timeline_layers":["000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9"],
     460            4 :             "missing_layers":["This shouldn't fail deserialization"],
     461            4 :             "layer_metadata":{
     462            4 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9": { "file_size": 25600000 },
     463            4 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51": { "file_size": 9007199254741001 }
     464            4 :             },
     465            4 :             "disk_consistent_lsn":"0/16960E8",
     466            4 :             "metadata_bytes":[113,11,159,210,0,54,0,4,0,0,0,0,1,105,96,232,1,0,0,0,0,1,105,96,112,0,0,0,0,0,0,0,0,0,0,0,0,0,1,105,96,112,0,0,0,0,1,105,96,112,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
     467            4 :         }"#;
     468            4 : 
     469            4 :         let expected = IndexPart {
     470            4 :             // note this is not verified, could be anything, but exists for humans debugging.. could be the git version instead?
     471            4 :             version: 1,
     472            4 :             layer_metadata: HashMap::from([
     473            4 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9".parse().unwrap(), LayerFileMetadata {
     474            4 :                     file_size: 25600000,
     475            4 :                     generation: Generation::none(),
     476            4 :                     shard: ShardIndex::unsharded()
     477            4 :                 }),
     478            4 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51".parse().unwrap(), LayerFileMetadata {
     479            4 :                     // serde_json should always parse this but this might be a double with jq for
     480            4 :                     // example.
     481            4 :                     file_size: 9007199254741001,
     482            4 :                     generation: Generation::none(),
     483            4 :                     shard: ShardIndex::unsharded()
     484            4 :                 })
     485            4 :             ]),
     486            4 :             disk_consistent_lsn: "0/16960E8".parse::<Lsn>().unwrap(),
     487            4 :             metadata: TimelineMetadata::from_bytes(&[113,11,159,210,0,54,0,4,0,0,0,0,1,105,96,232,1,0,0,0,0,1,105,96,112,0,0,0,0,0,0,0,0,0,0,0,0,0,1,105,96,112,0,0,0,0,1,105,96,112,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]).unwrap(),
     488            4 :             deleted_at: None,
     489            4 :             archived_at: None,
     490            4 :             lineage: Lineage::default(),
     491            4 :             gc_blocking: None,
     492            4 :             last_aux_file_policy: None,
     493            4 :             import_pgdata: None,
     494            4 :             rel_size_migration: None,
     495            4 :             l2_lsn: None,
     496            4 :         };
     497            4 : 
     498            4 :         let part = IndexPart::from_json_bytes(example.as_bytes()).unwrap();
     499            4 :         assert_eq!(part, expected);
     500            4 :     }
     501              : 
     502              :     #[test]
     503            4 :     fn v2_indexpart_is_parsed_with_deleted_at() {
     504            4 :         let example = r#"{
     505            4 :             "version":2,
     506            4 :             "timeline_layers":["000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9"],
     507            4 :             "missing_layers":["This shouldn't fail deserialization"],
     508            4 :             "layer_metadata":{
     509            4 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9": { "file_size": 25600000 },
     510            4 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51": { "file_size": 9007199254741001 }
     511            4 :             },
     512            4 :             "disk_consistent_lsn":"0/16960E8",
     513            4 :             "metadata_bytes":[113,11,159,210,0,54,0,4,0,0,0,0,1,105,96,232,1,0,0,0,0,1,105,96,112,0,0,0,0,0,0,0,0,0,0,0,0,0,1,105,96,112,0,0,0,0,1,105,96,112,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
     514            4 :             "deleted_at": "2023-07-31T09:00:00.123"
     515            4 :         }"#;
     516            4 : 
     517            4 :         let expected = IndexPart {
     518            4 :             // note this is not verified, could be anything, but exists for humans debugging.. could be the git version instead?
     519            4 :             version: 2,
     520            4 :             layer_metadata: HashMap::from([
     521            4 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9".parse().unwrap(), LayerFileMetadata {
     522            4 :                     file_size: 25600000,
     523            4 :                     generation: Generation::none(),
     524            4 :                     shard: ShardIndex::unsharded()
     525            4 :                 }),
     526            4 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51".parse().unwrap(), LayerFileMetadata {
     527            4 :                     // serde_json should always parse this but this might be a double with jq for
     528            4 :                     // example.
     529            4 :                     file_size: 9007199254741001,
     530            4 :                     generation: Generation::none(),
     531            4 :                     shard: ShardIndex::unsharded()
     532            4 :                 })
     533            4 :             ]),
     534            4 :             disk_consistent_lsn: "0/16960E8".parse::<Lsn>().unwrap(),
     535            4 :             metadata: TimelineMetadata::from_bytes(&[113,11,159,210,0,54,0,4,0,0,0,0,1,105,96,232,1,0,0,0,0,1,105,96,112,0,0,0,0,0,0,0,0,0,0,0,0,0,1,105,96,112,0,0,0,0,1,105,96,112,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]).unwrap(),
     536            4 :             deleted_at: Some(parse_naive_datetime("2023-07-31T09:00:00.123000000")),
     537            4 :             archived_at: None,
     538            4 :             lineage: Lineage::default(),
     539            4 :             gc_blocking: None,
     540            4 :             last_aux_file_policy: None,
     541            4 :             import_pgdata: None,
     542            4 :             rel_size_migration: None,
     543            4 :             l2_lsn: None,
     544            4 :         };
     545            4 : 
     546            4 :         let part = IndexPart::from_json_bytes(example.as_bytes()).unwrap();
     547            4 :         assert_eq!(part, expected);
     548            4 :     }
     549              : 
     550              :     #[test]
     551            4 :     fn empty_layers_are_parsed() {
     552            4 :         let empty_layers_json = r#"{
     553            4 :             "version":1,
     554            4 :             "timeline_layers":[],
     555            4 :             "layer_metadata":{},
     556            4 :             "disk_consistent_lsn":"0/2532648",
     557            4 :             "metadata_bytes":[136,151,49,208,0,70,0,4,0,0,0,0,2,83,38,72,1,0,0,0,0,2,83,38,32,1,87,198,240,135,97,119,45,125,38,29,155,161,140,141,255,210,0,0,0,0,2,83,38,72,0,0,0,0,1,73,240,192,0,0,0,0,1,73,240,192,0,0,0,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
     558            4 :         }"#;
     559            4 : 
     560            4 :         let expected = IndexPart {
     561            4 :             version: 1,
     562            4 :             layer_metadata: HashMap::new(),
     563            4 :             disk_consistent_lsn: "0/2532648".parse::<Lsn>().unwrap(),
     564            4 :             metadata: TimelineMetadata::from_bytes(&[
     565            4 :                 136, 151, 49, 208, 0, 70, 0, 4, 0, 0, 0, 0, 2, 83, 38, 72, 1, 0, 0, 0, 0, 2, 83,
     566            4 :                 38, 32, 1, 87, 198, 240, 135, 97, 119, 45, 125, 38, 29, 155, 161, 140, 141, 255,
     567            4 :                 210, 0, 0, 0, 0, 2, 83, 38, 72, 0, 0, 0, 0, 1, 73, 240, 192, 0, 0, 0, 0, 1, 73,
     568            4 :                 240, 192, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     569            4 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     570            4 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     571            4 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     572            4 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     573            4 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     574            4 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     575            4 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     576            4 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     577            4 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     578            4 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     579            4 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     580            4 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     581            4 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     582            4 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     583            4 :                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
     584            4 :                 0, 0,
     585            4 :             ])
     586            4 :             .unwrap(),
     587            4 :             deleted_at: None,
     588            4 :             archived_at: None,
     589            4 :             lineage: Lineage::default(),
     590            4 :             gc_blocking: None,
     591            4 :             last_aux_file_policy: None,
     592            4 :             import_pgdata: None,
     593            4 :             rel_size_migration: None,
     594            4 :             l2_lsn: None,
     595            4 :         };
     596            4 : 
     597            4 :         let empty_layers_parsed = IndexPart::from_json_bytes(empty_layers_json.as_bytes()).unwrap();
     598            4 : 
     599            4 :         assert_eq!(empty_layers_parsed, expected);
     600            4 :     }
     601              : 
     602              :     #[test]
     603            4 :     fn v4_indexpart_is_parsed() {
     604            4 :         let example = r#"{
     605            4 :             "version":4,
     606            4 :             "layer_metadata":{
     607            4 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9": { "file_size": 25600000 },
     608            4 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51": { "file_size": 9007199254741001 }
     609            4 :             },
     610            4 :             "disk_consistent_lsn":"0/16960E8",
     611            4 :             "metadata_bytes":[113,11,159,210,0,54,0,4,0,0,0,0,1,105,96,232,1,0,0,0,0,1,105,96,112,0,0,0,0,0,0,0,0,0,0,0,0,0,1,105,96,112,0,0,0,0,1,105,96,112,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
     612            4 :             "deleted_at": "2023-07-31T09:00:00.123"
     613            4 :         }"#;
     614            4 : 
     615            4 :         let expected = IndexPart {
     616            4 :             version: 4,
     617            4 :             layer_metadata: HashMap::from([
     618            4 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9".parse().unwrap(), LayerFileMetadata {
     619            4 :                     file_size: 25600000,
     620            4 :                     generation: Generation::none(),
     621            4 :                     shard: ShardIndex::unsharded()
     622            4 :                 }),
     623            4 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51".parse().unwrap(), LayerFileMetadata {
     624            4 :                     // serde_json should always parse this but this might be a double with jq for
     625            4 :                     // example.
     626            4 :                     file_size: 9007199254741001,
     627            4 :                     generation: Generation::none(),
     628            4 :                     shard: ShardIndex::unsharded()
     629            4 :                 })
     630            4 :             ]),
     631            4 :             disk_consistent_lsn: "0/16960E8".parse::<Lsn>().unwrap(),
     632            4 :             metadata: TimelineMetadata::from_bytes(&[113,11,159,210,0,54,0,4,0,0,0,0,1,105,96,232,1,0,0,0,0,1,105,96,112,0,0,0,0,0,0,0,0,0,0,0,0,0,1,105,96,112,0,0,0,0,1,105,96,112,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]).unwrap(),
     633            4 :             deleted_at: Some(parse_naive_datetime("2023-07-31T09:00:00.123000000")),
     634            4 :             archived_at: None,
     635            4 :             lineage: Lineage::default(),
     636            4 :             gc_blocking: None,
     637            4 :             last_aux_file_policy: None,
     638            4 :             import_pgdata: None,
     639            4 :             rel_size_migration: None,
     640            4 :             l2_lsn: None,
     641            4 :         };
     642            4 : 
     643            4 :         let part = IndexPart::from_json_bytes(example.as_bytes()).unwrap();
     644            4 :         assert_eq!(part, expected);
     645            4 :     }
     646              : 
     647              :     #[test]
     648            4 :     fn v5_indexpart_is_parsed() {
     649            4 :         let example = r#"{
     650            4 :             "version":5,
     651            4 :             "layer_metadata":{
     652            4 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000014EF420-00000000014EF499":{"file_size":23289856,"generation":1},
     653            4 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000014EF499-00000000015A7619":{"file_size":1015808,"generation":1}},
     654            4 :                 "disk_consistent_lsn":"0/15A7618",
     655            4 :                 "metadata_bytes":[226,88,25,241,0,46,0,4,0,0,0,0,1,90,118,24,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,78,244,32,0,0,0,0,1,78,244,32,0,0,0,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
     656            4 :                 "lineage":{
     657            4 :                     "original_ancestor":["e2bfd8c633d713d279e6fcd2bcc15b6d","0/15A7618","2024-05-07T18:52:36.322426563"],
     658            4 :                     "reparenting_history":["e1bfd8c633d713d279e6fcd2bcc15b6d"]
     659            4 :                 }
     660            4 :         }"#;
     661            4 : 
     662            4 :         let expected = IndexPart {
     663            4 :             version: 5,
     664            4 :             layer_metadata: HashMap::from([
     665            4 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000014EF420-00000000014EF499".parse().unwrap(), LayerFileMetadata {
     666            4 :                     file_size: 23289856,
     667            4 :                     generation: Generation::new(1),
     668            4 :                     shard: ShardIndex::unsharded(),
     669            4 :                 }),
     670            4 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000014EF499-00000000015A7619".parse().unwrap(), LayerFileMetadata {
     671            4 :                     file_size: 1015808,
     672            4 :                     generation: Generation::new(1),
     673            4 :                     shard: ShardIndex::unsharded(),
     674            4 :                 })
     675            4 :             ]),
     676            4 :             disk_consistent_lsn: Lsn::from_str("0/15A7618").unwrap(),
     677            4 :             metadata: TimelineMetadata::from_bytes(&[226,88,25,241,0,46,0,4,0,0,0,0,1,90,118,24,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,78,244,32,0,0,0,0,1,78,244,32,0,0,0,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]).unwrap(),
     678            4 :             deleted_at: None,
     679            4 :             archived_at: None,
     680            4 :             lineage: Lineage {
     681            4 :                 reparenting_history_truncated: false,
     682            4 :                 reparenting_history: vec![TimelineId::from_str("e1bfd8c633d713d279e6fcd2bcc15b6d").unwrap()],
     683            4 :                 original_ancestor: Some((TimelineId::from_str("e2bfd8c633d713d279e6fcd2bcc15b6d").unwrap(), Lsn::from_str("0/15A7618").unwrap(), parse_naive_datetime("2024-05-07T18:52:36.322426563"))),
     684            4 :             },
     685            4 :             gc_blocking: None,
     686            4 :             last_aux_file_policy: None,
     687            4 :             import_pgdata: None,
     688            4 :             rel_size_migration: None,
     689            4 :             l2_lsn: None,
     690            4 :         };
     691            4 : 
     692            4 :         let part = IndexPart::from_json_bytes(example.as_bytes()).unwrap();
     693            4 :         assert_eq!(part, expected);
     694            4 :     }
     695              : 
     696              :     #[test]
     697            4 :     fn v6_indexpart_is_parsed() {
     698            4 :         let example = r#"{
     699            4 :             "version":6,
     700            4 :             "layer_metadata":{
     701            4 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9": { "file_size": 25600000 },
     702            4 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51": { "file_size": 9007199254741001 }
     703            4 :             },
     704            4 :             "disk_consistent_lsn":"0/16960E8",
     705            4 :             "metadata_bytes":[113,11,159,210,0,54,0,4,0,0,0,0,1,105,96,232,1,0,0,0,0,1,105,96,112,0,0,0,0,0,0,0,0,0,0,0,0,0,1,105,96,112,0,0,0,0,1,105,96,112,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
     706            4 :             "deleted_at": "2023-07-31T09:00:00.123",
     707            4 :             "lineage":{
     708            4 :                 "original_ancestor":["e2bfd8c633d713d279e6fcd2bcc15b6d","0/15A7618","2024-05-07T18:52:36.322426563"],
     709            4 :                 "reparenting_history":["e1bfd8c633d713d279e6fcd2bcc15b6d"]
     710            4 :             },
     711            4 :             "last_aux_file_policy": "V2"
     712            4 :         }"#;
     713            4 : 
     714            4 :         let expected = IndexPart {
     715            4 :             version: 6,
     716            4 :             layer_metadata: HashMap::from([
     717            4 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9".parse().unwrap(), LayerFileMetadata {
     718            4 :                     file_size: 25600000,
     719            4 :                     generation: Generation::none(),
     720            4 :                     shard: ShardIndex::unsharded()
     721            4 :                 }),
     722            4 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51".parse().unwrap(), LayerFileMetadata {
     723            4 :                     // serde_json should always parse this but this might be a double with jq for
     724            4 :                     // example.
     725            4 :                     file_size: 9007199254741001,
     726            4 :                     generation: Generation::none(),
     727            4 :                     shard: ShardIndex::unsharded()
     728            4 :                 })
     729            4 :             ]),
     730            4 :             disk_consistent_lsn: "0/16960E8".parse::<Lsn>().unwrap(),
     731            4 :             metadata: TimelineMetadata::from_bytes(&[113,11,159,210,0,54,0,4,0,0,0,0,1,105,96,232,1,0,0,0,0,1,105,96,112,0,0,0,0,0,0,0,0,0,0,0,0,0,1,105,96,112,0,0,0,0,1,105,96,112,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]).unwrap(),
     732            4 :             deleted_at: Some(parse_naive_datetime("2023-07-31T09:00:00.123000000")),
     733            4 :             archived_at: None,
     734            4 :             lineage: Lineage {
     735            4 :                 reparenting_history_truncated: false,
     736            4 :                 reparenting_history: vec![TimelineId::from_str("e1bfd8c633d713d279e6fcd2bcc15b6d").unwrap()],
     737            4 :                 original_ancestor: Some((TimelineId::from_str("e2bfd8c633d713d279e6fcd2bcc15b6d").unwrap(), Lsn::from_str("0/15A7618").unwrap(), parse_naive_datetime("2024-05-07T18:52:36.322426563"))),
     738            4 :             },
     739            4 :             gc_blocking: None,
     740            4 :             last_aux_file_policy: Some(AuxFilePolicy::V2),
     741            4 :             import_pgdata: None,
     742            4 :             rel_size_migration: None,
     743            4 :             l2_lsn: None,
     744            4 :         };
     745            4 : 
     746            4 :         let part = IndexPart::from_json_bytes(example.as_bytes()).unwrap();
     747            4 :         assert_eq!(part, expected);
     748            4 :     }
     749              : 
     750              :     #[test]
     751            4 :     fn v7_indexpart_is_parsed() {
     752            4 :         let example = r#"{
     753            4 :             "version": 7,
     754            4 :             "layer_metadata":{
     755            4 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9": { "file_size": 25600000 },
     756            4 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51": { "file_size": 9007199254741001 }
     757            4 :             },
     758            4 :             "disk_consistent_lsn":"0/16960E8",
     759            4 :             "metadata": {
     760            4 :                 "disk_consistent_lsn": "0/16960E8",
     761            4 :                 "prev_record_lsn": "0/1696070",
     762            4 :                 "ancestor_timeline": "e45a7f37d3ee2ff17dc14bf4f4e3f52e",
     763            4 :                 "ancestor_lsn": "0/0",
     764            4 :                 "latest_gc_cutoff_lsn": "0/1696070",
     765            4 :                 "initdb_lsn": "0/1696070",
     766            4 :                 "pg_version": 14
     767            4 :             },
     768            4 :             "deleted_at": "2023-07-31T09:00:00.123"
     769            4 :         }"#;
     770            4 : 
     771            4 :         let expected = IndexPart {
     772            4 :             version: 7,
     773            4 :             layer_metadata: HashMap::from([
     774            4 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9".parse().unwrap(), LayerFileMetadata {
     775            4 :                     file_size: 25600000,
     776            4 :                     generation: Generation::none(),
     777            4 :                     shard: ShardIndex::unsharded()
     778            4 :                 }),
     779            4 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51".parse().unwrap(), LayerFileMetadata {
     780            4 :                     file_size: 9007199254741001,
     781            4 :                     generation: Generation::none(),
     782            4 :                     shard: ShardIndex::unsharded()
     783            4 :                 })
     784            4 :             ]),
     785            4 :             disk_consistent_lsn: "0/16960E8".parse::<Lsn>().unwrap(),
     786            4 :             metadata: TimelineMetadata::new(
     787            4 :                 Lsn::from_str("0/16960E8").unwrap(),
     788            4 :                 Some(Lsn::from_str("0/1696070").unwrap()),
     789            4 :                 Some(TimelineId::from_str("e45a7f37d3ee2ff17dc14bf4f4e3f52e").unwrap()),
     790            4 :                 Lsn::INVALID,
     791            4 :                 Lsn::from_str("0/1696070").unwrap(),
     792            4 :                 Lsn::from_str("0/1696070").unwrap(),
     793            4 :                 14,
     794            4 :             ).with_recalculated_checksum().unwrap(),
     795            4 :             deleted_at: Some(parse_naive_datetime("2023-07-31T09:00:00.123000000")),
     796            4 :             archived_at: None,
     797            4 :             lineage: Default::default(),
     798            4 :             gc_blocking: None,
     799            4 :             last_aux_file_policy: Default::default(),
     800            4 :             import_pgdata: None,
     801            4 :             rel_size_migration: None,
     802            4 :             l2_lsn: None,
     803            4 :         };
     804            4 : 
     805            4 :         let part = IndexPart::from_json_bytes(example.as_bytes()).unwrap();
     806            4 :         assert_eq!(part, expected);
     807            4 :     }
     808              : 
     809              :     #[test]
     810            4 :     fn v8_indexpart_is_parsed() {
     811            4 :         let example = r#"{
     812            4 :             "version": 8,
     813            4 :             "layer_metadata":{
     814            4 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9": { "file_size": 25600000 },
     815            4 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51": { "file_size": 9007199254741001 }
     816            4 :             },
     817            4 :             "disk_consistent_lsn":"0/16960E8",
     818            4 :             "metadata": {
     819            4 :                 "disk_consistent_lsn": "0/16960E8",
     820            4 :                 "prev_record_lsn": "0/1696070",
     821            4 :                 "ancestor_timeline": "e45a7f37d3ee2ff17dc14bf4f4e3f52e",
     822            4 :                 "ancestor_lsn": "0/0",
     823            4 :                 "latest_gc_cutoff_lsn": "0/1696070",
     824            4 :                 "initdb_lsn": "0/1696070",
     825            4 :                 "pg_version": 14
     826            4 :             },
     827            4 :             "deleted_at": "2023-07-31T09:00:00.123",
     828            4 :             "archived_at": "2023-04-29T09:00:00.123"
     829            4 :         }"#;
     830            4 : 
     831            4 :         let expected = IndexPart {
     832            4 :             version: 8,
     833            4 :             layer_metadata: HashMap::from([
     834            4 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9".parse().unwrap(), LayerFileMetadata {
     835            4 :                     file_size: 25600000,
     836            4 :                     generation: Generation::none(),
     837            4 :                     shard: ShardIndex::unsharded()
     838            4 :                 }),
     839            4 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51".parse().unwrap(), LayerFileMetadata {
     840            4 :                     file_size: 9007199254741001,
     841            4 :                     generation: Generation::none(),
     842            4 :                     shard: ShardIndex::unsharded()
     843            4 :                 })
     844            4 :             ]),
     845            4 :             disk_consistent_lsn: "0/16960E8".parse::<Lsn>().unwrap(),
     846            4 :             metadata: TimelineMetadata::new(
     847            4 :                 Lsn::from_str("0/16960E8").unwrap(),
     848            4 :                 Some(Lsn::from_str("0/1696070").unwrap()),
     849            4 :                 Some(TimelineId::from_str("e45a7f37d3ee2ff17dc14bf4f4e3f52e").unwrap()),
     850            4 :                 Lsn::INVALID,
     851            4 :                 Lsn::from_str("0/1696070").unwrap(),
     852            4 :                 Lsn::from_str("0/1696070").unwrap(),
     853            4 :                 14,
     854            4 :             ).with_recalculated_checksum().unwrap(),
     855            4 :             deleted_at: Some(parse_naive_datetime("2023-07-31T09:00:00.123000000")),
     856            4 :             archived_at: Some(parse_naive_datetime("2023-04-29T09:00:00.123000000")),
     857            4 :             lineage: Default::default(),
     858            4 :             gc_blocking: None,
     859            4 :             last_aux_file_policy: Default::default(),
     860            4 :             import_pgdata: None,
     861            4 :             rel_size_migration: None,
     862            4 :             l2_lsn: None,
     863            4 :         };
     864            4 : 
     865            4 :         let part = IndexPart::from_json_bytes(example.as_bytes()).unwrap();
     866            4 :         assert_eq!(part, expected);
     867            4 :     }
     868              : 
     869              :     #[test]
     870            4 :     fn v9_indexpart_is_parsed() {
     871            4 :         let example = r#"{
     872            4 :             "version": 9,
     873            4 :             "layer_metadata":{
     874            4 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9": { "file_size": 25600000 },
     875            4 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51": { "file_size": 9007199254741001 }
     876            4 :             },
     877            4 :             "disk_consistent_lsn":"0/16960E8",
     878            4 :             "metadata": {
     879            4 :                 "disk_consistent_lsn": "0/16960E8",
     880            4 :                 "prev_record_lsn": "0/1696070",
     881            4 :                 "ancestor_timeline": "e45a7f37d3ee2ff17dc14bf4f4e3f52e",
     882            4 :                 "ancestor_lsn": "0/0",
     883            4 :                 "latest_gc_cutoff_lsn": "0/1696070",
     884            4 :                 "initdb_lsn": "0/1696070",
     885            4 :                 "pg_version": 14
     886            4 :             },
     887            4 :             "gc_blocking": {
     888            4 :                 "started_at": "2024-07-19T09:00:00.123",
     889            4 :                 "reasons": ["DetachAncestor"]
     890            4 :             }
     891            4 :         }"#;
     892            4 : 
     893            4 :         let expected = IndexPart {
     894            4 :             version: 9,
     895            4 :             layer_metadata: HashMap::from([
     896            4 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9".parse().unwrap(), LayerFileMetadata {
     897            4 :                     file_size: 25600000,
     898            4 :                     generation: Generation::none(),
     899            4 :                     shard: ShardIndex::unsharded()
     900            4 :                 }),
     901            4 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51".parse().unwrap(), LayerFileMetadata {
     902            4 :                     file_size: 9007199254741001,
     903            4 :                     generation: Generation::none(),
     904            4 :                     shard: ShardIndex::unsharded()
     905            4 :                 })
     906            4 :             ]),
     907            4 :             disk_consistent_lsn: "0/16960E8".parse::<Lsn>().unwrap(),
     908            4 :             metadata: TimelineMetadata::new(
     909            4 :                 Lsn::from_str("0/16960E8").unwrap(),
     910            4 :                 Some(Lsn::from_str("0/1696070").unwrap()),
     911            4 :                 Some(TimelineId::from_str("e45a7f37d3ee2ff17dc14bf4f4e3f52e").unwrap()),
     912            4 :                 Lsn::INVALID,
     913            4 :                 Lsn::from_str("0/1696070").unwrap(),
     914            4 :                 Lsn::from_str("0/1696070").unwrap(),
     915            4 :                 14,
     916            4 :             ).with_recalculated_checksum().unwrap(),
     917            4 :             deleted_at: None,
     918            4 :             lineage: Default::default(),
     919            4 :             gc_blocking: Some(GcBlocking {
     920            4 :                 started_at: parse_naive_datetime("2024-07-19T09:00:00.123000000"),
     921            4 :                 reasons: enumset::EnumSet::from_iter([GcBlockingReason::DetachAncestor]),
     922            4 :             }),
     923            4 :             last_aux_file_policy: Default::default(),
     924            4 :             archived_at: None,
     925            4 :             import_pgdata: None,
     926            4 :             rel_size_migration: None,
     927            4 :             l2_lsn: None,
     928            4 :         };
     929            4 : 
     930            4 :         let part = IndexPart::from_json_bytes(example.as_bytes()).unwrap();
     931            4 :         assert_eq!(part, expected);
     932            4 :     }
     933              : 
     934              :     #[test]
     935            4 :     fn v10_importpgdata_is_parsed() {
     936            4 :         let example = r#"{
     937            4 :             "version": 10,
     938            4 :             "layer_metadata":{
     939            4 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9": { "file_size": 25600000 },
     940            4 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51": { "file_size": 9007199254741001 }
     941            4 :             },
     942            4 :             "disk_consistent_lsn":"0/16960E8",
     943            4 :             "metadata": {
     944            4 :                 "disk_consistent_lsn": "0/16960E8",
     945            4 :                 "prev_record_lsn": "0/1696070",
     946            4 :                 "ancestor_timeline": "e45a7f37d3ee2ff17dc14bf4f4e3f52e",
     947            4 :                 "ancestor_lsn": "0/0",
     948            4 :                 "latest_gc_cutoff_lsn": "0/1696070",
     949            4 :                 "initdb_lsn": "0/1696070",
     950            4 :                 "pg_version": 14
     951            4 :             },
     952            4 :             "gc_blocking": {
     953            4 :                 "started_at": "2024-07-19T09:00:00.123",
     954            4 :                 "reasons": ["DetachAncestor"]
     955            4 :             },
     956            4 :             "import_pgdata": {
     957            4 :                 "V1": {
     958            4 :                     "Done": {
     959            4 :                         "idempotency_key": "specified-by-client-218a5213-5044-4562-a28d-d024c5f057f5",
     960            4 :                         "started_at": "2024-11-13T09:23:42.123",
     961            4 :                         "finished_at": "2024-11-13T09:42:23.123"
     962            4 :                     }
     963            4 :                 }
     964            4 :             }
     965            4 :         }"#;
     966            4 : 
     967            4 :         let expected = IndexPart {
     968            4 :             version: 10,
     969            4 :             layer_metadata: HashMap::from([
     970            4 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9".parse().unwrap(), LayerFileMetadata {
     971            4 :                     file_size: 25600000,
     972            4 :                     generation: Generation::none(),
     973            4 :                     shard: ShardIndex::unsharded()
     974            4 :                 }),
     975            4 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51".parse().unwrap(), LayerFileMetadata {
     976            4 :                     file_size: 9007199254741001,
     977            4 :                     generation: Generation::none(),
     978            4 :                     shard: ShardIndex::unsharded()
     979            4 :                 })
     980            4 :             ]),
     981            4 :             disk_consistent_lsn: "0/16960E8".parse::<Lsn>().unwrap(),
     982            4 :             metadata: TimelineMetadata::new(
     983            4 :                 Lsn::from_str("0/16960E8").unwrap(),
     984            4 :                 Some(Lsn::from_str("0/1696070").unwrap()),
     985            4 :                 Some(TimelineId::from_str("e45a7f37d3ee2ff17dc14bf4f4e3f52e").unwrap()),
     986            4 :                 Lsn::INVALID,
     987            4 :                 Lsn::from_str("0/1696070").unwrap(),
     988            4 :                 Lsn::from_str("0/1696070").unwrap(),
     989            4 :                 14,
     990            4 :             ).with_recalculated_checksum().unwrap(),
     991            4 :             deleted_at: None,
     992            4 :             lineage: Default::default(),
     993            4 :             gc_blocking: Some(GcBlocking {
     994            4 :                 started_at: parse_naive_datetime("2024-07-19T09:00:00.123000000"),
     995            4 :                 reasons: enumset::EnumSet::from_iter([GcBlockingReason::DetachAncestor]),
     996            4 :             }),
     997            4 :             last_aux_file_policy: Default::default(),
     998            4 :             archived_at: None,
     999            4 :             import_pgdata: Some(import_pgdata::index_part_format::Root::V1(import_pgdata::index_part_format::V1::Done(import_pgdata::index_part_format::Done{
    1000            4 :                 started_at: parse_naive_datetime("2024-11-13T09:23:42.123000000"),
    1001            4 :                 finished_at: parse_naive_datetime("2024-11-13T09:42:23.123000000"),
    1002            4 :                 idempotency_key: import_pgdata::index_part_format::IdempotencyKey::new("specified-by-client-218a5213-5044-4562-a28d-d024c5f057f5".to_string()),
    1003            4 :             }))),
    1004            4 :             rel_size_migration: None,
    1005            4 :             l2_lsn: None,
    1006            4 :         };
    1007            4 : 
    1008            4 :         let part = IndexPart::from_json_bytes(example.as_bytes()).unwrap();
    1009            4 :         assert_eq!(part, expected);
    1010            4 :     }
    1011              : 
    1012              :     #[test]
    1013            4 :     fn v11_rel_size_migration_is_parsed() {
    1014            4 :         let example = r#"{
    1015            4 :             "version": 11,
    1016            4 :             "layer_metadata":{
    1017            4 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9": { "file_size": 25600000 },
    1018            4 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51": { "file_size": 9007199254741001 }
    1019            4 :             },
    1020            4 :             "disk_consistent_lsn":"0/16960E8",
    1021            4 :             "metadata": {
    1022            4 :                 "disk_consistent_lsn": "0/16960E8",
    1023            4 :                 "prev_record_lsn": "0/1696070",
    1024            4 :                 "ancestor_timeline": "e45a7f37d3ee2ff17dc14bf4f4e3f52e",
    1025            4 :                 "ancestor_lsn": "0/0",
    1026            4 :                 "latest_gc_cutoff_lsn": "0/1696070",
    1027            4 :                 "initdb_lsn": "0/1696070",
    1028            4 :                 "pg_version": 14
    1029            4 :             },
    1030            4 :             "gc_blocking": {
    1031            4 :                 "started_at": "2024-07-19T09:00:00.123",
    1032            4 :                 "reasons": ["DetachAncestor"]
    1033            4 :             },
    1034            4 :             "import_pgdata": {
    1035            4 :                 "V1": {
    1036            4 :                     "Done": {
    1037            4 :                         "idempotency_key": "specified-by-client-218a5213-5044-4562-a28d-d024c5f057f5",
    1038            4 :                         "started_at": "2024-11-13T09:23:42.123",
    1039            4 :                         "finished_at": "2024-11-13T09:42:23.123"
    1040            4 :                     }
    1041            4 :                 }
    1042            4 :             },
    1043            4 :             "rel_size_migration": "legacy"
    1044            4 :         }"#;
    1045            4 : 
    1046            4 :         let expected = IndexPart {
    1047            4 :             version: 11,
    1048            4 :             layer_metadata: HashMap::from([
    1049            4 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9".parse().unwrap(), LayerFileMetadata {
    1050            4 :                     file_size: 25600000,
    1051            4 :                     generation: Generation::none(),
    1052            4 :                     shard: ShardIndex::unsharded()
    1053            4 :                 }),
    1054            4 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51".parse().unwrap(), LayerFileMetadata {
    1055            4 :                     file_size: 9007199254741001,
    1056            4 :                     generation: Generation::none(),
    1057            4 :                     shard: ShardIndex::unsharded()
    1058            4 :                 })
    1059            4 :             ]),
    1060            4 :             disk_consistent_lsn: "0/16960E8".parse::<Lsn>().unwrap(),
    1061            4 :             metadata: TimelineMetadata::new(
    1062            4 :                 Lsn::from_str("0/16960E8").unwrap(),
    1063            4 :                 Some(Lsn::from_str("0/1696070").unwrap()),
    1064            4 :                 Some(TimelineId::from_str("e45a7f37d3ee2ff17dc14bf4f4e3f52e").unwrap()),
    1065            4 :                 Lsn::INVALID,
    1066            4 :                 Lsn::from_str("0/1696070").unwrap(),
    1067            4 :                 Lsn::from_str("0/1696070").unwrap(),
    1068            4 :                 14,
    1069            4 :             ).with_recalculated_checksum().unwrap(),
    1070            4 :             deleted_at: None,
    1071            4 :             lineage: Default::default(),
    1072            4 :             gc_blocking: Some(GcBlocking {
    1073            4 :                 started_at: parse_naive_datetime("2024-07-19T09:00:00.123000000"),
    1074            4 :                 reasons: enumset::EnumSet::from_iter([GcBlockingReason::DetachAncestor]),
    1075            4 :             }),
    1076            4 :             last_aux_file_policy: Default::default(),
    1077            4 :             archived_at: None,
    1078            4 :             import_pgdata: Some(import_pgdata::index_part_format::Root::V1(import_pgdata::index_part_format::V1::Done(import_pgdata::index_part_format::Done{
    1079            4 :                 started_at: parse_naive_datetime("2024-11-13T09:23:42.123000000"),
    1080            4 :                 finished_at: parse_naive_datetime("2024-11-13T09:42:23.123000000"),
    1081            4 :                 idempotency_key: import_pgdata::index_part_format::IdempotencyKey::new("specified-by-client-218a5213-5044-4562-a28d-d024c5f057f5".to_string()),
    1082            4 :             }))),
    1083            4 :             rel_size_migration: Some(RelSizeMigration::Legacy),
    1084            4 :             l2_lsn: None,
    1085            4 :         };
    1086            4 : 
    1087            4 :         let part = IndexPart::from_json_bytes(example.as_bytes()).unwrap();
    1088            4 :         assert_eq!(part, expected);
    1089            4 :     }
    1090              : 
    1091              :     #[test]
    1092            4 :     fn v12_l2_lsn_is_parsed() {
    1093            4 :         let example = r#"{
    1094            4 :             "version": 12,
    1095            4 :             "layer_metadata":{
    1096            4 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9": { "file_size": 25600000 },
    1097            4 :                 "000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51": { "file_size": 9007199254741001 }
    1098            4 :             },
    1099            4 :             "disk_consistent_lsn":"0/16960E8",
    1100            4 :             "metadata": {
    1101            4 :                 "disk_consistent_lsn": "0/16960E8",
    1102            4 :                 "prev_record_lsn": "0/1696070",
    1103            4 :                 "ancestor_timeline": "e45a7f37d3ee2ff17dc14bf4f4e3f52e",
    1104            4 :                 "ancestor_lsn": "0/0",
    1105            4 :                 "latest_gc_cutoff_lsn": "0/1696070",
    1106            4 :                 "initdb_lsn": "0/1696070",
    1107            4 :                 "pg_version": 14
    1108            4 :             },
    1109            4 :             "gc_blocking": {
    1110            4 :                 "started_at": "2024-07-19T09:00:00.123",
    1111            4 :                 "reasons": ["DetachAncestor"]
    1112            4 :             },
    1113            4 :             "import_pgdata": {
    1114            4 :                 "V1": {
    1115            4 :                     "Done": {
    1116            4 :                         "idempotency_key": "specified-by-client-218a5213-5044-4562-a28d-d024c5f057f5",
    1117            4 :                         "started_at": "2024-11-13T09:23:42.123",
    1118            4 :                         "finished_at": "2024-11-13T09:42:23.123"
    1119            4 :                     }
    1120            4 :                 }
    1121            4 :             },
    1122            4 :             "rel_size_migration": "legacy",
    1123            4 :             "l2_lsn": "0/16960E8"
    1124            4 :         }"#;
    1125            4 : 
    1126            4 :         let expected = IndexPart {
    1127            4 :             version: 12,
    1128            4 :             layer_metadata: HashMap::from([
    1129            4 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__0000000001696070-00000000016960E9".parse().unwrap(), LayerFileMetadata {
    1130            4 :                     file_size: 25600000,
    1131            4 :                     generation: Generation::none(),
    1132            4 :                     shard: ShardIndex::unsharded()
    1133            4 :                 }),
    1134            4 :                 ("000000000000000000000000000000000000-FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF__00000000016B59D8-00000000016B5A51".parse().unwrap(), LayerFileMetadata {
    1135            4 :                     file_size: 9007199254741001,
    1136            4 :                     generation: Generation::none(),
    1137            4 :                     shard: ShardIndex::unsharded()
    1138            4 :                 })
    1139            4 :             ]),
    1140            4 :             disk_consistent_lsn: "0/16960E8".parse::<Lsn>().unwrap(),
    1141            4 :             metadata: TimelineMetadata::new(
    1142            4 :                 Lsn::from_str("0/16960E8").unwrap(),
    1143            4 :                 Some(Lsn::from_str("0/1696070").unwrap()),
    1144            4 :                 Some(TimelineId::from_str("e45a7f37d3ee2ff17dc14bf4f4e3f52e").unwrap()),
    1145            4 :                 Lsn::INVALID,
    1146            4 :                 Lsn::from_str("0/1696070").unwrap(),
    1147            4 :                 Lsn::from_str("0/1696070").unwrap(),
    1148            4 :                 14,
    1149            4 :             ).with_recalculated_checksum().unwrap(),
    1150            4 :             deleted_at: None,
    1151            4 :             lineage: Default::default(),
    1152            4 :             gc_blocking: Some(GcBlocking {
    1153            4 :                 started_at: parse_naive_datetime("2024-07-19T09:00:00.123000000"),
    1154            4 :                 reasons: enumset::EnumSet::from_iter([GcBlockingReason::DetachAncestor]),
    1155            4 :             }),
    1156            4 :             last_aux_file_policy: Default::default(),
    1157            4 :             archived_at: None,
    1158            4 :             import_pgdata: Some(import_pgdata::index_part_format::Root::V1(import_pgdata::index_part_format::V1::Done(import_pgdata::index_part_format::Done{
    1159            4 :                 started_at: parse_naive_datetime("2024-11-13T09:23:42.123000000"),
    1160            4 :                 finished_at: parse_naive_datetime("2024-11-13T09:42:23.123000000"),
    1161            4 :                 idempotency_key: import_pgdata::index_part_format::IdempotencyKey::new("specified-by-client-218a5213-5044-4562-a28d-d024c5f057f5".to_string()),
    1162            4 :             }))),
    1163            4 :             rel_size_migration: Some(RelSizeMigration::Legacy),
    1164            4 :             l2_lsn: Some("0/16960E8".parse::<Lsn>().unwrap()),
    1165            4 :         };
    1166            4 : 
    1167            4 :         let part = IndexPart::from_json_bytes(example.as_bytes()).unwrap();
    1168            4 :         assert_eq!(part, expected);
    1169            4 :     }
    1170              : 
    1171           72 :     fn parse_naive_datetime(s: &str) -> NaiveDateTime {
    1172           72 :         chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S.%f").unwrap()
    1173           72 :     }
    1174              : }
        

Generated by: LCOV version 2.1-beta