Skip to main content

mz_adapter/catalog/
builtin_table_updates.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
10mod notice;
11
12use bytesize::ByteSize;
13use ipnet::IpNet;
14use mz_adapter_types::compaction::CompactionWindow;
15use mz_audit_log::VersionedStorageUsage;
16use mz_catalog::SYSTEM_CONN_ID;
17use mz_catalog::builtin::{
18    BuiltinTable, MZ_AGGREGATES, MZ_ARRAY_TYPES, MZ_BASE_TYPES, MZ_CLUSTER_REPLICA_SIZE_INTERNAL,
19    MZ_CLUSTER_REPLICA_SIZES, MZ_COLUMNS, MZ_EGRESS_IPS, MZ_FUNCTIONS,
20    MZ_HISTORY_RETENTION_STRATEGIES, MZ_INDEX_COLUMNS, MZ_LICENSE_KEYS, MZ_LIST_TYPES,
21    MZ_MAP_TYPES, MZ_MATERIALIZED_VIEW_REFRESH_STRATEGIES, MZ_OBJECT_DEPENDENCIES,
22    MZ_OBJECT_GLOBAL_IDS, MZ_OPERATORS, MZ_PSEUDO_TYPES, MZ_REPLACEMENTS, MZ_ROLE_AUTH,
23    MZ_SESSIONS, MZ_SOURCE_REFERENCES, MZ_STORAGE_USAGE_BY_SHARD, MZ_SUBSCRIPTIONS,
24    MZ_TYPE_PG_METADATA, MZ_TYPES, MZ_WEBHOOKS_SOURCES,
25};
26use mz_catalog::durable::SourceReferences;
27use mz_catalog::memory::error::Error;
28use mz_catalog::memory::objects::{
29    CatalogEntry, CatalogItem, DataSourceDesc, Func, Index, MaterializedView, Table,
30    TableDataSource, Type,
31};
32use mz_expr::MirScalarExpr;
33use mz_license_keys::ValidatedLicenseKey;
34use mz_orchestrator::{CpuLimit, DiskLimit, MemoryLimit};
35use mz_ore::cast::CastFrom;
36use mz_ore::collections::CollectionExt;
37use mz_persist_client::batch::ProtoBatch;
38use mz_repr::adt::array::ArrayDimension;
39use mz_repr::adt::interval::Interval;
40use mz_repr::adt::jsonb::Jsonb;
41use mz_repr::adt::mz_acl_item::PrivilegeMap;
42use mz_repr::refresh_schedule::RefreshEvery;
43use mz_repr::role_id::RoleId;
44use mz_repr::{
45    CatalogItemId, Datum, Diff, GlobalId, ReprColumnType, Row, RowPacker, SqlScalarType, Timestamp,
46};
47use mz_sql::ast::{CreateIndexStatement, Statement};
48use mz_sql::catalog::{CatalogType, TypeCategory};
49use mz_sql::func::FuncImplCatalogDetails;
50use mz_sql::names::SchemaSpecifier;
51use mz_sql_parser::ast::display::AstDisplay;
52use mz_storage_client::client::TableData;
53use smallvec::smallvec;
54
55// DO NOT add any more imports from `crate` outside of `crate::catalog`.
56use crate::active_compute_sink::ActiveSubscribe;
57use crate::catalog::CatalogState;
58use crate::coord::ConnMeta;
59
60/// An update to a built-in table.
61#[derive(Debug, Clone)]
62pub struct BuiltinTableUpdate<T = CatalogItemId> {
63    /// The reference of the table to update.
64    pub id: T,
65    /// The data to put into the table.
66    pub data: TableData,
67}
68
69impl<T> BuiltinTableUpdate<T> {
70    /// Create a [`BuiltinTableUpdate`] from a [`Row`].
71    pub fn row(id: T, row: Row, diff: Diff) -> BuiltinTableUpdate<T> {
72        BuiltinTableUpdate {
73            id,
74            data: TableData::Rows(vec![(row, diff)]),
75        }
76    }
77
78    pub fn batch(id: T, batch: ProtoBatch) -> BuiltinTableUpdate<T> {
79        BuiltinTableUpdate {
80            id,
81            data: TableData::Batches(smallvec![batch]),
82        }
83    }
84}
85
86impl CatalogState {
87    pub fn resolve_builtin_table_updates(
88        &self,
89        builtin_table_update: Vec<BuiltinTableUpdate<&'static BuiltinTable>>,
90    ) -> Vec<BuiltinTableUpdate<CatalogItemId>> {
91        builtin_table_update
92            .into_iter()
93            .map(|builtin_table_update| self.resolve_builtin_table_update(builtin_table_update))
94            .collect()
95    }
96
97    pub fn resolve_builtin_table_update(
98        &self,
99        BuiltinTableUpdate { id, data }: BuiltinTableUpdate<&'static BuiltinTable>,
100    ) -> BuiltinTableUpdate<CatalogItemId> {
101        let id = self.resolve_builtin_table(id);
102        BuiltinTableUpdate { id, data }
103    }
104
105    pub fn pack_depends_update(
106        &self,
107        depender: CatalogItemId,
108        dependee: CatalogItemId,
109        diff: Diff,
110    ) -> BuiltinTableUpdate<&'static BuiltinTable> {
111        let row = Row::pack_slice(&[
112            Datum::String(&depender.to_string()),
113            Datum::String(&dependee.to_string()),
114        ]);
115        BuiltinTableUpdate::row(&*MZ_OBJECT_DEPENDENCIES, row, diff)
116    }
117
118    pub(super) fn pack_role_auth_update(
119        &self,
120        id: RoleId,
121        diff: Diff,
122    ) -> BuiltinTableUpdate<&'static BuiltinTable> {
123        let role_auth = self.get_role_auth(&id);
124        let role = self.get_role(&id);
125        BuiltinTableUpdate::row(
126            &*MZ_ROLE_AUTH,
127            Row::pack_slice(&[
128                Datum::String(&role_auth.role_id.to_string()),
129                Datum::UInt32(role.oid),
130                match &role_auth.password_hash {
131                    Some(hash) => Datum::String(hash),
132                    None => Datum::Null,
133                },
134                Datum::TimestampTz(
135                    mz_ore::now::to_datetime(role_auth.updated_at)
136                        .try_into()
137                        .expect("must fit"),
138                ),
139            ]),
140            diff,
141        )
142    }
143
144    pub(super) fn pack_item_update(
145        &self,
146        id: CatalogItemId,
147        diff: Diff,
148    ) -> Vec<BuiltinTableUpdate<&'static BuiltinTable>> {
149        let entry = self.get_entry(&id);
150        let oid = entry.oid();
151        let conn_id = entry.item().conn_id().unwrap_or(&SYSTEM_CONN_ID);
152        let schema_id = &self
153            .get_schema(
154                &entry.name().qualifiers.database_spec,
155                &entry.name().qualifiers.schema_spec,
156                conn_id,
157            )
158            .id;
159        let name = &entry.name().item;
160        let owner_id = entry.owner_id();
161        let privileges_row = self.pack_privilege_array_row(entry.privileges());
162        let privileges = privileges_row.unpack_first();
163        let mut updates = match entry.item() {
164            CatalogItem::Index(index) => self.pack_index_update(id, index, diff),
165            CatalogItem::Source(source) => {
166                match &source.data_source {
167                    DataSourceDesc::Webhook { .. } => {
168                        vec![self.pack_webhook_source_update(id, diff)]
169                    }
170                    // Old-syntax subsource metadata (mz_postgres/mysql/sql_server_source_tables)
171                    // is now derived from create_sql by materialized views over
172                    // mz_catalog_raw, so ingestion exports need no special packing.
173                    DataSourceDesc::Ingestion { .. }
174                    | DataSourceDesc::OldSyntaxIngestion { .. }
175                    | DataSourceDesc::IngestionExport { .. }
176                    | DataSourceDesc::Introspection(_)
177                    | DataSourceDesc::Progress
178                    | DataSourceDesc::Catalog => vec![],
179                }
180            }
181            CatalogItem::MaterializedView(mview) => {
182                self.pack_materialized_view_update(id, mview, diff)
183            }
184            // mz_sinks, mz_kafka_sinks and mz_iceberg_sinks read create_sql
185            // out of mz_catalog_raw, so there is nothing to pack here.
186            CatalogItem::Sink(_) => vec![],
187            CatalogItem::Type(ty) => {
188                self.pack_type_update(id, oid, schema_id, name, owner_id, privileges, ty, diff)
189            }
190            CatalogItem::Func(func) => {
191                self.pack_func_update(id, schema_id, name, owner_id, func, diff)
192            }
193            // A metric sink packs no builtin-table row, so it holds a catalog name that no
194            // catalog relation reports. SQL-572 adds the `mz_metric_sinks` view. Until then,
195            // listing or dropping a metric sink requires knowing its name out of band.
196            //
197            // NOTE: creating a metric sink takes SELECT on the FROM relation, not ownership
198            // of it, so once metric sinks are user-creatable this gap would let a reader
199            // egress another role's rows with no catalog relation the owner could see it in.
200            // SQL-572 has to land before user-facing metric sink DDL does.
201            CatalogItem::Table(_)
202            | CatalogItem::View(_)
203            | CatalogItem::Log(_)
204            | CatalogItem::Secret(_)
205            | CatalogItem::MetricSink(_) => vec![],
206            // Connection details (mz_kafka_connections, mz_ssh_tunnel_connections,
207            // mz_aws_connections, mz_aws_privatelink_connections) are now derived
208            // from the persisted create_sql by materialized views over
209            // mz_catalog_raw, so connections need no special packing here.
210            CatalogItem::Connection(_) => vec![],
211        };
212
213        if !entry.item().is_temporary() {
214            // Populate or clean up the `mz_object_dependencies` table.
215            // TODO(jkosh44) Unclear if this table wants to include all uses or only references.
216            for dependee in entry.item().references().items() {
217                updates.push(self.pack_depends_update(id, *dependee, diff))
218            }
219        }
220
221        // Always report the latest for an objects columns.
222        if let Some(desc) = entry.relation_desc_latest() {
223            let defaults = match entry.item() {
224                CatalogItem::Table(Table {
225                    data_source: TableDataSource::TableWrites { defaults },
226                    ..
227                }) => Some(defaults),
228                _ => None,
229            };
230            for (i, (column_name, column_type)) in desc.iter().enumerate() {
231                let default: Option<String> = defaults.map(|d| d[i].to_ast_string_stable());
232                let default: Datum = default
233                    .as_ref()
234                    .map(|d| Datum::String(d))
235                    .unwrap_or(Datum::Null);
236                let pgtype = mz_pgrepr::Type::from(&column_type.scalar_type);
237                let (type_name, type_oid) = match &column_type.scalar_type {
238                    SqlScalarType::List {
239                        custom_id: Some(custom_id),
240                        ..
241                    }
242                    | SqlScalarType::Map {
243                        custom_id: Some(custom_id),
244                        ..
245                    }
246                    | SqlScalarType::Record {
247                        custom_id: Some(custom_id),
248                        ..
249                    } => {
250                        let entry = self.get_entry(custom_id);
251                        // NOTE(benesch): the `mz_columns.type text` field is
252                        // wrong. Types do not have a name that can be
253                        // represented as a single textual field. There can be
254                        // multiple types with the same name in different
255                        // schemas and databases. We should eventually deprecate
256                        // the `type` field in favor of a new `type_id` field
257                        // that can be joined against `mz_types`.
258                        //
259                        // For now, in the interest of pragmatism, we just use
260                        // the type's item name, and accept that there may be
261                        // ambiguity if the same type name is used in multiple
262                        // schemas. The ambiguity is mitigated by the OID, which
263                        // can be joined against `mz_types.oid` to resolve the
264                        // ambiguity.
265                        let name = &*entry.name().item;
266                        let oid = entry.oid();
267                        (name, oid)
268                    }
269                    _ => (pgtype.name(), pgtype.oid()),
270                };
271                updates.push(BuiltinTableUpdate::row(
272                    &*MZ_COLUMNS,
273                    Row::pack_slice(&[
274                        Datum::String(&id.to_string()),
275                        Datum::String(column_name),
276                        Datum::UInt64(u64::cast_from(i + 1)),
277                        Datum::from(column_type.nullable),
278                        Datum::String(type_name),
279                        default,
280                        Datum::UInt32(type_oid),
281                        Datum::Int32(pgtype.typmod()),
282                    ]),
283                    diff,
284                ));
285            }
286        }
287
288        // Use initial lcw so that we can tell apart default from non-existent windows.
289        if let Some(cw) = entry.item().initial_logical_compaction_window() {
290            updates.push(self.pack_history_retention_strategy_update(id, cw, diff));
291        }
292
293        updates.extend(Self::pack_item_global_id_update(entry, diff));
294
295        updates
296    }
297
298    fn pack_item_global_id_update(
299        entry: &CatalogEntry,
300        diff: Diff,
301    ) -> impl Iterator<Item = BuiltinTableUpdate<&'static BuiltinTable>> + use<'_> {
302        let id = entry.id().to_string();
303        let global_ids = entry.global_ids();
304        global_ids.map(move |global_id| {
305            BuiltinTableUpdate::row(
306                &*MZ_OBJECT_GLOBAL_IDS,
307                Row::pack_slice(&[Datum::String(&id), Datum::String(&global_id.to_string())]),
308                diff,
309            )
310        })
311    }
312
313    fn pack_history_retention_strategy_update(
314        &self,
315        id: CatalogItemId,
316        cw: CompactionWindow,
317        diff: Diff,
318    ) -> BuiltinTableUpdate<&'static BuiltinTable> {
319        let cw: u64 = cw.comparable_timestamp().into();
320        let cw = Jsonb::from_serde_json(serde_json::Value::Number(serde_json::Number::from(cw)))
321            .expect("must serialize");
322        BuiltinTableUpdate::row(
323            &*MZ_HISTORY_RETENTION_STRATEGIES,
324            Row::pack_slice(&[
325                Datum::String(&id.to_string()),
326                // FOR is the only strategy at the moment. We may introduce FROM or others later.
327                Datum::String("FOR"),
328                cw.into_row().into_element(),
329            ]),
330            diff,
331        )
332    }
333
334    fn pack_materialized_view_update(
335        &self,
336        id: CatalogItemId,
337        mview: &MaterializedView,
338        diff: Diff,
339    ) -> Vec<BuiltinTableUpdate<&'static BuiltinTable>> {
340        let mut updates = Vec::new();
341
342        if let Some(refresh_schedule) = &mview.refresh_schedule {
343            // This can't be `ON COMMIT`, because that is represented by a `None` instead of an
344            // empty `RefreshSchedule`.
345            assert!(!refresh_schedule.is_empty());
346            for RefreshEvery {
347                interval,
348                aligned_to,
349            } in refresh_schedule.everies.iter()
350            {
351                let aligned_to_dt = mz_ore::now::to_datetime(
352                    <&Timestamp as TryInto<u64>>::try_into(aligned_to).expect("undoes planning"),
353                );
354                updates.push(BuiltinTableUpdate::row(
355                    &*MZ_MATERIALIZED_VIEW_REFRESH_STRATEGIES,
356                    Row::pack_slice(&[
357                        Datum::String(&id.to_string()),
358                        Datum::String("every"),
359                        Datum::Interval(
360                            Interval::from_duration(interval).expect(
361                                "planning ensured that this is convertible back to Interval",
362                            ),
363                        ),
364                        Datum::TimestampTz(aligned_to_dt.try_into().expect("undoes planning")),
365                        Datum::Null,
366                    ]),
367                    diff,
368                ));
369            }
370            for at in refresh_schedule.ats.iter() {
371                let at_dt = mz_ore::now::to_datetime(
372                    <&Timestamp as TryInto<u64>>::try_into(at).expect("undoes planning"),
373                );
374                updates.push(BuiltinTableUpdate::row(
375                    &*MZ_MATERIALIZED_VIEW_REFRESH_STRATEGIES,
376                    Row::pack_slice(&[
377                        Datum::String(&id.to_string()),
378                        Datum::String("at"),
379                        Datum::Null,
380                        Datum::Null,
381                        Datum::TimestampTz(at_dt.try_into().expect("undoes planning")),
382                    ]),
383                    diff,
384                ));
385            }
386        } else {
387            updates.push(BuiltinTableUpdate::row(
388                &*MZ_MATERIALIZED_VIEW_REFRESH_STRATEGIES,
389                Row::pack_slice(&[
390                    Datum::String(&id.to_string()),
391                    Datum::String("on-commit"),
392                    Datum::Null,
393                    Datum::Null,
394                    Datum::Null,
395                ]),
396                diff,
397            ));
398        }
399
400        if let Some(target_id) = mview.replacement_target {
401            updates.push(BuiltinTableUpdate::row(
402                &*MZ_REPLACEMENTS,
403                Row::pack_slice(&[
404                    Datum::String(&id.to_string()),
405                    Datum::String(&target_id.to_string()),
406                ]),
407                diff,
408            ));
409        }
410
411        updates
412    }
413
414    fn pack_index_update(
415        &self,
416        id: CatalogItemId,
417        index: &Index,
418        diff: Diff,
419    ) -> Vec<BuiltinTableUpdate<&'static BuiltinTable>> {
420        let mut updates = vec![];
421
422        let create_stmt = mz_sql::parse::parse(&index.create_sql)
423            .unwrap_or_else(|e| {
424                panic!(
425                    "create_sql cannot be invalid: `{}` --- error: `{}`",
426                    index.create_sql, e
427                )
428            })
429            .into_element()
430            .ast;
431
432        let key_sqls = match &create_stmt {
433            Statement::CreateIndex(CreateIndexStatement { key_parts, .. }) => key_parts
434                .as_ref()
435                .expect("key_parts is filled in during planning"),
436            _ => unreachable!(),
437        };
438
439        let on_entry = self.get_entry_by_global_id(&index.on);
440        let on_desc = on_entry
441            .relation_desc()
442            .expect("can only create indexes on items with a valid description");
443        let repr_col_types: Vec<ReprColumnType> = on_desc
444            .typ()
445            .column_types
446            .iter()
447            .map(ReprColumnType::from)
448            .collect();
449        for (i, key) in index.keys.iter().enumerate() {
450            let nullable = key.typ(&repr_col_types).nullable;
451            let seq_in_index = u64::cast_from(i + 1);
452            let key_sql = key_sqls
453                .get(i)
454                .expect("missing sql information for index key")
455                .to_ast_string_simple();
456            let (field_number, expression) = match key {
457                MirScalarExpr::Column(col, _) => {
458                    (Datum::UInt64(u64::cast_from(*col + 1)), Datum::Null)
459                }
460                _ => (Datum::Null, Datum::String(&key_sql)),
461            };
462            updates.push(BuiltinTableUpdate::row(
463                &*MZ_INDEX_COLUMNS,
464                Row::pack_slice(&[
465                    Datum::String(&id.to_string()),
466                    Datum::UInt64(seq_in_index),
467                    field_number,
468                    expression,
469                    Datum::from(nullable),
470                ]),
471                diff,
472            ));
473        }
474
475        updates
476    }
477
478    fn pack_type_update(
479        &self,
480        id: CatalogItemId,
481        oid: u32,
482        schema_id: &SchemaSpecifier,
483        name: &str,
484        owner_id: &RoleId,
485        privileges: Datum,
486        typ: &Type,
487        diff: Diff,
488    ) -> Vec<BuiltinTableUpdate<&'static BuiltinTable>> {
489        let mut out = vec![];
490
491        let redacted = typ.create_sql.as_ref().map(|create_sql| {
492            mz_sql::parse::parse(create_sql)
493                .unwrap_or_else(|_| panic!("create_sql cannot be invalid: {}", create_sql))
494                .into_element()
495                .ast
496                .to_ast_string_redacted()
497        });
498
499        out.push(BuiltinTableUpdate::row(
500            &*MZ_TYPES,
501            Row::pack_slice(&[
502                Datum::String(&id.to_string()),
503                Datum::UInt32(oid),
504                Datum::String(&schema_id.to_string()),
505                Datum::String(name),
506                Datum::String(&TypeCategory::from_catalog_type(&typ.details.typ).to_string()),
507                Datum::String(&owner_id.to_string()),
508                privileges,
509                if let Some(create_sql) = &typ.create_sql {
510                    Datum::String(create_sql)
511                } else {
512                    Datum::Null
513                },
514                if let Some(redacted) = &redacted {
515                    Datum::String(redacted)
516                } else {
517                    Datum::Null
518                },
519            ]),
520            diff,
521        ));
522
523        let mut row = Row::default();
524        let mut packer = row.packer();
525
526        fn append_modifier(packer: &mut RowPacker<'_>, mods: &[i64]) {
527            if mods.is_empty() {
528                packer.push(Datum::Null);
529            } else {
530                packer.push_list(mods.iter().map(|m| Datum::Int64(*m)));
531            }
532        }
533
534        let index_id = match &typ.details.typ {
535            CatalogType::Array {
536                element_reference: element_id,
537            } => {
538                packer.push(Datum::String(&id.to_string()));
539                packer.push(Datum::String(&element_id.to_string()));
540                &MZ_ARRAY_TYPES
541            }
542            CatalogType::List {
543                element_reference: element_id,
544                element_modifiers,
545            } => {
546                packer.push(Datum::String(&id.to_string()));
547                packer.push(Datum::String(&element_id.to_string()));
548                append_modifier(&mut packer, element_modifiers);
549                &MZ_LIST_TYPES
550            }
551            CatalogType::Map {
552                key_reference: key_id,
553                value_reference: value_id,
554                key_modifiers,
555                value_modifiers,
556            } => {
557                packer.push(Datum::String(&id.to_string()));
558                packer.push(Datum::String(&key_id.to_string()));
559                packer.push(Datum::String(&value_id.to_string()));
560                append_modifier(&mut packer, key_modifiers);
561                append_modifier(&mut packer, value_modifiers);
562                &MZ_MAP_TYPES
563            }
564            CatalogType::Pseudo => {
565                packer.push(Datum::String(&id.to_string()));
566                &MZ_PSEUDO_TYPES
567            }
568            _ => {
569                packer.push(Datum::String(&id.to_string()));
570                &MZ_BASE_TYPES
571            }
572        };
573        out.push(BuiltinTableUpdate::row(index_id, row, diff));
574
575        if let Some(pg_metadata) = &typ.details.pg_metadata {
576            out.push(BuiltinTableUpdate::row(
577                &*MZ_TYPE_PG_METADATA,
578                Row::pack_slice(&[
579                    Datum::String(&id.to_string()),
580                    Datum::UInt32(pg_metadata.typinput_oid),
581                    Datum::UInt32(pg_metadata.typreceive_oid),
582                ]),
583                diff,
584            ));
585        }
586
587        out
588    }
589
590    fn pack_func_update(
591        &self,
592        id: CatalogItemId,
593        schema_id: &SchemaSpecifier,
594        name: &str,
595        owner_id: &RoleId,
596        func: &Func,
597        diff: Diff,
598    ) -> Vec<BuiltinTableUpdate<&'static BuiltinTable>> {
599        let mut updates = vec![];
600        for func_impl_details in func.inner.func_impls() {
601            let arg_type_ids = func_impl_details
602                .arg_typs
603                .iter()
604                .map(|typ| self.get_system_type(typ).id().to_string())
605                .collect::<Vec<_>>();
606
607            let mut row = Row::default();
608            row.packer()
609                .try_push_array(
610                    &[ArrayDimension {
611                        lower_bound: 1,
612                        length: arg_type_ids.len(),
613                    }],
614                    arg_type_ids.iter().map(|id| Datum::String(id)),
615                )
616                .expect(
617                    "arg_type_ids is 1 dimensional, and its length is used for the array length",
618                );
619            let arg_type_ids = row.unpack_first();
620
621            updates.push(BuiltinTableUpdate::row(
622                &*MZ_FUNCTIONS,
623                Row::pack_slice(&[
624                    Datum::String(&id.to_string()),
625                    Datum::UInt32(func_impl_details.oid),
626                    Datum::String(&schema_id.to_string()),
627                    Datum::String(name),
628                    arg_type_ids,
629                    Datum::from(
630                        func_impl_details
631                            .variadic_typ
632                            .map(|typ| self.get_system_type(typ).id().to_string())
633                            .as_deref(),
634                    ),
635                    Datum::from(
636                        func_impl_details
637                            .return_typ
638                            .map(|typ| self.get_system_type(typ).id().to_string())
639                            .as_deref(),
640                    ),
641                    func_impl_details.return_is_set.into(),
642                    Datum::String(&owner_id.to_string()),
643                ]),
644                diff,
645            ));
646
647            if let mz_sql::func::Func::Aggregate(_) = func.inner {
648                updates.push(BuiltinTableUpdate::row(
649                    &*MZ_AGGREGATES,
650                    Row::pack_slice(&[
651                        Datum::UInt32(func_impl_details.oid),
652                        // TODO(database-issues#1064): Support ordered-set aggregate functions.
653                        Datum::String("n"),
654                        Datum::Int16(0),
655                    ]),
656                    diff,
657                ));
658            }
659        }
660        updates
661    }
662
663    pub fn pack_op_update(
664        &self,
665        operator: &str,
666        func_impl_details: FuncImplCatalogDetails,
667        diff: Diff,
668    ) -> BuiltinTableUpdate<&'static BuiltinTable> {
669        let arg_type_ids = func_impl_details
670            .arg_typs
671            .iter()
672            .map(|typ| self.get_system_type(typ).id().to_string())
673            .collect::<Vec<_>>();
674
675        let mut row = Row::default();
676        row.packer()
677            .try_push_array(
678                &[ArrayDimension {
679                    lower_bound: 1,
680                    length: arg_type_ids.len(),
681                }],
682                arg_type_ids.iter().map(|id| Datum::String(id)),
683            )
684            .expect("arg_type_ids is 1 dimensional, and its length is used for the array length");
685        let arg_type_ids = row.unpack_first();
686
687        BuiltinTableUpdate::row(
688            &*MZ_OPERATORS,
689            Row::pack_slice(&[
690                Datum::UInt32(func_impl_details.oid),
691                Datum::String(operator),
692                arg_type_ids,
693                Datum::from(
694                    func_impl_details
695                        .return_typ
696                        .map(|typ| self.get_system_type(typ).id().to_string())
697                        .as_deref(),
698                ),
699            ]),
700            diff,
701        )
702    }
703
704    pub fn pack_storage_usage_update(
705        &self,
706        VersionedStorageUsage::V1(event): VersionedStorageUsage,
707        diff: Diff,
708    ) -> BuiltinTableUpdate<&'static BuiltinTable> {
709        let id = &MZ_STORAGE_USAGE_BY_SHARD;
710        let row = Row::pack_slice(&[
711            Datum::UInt64(event.id),
712            Datum::from(event.shard_id.as_deref()),
713            Datum::UInt64(event.size_bytes),
714            Datum::TimestampTz(
715                mz_ore::now::to_datetime(event.collection_timestamp)
716                    .try_into()
717                    .expect("must fit"),
718            ),
719        ]);
720        BuiltinTableUpdate::row(id, row, diff)
721    }
722
723    pub fn pack_egress_ip_update(
724        &self,
725        ip: &IpNet,
726    ) -> Result<BuiltinTableUpdate<&'static BuiltinTable>, Error> {
727        let id = &MZ_EGRESS_IPS;
728        let addr = ip.network();
729        let row = Row::pack_slice(&[
730            Datum::String(&addr.to_string()),
731            Datum::Int32(ip.prefix_len().into()),
732            Datum::String(&format!("{}/{}", addr, ip.prefix_len())),
733        ]);
734        Ok(BuiltinTableUpdate::row(id, row, Diff::ONE))
735    }
736
737    pub fn pack_license_key_update(
738        &self,
739        license_key: &ValidatedLicenseKey,
740    ) -> Result<BuiltinTableUpdate<&'static BuiltinTable>, Error> {
741        let id = &MZ_LICENSE_KEYS;
742        let row = Row::pack_slice(&[
743            Datum::String(&license_key.id),
744            Datum::String(&license_key.organization),
745            Datum::String(&license_key.environment_id),
746            Datum::TimestampTz(
747                mz_ore::now::to_datetime(license_key.expiration * 1000)
748                    .try_into()
749                    .expect("must fit"),
750            ),
751            Datum::TimestampTz(
752                mz_ore::now::to_datetime(license_key.not_before * 1000)
753                    .try_into()
754                    .expect("must fit"),
755            ),
756        ]);
757        Ok(BuiltinTableUpdate::row(id, row, Diff::ONE))
758    }
759
760    pub fn pack_all_replica_size_updates(&self) -> Vec<BuiltinTableUpdate<&'static BuiltinTable>> {
761        let mut updates = Vec::new();
762        for (size, alloc) in &self.cluster_replica_sizes.0 {
763            // Just invent something when the limits are `None`, which only happens in non-prod
764            // environments (tests, process orchestrator, etc.)
765            let DiskLimit(ByteSize(disk_bytes)) =
766                (alloc.disk_limit).unwrap_or(DiskLimit::ARBITRARY);
767
768            // The disk column of mz_clusters / mz_cluster_replicas MVs needs
769            // `swap_enabled` and `disk_bytes`; expose them through a parallel
770            // internal table. Unlike the public sizes table below, we write
771            // here unconditionally — `cluster_replica_size_has_disk` previously
772            // indexed the in-memory map without checking `disabled`, so a
773            // managed cluster pinned to a disabled size still resolved its
774            // `disk` column from the real allocation. Writing disabled rows
775            // here preserves that behavior.
776            let internal_row = Row::pack_slice(&[
777                size.as_str().into(),
778                Datum::from(alloc.swap_enabled),
779                disk_bytes.into(),
780            ]);
781            updates.push(BuiltinTableUpdate::row(
782                &*MZ_CLUSTER_REPLICA_SIZE_INTERNAL,
783                internal_row,
784                Diff::ONE,
785            ));
786
787            if alloc.disabled {
788                continue;
789            }
790
791            let cpu_limit = alloc.cpu_limit.unwrap_or(CpuLimit::MAX);
792            let MemoryLimit(ByteSize(memory_bytes)) =
793                (alloc.memory_limit).unwrap_or(MemoryLimit::MAX);
794
795            let row = Row::pack_slice(&[
796                size.as_str().into(),
797                u64::cast_from(alloc.scale).into(),
798                u64::cast_from(alloc.workers).into(),
799                cpu_limit.as_nanocpus().into(),
800                memory_bytes.into(),
801                disk_bytes.into(),
802                (alloc.credits_per_hour).into(),
803            ]);
804
805            updates.push(BuiltinTableUpdate::row(
806                &*MZ_CLUSTER_REPLICA_SIZES,
807                row,
808                Diff::ONE,
809            ));
810        }
811
812        updates
813    }
814
815    pub fn pack_subscribe_update(
816        &self,
817        id: GlobalId,
818        subscribe: &ActiveSubscribe,
819        diff: Diff,
820    ) -> BuiltinTableUpdate<&'static BuiltinTable> {
821        let mut row = Row::default();
822        let mut packer = row.packer();
823        packer.push(Datum::String(&id.to_string()));
824        packer.push(Datum::Uuid(subscribe.session_uuid));
825        packer.push(Datum::String(&subscribe.cluster_id.to_string()));
826
827        let start_dt = mz_ore::now::to_datetime(subscribe.start_time);
828        packer.push(Datum::TimestampTz(start_dt.try_into().expect("must fit")));
829
830        let depends_on: Vec<_> = subscribe
831            .depends_on
832            .iter()
833            .map(|id| id.to_string())
834            .collect();
835        packer.push_list(depends_on.iter().map(|s| Datum::String(s)));
836
837        BuiltinTableUpdate::row(&*MZ_SUBSCRIPTIONS, row, diff)
838    }
839
840    pub fn pack_session_update(
841        &self,
842        conn: &ConnMeta,
843        diff: Diff,
844    ) -> BuiltinTableUpdate<&'static BuiltinTable> {
845        let connect_dt = mz_ore::now::to_datetime(conn.connected_at());
846        BuiltinTableUpdate::row(
847            &*MZ_SESSIONS,
848            Row::pack_slice(&[
849                Datum::Uuid(conn.uuid()),
850                Datum::UInt32(conn.conn_id().unhandled()),
851                Datum::String(&conn.authenticated_role_id().to_string()),
852                Datum::from(conn.client_ip().map(|ip| ip.to_string()).as_deref()),
853                Datum::TimestampTz(connect_dt.try_into().expect("must fit")),
854            ]),
855            diff,
856        )
857    }
858
859    fn pack_privilege_array_row(&self, privileges: &PrivilegeMap) -> Row {
860        let mut row = Row::default();
861        let flat_privileges: Vec<_> = privileges.all_values_owned().collect();
862        row.packer()
863            .try_push_array(
864                &[ArrayDimension {
865                    lower_bound: 1,
866                    length: flat_privileges.len(),
867                }],
868                flat_privileges
869                    .into_iter()
870                    .map(|mz_acl_item| Datum::MzAclItem(mz_acl_item.clone())),
871            )
872            .expect("privileges is 1 dimensional, and its length is used for the array length");
873        row
874    }
875
876    pub fn pack_webhook_source_update(
877        &self,
878        item_id: CatalogItemId,
879        diff: Diff,
880    ) -> BuiltinTableUpdate<&'static BuiltinTable> {
881        let url = self
882            .try_get_webhook_url(&item_id)
883            .expect("webhook source should exist");
884        let url = url.to_string();
885        let name = &self.get_entry(&item_id).name().item;
886        let id_str = item_id.to_string();
887
888        BuiltinTableUpdate::row(
889            &*MZ_WEBHOOKS_SOURCES,
890            Row::pack_slice(&[
891                Datum::String(&id_str),
892                Datum::String(name),
893                Datum::String(&url),
894            ]),
895            diff,
896        )
897    }
898
899    pub fn pack_source_references_update(
900        &self,
901        source_references: &SourceReferences,
902        diff: Diff,
903    ) -> Vec<BuiltinTableUpdate<&'static BuiltinTable>> {
904        let source_id = source_references.source_id.to_string();
905        let updated_at = &source_references.updated_at;
906        source_references
907            .references
908            .iter()
909            .map(|reference| {
910                let mut row = Row::default();
911                let mut packer = row.packer();
912                packer.extend([
913                    Datum::String(&source_id),
914                    reference
915                        .namespace
916                        .as_ref()
917                        .map(|s| Datum::String(s))
918                        .unwrap_or(Datum::Null),
919                    Datum::String(&reference.name),
920                    Datum::TimestampTz(
921                        mz_ore::now::to_datetime(*updated_at)
922                            .try_into()
923                            .expect("must fit"),
924                    ),
925                ]);
926                if reference.columns.len() > 0 {
927                    packer
928                        .try_push_array(
929                            &[ArrayDimension {
930                                lower_bound: 1,
931                                length: reference.columns.len(),
932                            }],
933                            reference.columns.iter().map(|col| Datum::String(col)),
934                        )
935                        .expect(
936                            "columns is 1 dimensional, and its length is used for the array length",
937                        );
938                } else {
939                    packer.push(Datum::Null);
940                }
941
942                BuiltinTableUpdate::row(&*MZ_SOURCE_REFERENCES, row, diff)
943            })
944            .collect()
945    }
946}