LCOV - code coverage report
Current view: top level - pageserver/src/tenant/remote_timeline_client - index.rs (source / functions) Coverage Total Hit
Test: 2620485e474b48c32427149a5d91ef8fc2cd649e.info Lines: 91.8 % 994 912
Test Date: 2025-05-01 22:50:11 Functions: 34.7 % 95 33

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

Generated by: LCOV version 2.1-beta