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

Generated by: LCOV version 2.1-beta