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