Skip to main content

mz_catalog/durable/upgrade/
v90_to_v91.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10use crate::durable::upgrade::MigrationAction;
11use crate::durable::upgrade::json_compatible::JsonCompatible;
12use crate::durable::upgrade::objects_v90 as v90;
13use crate::durable::upgrade::objects_v91 as v91;
14
15crate::json_compatible!(v90::ItemKey with v91::ItemKey);
16crate::json_compatible!(v90::SchemaId with v91::SchemaId);
17crate::json_compatible!(v90::RoleId with v91::RoleId);
18crate::json_compatible!(v90::MzAclItem with v91::MzAclItem);
19crate::json_compatible!(v90::CatalogItem with v91::CatalogItem);
20crate::json_compatible!(v90::GlobalId with v91::GlobalId);
21crate::json_compatible!(v90::ItemVersion with v91::ItemVersion);
22
23/// Adds the `ephemeral_owner_session` field to items, backfilling it as
24/// `None`. Existing records all describe durable items, temporary items were
25/// never written to the catalog before this field existed.
26///
27/// `Item` records gained a new field, so their stored JSON is no longer
28/// readable as the v91 type and every such record is rewritten. All other
29/// records are unchanged and pass through untouched.
30///
31/// NOTE: The explicit rewrite matters even though serde would default the
32/// missing field to `None` on read. A later edit to an item retracts the
33/// record by writing its v91 encoding (with `ephemeral_owner_session: None`)
34/// at diff -1. Without the backfill, the stored record lacks the field, so
35/// the retraction doesn't match it and the collection is left with negative
36/// multiplicity.
37pub fn upgrade(
38    snapshot: Vec<v90::StateUpdateKind>,
39) -> Vec<MigrationAction<v90::StateUpdateKind, v91::StateUpdateKind>> {
40    let mut migrations = Vec::new();
41    for update in snapshot {
42        match update {
43            v90::StateUpdateKind::Item(old_item) => {
44                let new_item = migrate_item(old_item.clone());
45                migrations.push(MigrationAction::Update(
46                    v90::StateUpdateKind::Item(old_item),
47                    v91::StateUpdateKind::Item(new_item),
48                ));
49            }
50            _ => {}
51        }
52    }
53    migrations
54}
55
56fn migrate_item(old: v90::Item) -> v91::Item {
57    let v90::Item { key, value } = old;
58    v91::Item {
59        key: JsonCompatible::convert(&key),
60        value: v91::ItemValue {
61            schema_id: JsonCompatible::convert(&value.schema_id),
62            name: value.name,
63            definition: JsonCompatible::convert(&value.definition),
64            owner_id: JsonCompatible::convert(&value.owner_id),
65            privileges: value
66                .privileges
67                .iter()
68                .map(JsonCompatible::convert)
69                .collect(),
70            oid: value.oid,
71            global_id: JsonCompatible::convert(&value.global_id),
72            extra_versions: value
73                .extra_versions
74                .iter()
75                .map(JsonCompatible::convert)
76                .collect(),
77            ephemeral_owner_session: None,
78        },
79    }
80}
81
82#[cfg(test)]
83mod tests {
84    use crate::durable::upgrade::MigrationAction;
85    use crate::durable::upgrade::v90_to_v91::upgrade;
86    use crate::durable::upgrade::{objects_v90 as v90, objects_v91 as v91};
87
88    fn schema(id: u64) -> v90::Schema {
89        v90::Schema {
90            key: v90::SchemaKey {
91                id: v90::SchemaId::User(id),
92            },
93            value: v90::SchemaValue {
94                database_id: Some(v90::DatabaseId::User(1)),
95                name: format!("schema{id}"),
96                owner_id: v90::RoleId::User(1),
97                privileges: Vec::new(),
98                oid: 20_000,
99            },
100        }
101    }
102
103    fn item(id: u64) -> v90::Item {
104        v90::Item {
105            key: v90::ItemKey {
106                gid: v90::CatalogItemId::User(id),
107            },
108            value: v90::ItemValue {
109                schema_id: v90::SchemaId::User(1),
110                name: format!("item{id}"),
111                definition: v90::CatalogItem::V1(v90::CatalogItemV1 {
112                    create_sql: "CREATE VIEW v AS SELECT 1".to_string(),
113                }),
114                owner_id: v90::RoleId::User(1),
115                privileges: Vec::new(),
116                oid: 20_001,
117                global_id: v90::GlobalId::User(id),
118                extra_versions: Vec::new(),
119            },
120        }
121    }
122
123    #[mz_ore::test]
124    fn backfills_items_as_none() {
125        let migrations = upgrade(vec![
126            v90::StateUpdateKind::Schema(schema(1)),
127            v90::StateUpdateKind::Item(item(1)),
128        ]);
129        // The item migrates; the schema passes through.
130        assert_eq!(migrations.len(), 1);
131
132        let MigrationAction::Update(_, v91::StateUpdateKind::Item(item)) = &migrations[0] else {
133            panic!("expected an item update");
134        };
135        assert_eq!(item.value.ephemeral_owner_session, None);
136    }
137}