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_audit_log::VersionedStorageUsage;
15use mz_catalog::SYSTEM_CONN_ID;
16use mz_catalog::builtin::{
17    BuiltinTable, MZ_AGGREGATES, MZ_ARRAY_TYPES, MZ_BASE_TYPES, MZ_CLUSTER_REPLICA_SIZE_INTERNAL,
18    MZ_CLUSTER_REPLICA_SIZES, MZ_EGRESS_IPS, MZ_FUNCTIONS, MZ_LICENSE_KEYS, MZ_LIST_TYPES,
19    MZ_MAP_TYPES, MZ_OPERATORS, MZ_PSEUDO_TYPES, MZ_ROLE_AUTH, MZ_SESSIONS,
20    MZ_STORAGE_USAGE_BY_SHARD, MZ_SUBSCRIPTIONS, MZ_TYPE_PG_METADATA, MZ_TYPES,
21    MZ_WEBHOOKS_SOURCES,
22};
23use mz_catalog::memory::error::Error;
24use mz_catalog::memory::objects::{CatalogItem, DataSourceDesc, Func, Type};
25use mz_controller::clusters::ReplicaAllocation;
26use mz_license_keys::ValidatedLicenseKey;
27use mz_orchestrator::{CpuLimit, DiskLimit, MemoryLimit};
28use mz_ore::cast::CastFrom;
29use mz_ore::collections::CollectionExt;
30use mz_persist_client::batch::ProtoBatch;
31use mz_repr::adt::array::ArrayDimension;
32use mz_repr::adt::mz_acl_item::PrivilegeMap;
33use mz_repr::role_id::RoleId;
34use mz_repr::{CatalogItemId, Datum, Diff, GlobalId, Row, RowPacker};
35use mz_sql::catalog::{CatalogType, TypeCategory};
36use mz_sql::func::FuncImplCatalogDetails;
37use mz_sql::names::SchemaSpecifier;
38use mz_sql_parser::ast::display::AstDisplay;
39use mz_storage_client::client::TableData;
40use smallvec::smallvec;
41use uuid::Uuid;
42
43// DO NOT add any more imports from `crate` outside of `crate::catalog`.
44use crate::active_compute_sink::ActiveSubscribe;
45use crate::catalog::CatalogState;
46use crate::coord::ConnMeta;
47
48/// An update to a built-in table.
49#[derive(Debug, Clone)]
50pub struct BuiltinTableUpdate<T = CatalogItemId> {
51    /// The reference of the table to update.
52    pub id: T,
53    /// The data to put into the table.
54    pub data: TableData,
55}
56
57impl<T> BuiltinTableUpdate<T> {
58    /// Create a [`BuiltinTableUpdate`] from a [`Row`].
59    pub fn row(id: T, row: Row, diff: Diff) -> BuiltinTableUpdate<T> {
60        BuiltinTableUpdate {
61            id,
62            data: TableData::Rows(vec![(row, diff)]),
63        }
64    }
65
66    pub fn batch(id: T, batch: ProtoBatch) -> BuiltinTableUpdate<T> {
67        BuiltinTableUpdate {
68            id,
69            data: TableData::Batches(smallvec![batch]),
70        }
71    }
72}
73
74impl CatalogState {
75    pub fn resolve_builtin_table_updates(
76        &self,
77        builtin_table_update: Vec<BuiltinTableUpdate<&'static BuiltinTable>>,
78    ) -> Vec<BuiltinTableUpdate<CatalogItemId>> {
79        builtin_table_update
80            .into_iter()
81            .map(|builtin_table_update| self.resolve_builtin_table_update(builtin_table_update))
82            .collect()
83    }
84
85    pub fn resolve_builtin_table_update(
86        &self,
87        BuiltinTableUpdate { id, data }: BuiltinTableUpdate<&'static BuiltinTable>,
88    ) -> BuiltinTableUpdate<CatalogItemId> {
89        let id = self.resolve_builtin_table(id);
90        BuiltinTableUpdate { id, data }
91    }
92
93    pub(super) fn pack_role_auth_update(
94        &self,
95        id: RoleId,
96        diff: Diff,
97    ) -> BuiltinTableUpdate<&'static BuiltinTable> {
98        let role_auth = self.get_role_auth(&id);
99        let role = self.get_role(&id);
100        BuiltinTableUpdate::row(
101            &*MZ_ROLE_AUTH,
102            Row::pack_slice(&[
103                Datum::String(&role_auth.role_id.to_string()),
104                Datum::UInt32(role.oid),
105                match &role_auth.password_hash {
106                    Some(hash) => Datum::String(hash),
107                    None => Datum::Null,
108                },
109                Datum::TimestampTz(
110                    mz_ore::now::to_datetime(role_auth.updated_at)
111                        .try_into()
112                        .expect("must fit"),
113                ),
114            ]),
115            diff,
116        )
117    }
118
119    pub(super) fn pack_item_update(
120        &self,
121        id: CatalogItemId,
122        diff: Diff,
123    ) -> Vec<BuiltinTableUpdate<&'static BuiltinTable>> {
124        let entry = self.get_entry(&id);
125        let oid = entry.oid();
126        let conn_id = entry.item().conn_id().unwrap_or(&SYSTEM_CONN_ID);
127        let schema_id = &self
128            .get_schema(
129                &entry.name().qualifiers.database_spec,
130                &entry.name().qualifiers.schema_spec,
131                conn_id,
132            )
133            .id;
134        let name = &entry.name().item;
135        let owner_id = entry.owner_id();
136        let privileges_row = self.pack_privilege_array_row(entry.privileges());
137        let privileges = privileges_row.unpack_first();
138        let updates = match entry.item() {
139            CatalogItem::Source(source) => {
140                match &source.data_source {
141                    DataSourceDesc::Webhook { .. } => {
142                        vec![self.pack_webhook_source_update(id, diff)]
143                    }
144                    // Old-syntax subsource metadata (mz_postgres/mysql/sql_server_source_tables)
145                    // is now derived from create_sql by materialized views over
146                    // mz_catalog_raw, so ingestion exports need no special packing.
147                    DataSourceDesc::Ingestion { .. }
148                    | DataSourceDesc::OldSyntaxIngestion { .. }
149                    | DataSourceDesc::IngestionExport { .. }
150                    | DataSourceDesc::Introspection(_)
151                    | DataSourceDesc::Progress
152                    | DataSourceDesc::Catalog => vec![],
153                }
154            }
155            // mz_sinks, mz_kafka_sinks and mz_iceberg_sinks read create_sql
156            // out of mz_catalog_raw, so there is nothing to pack here.
157            CatalogItem::Sink(_) => vec![],
158            CatalogItem::Type(ty) => {
159                self.pack_type_update(id, oid, schema_id, name, owner_id, privileges, ty, diff)
160            }
161            CatalogItem::Func(func) => {
162                self.pack_func_update(id, schema_id, name, owner_id, func, diff)
163            }
164            // Tables, views, materialized views, indexes, and metric sinks
165            // are exposed through materialized views derived from
166            // `mz_catalog_raw`, and logs and secrets never had builtin-table
167            // rows, so none pack a row here.
168            CatalogItem::Table(_)
169            | CatalogItem::View(_)
170            | CatalogItem::MaterializedView(_)
171            | CatalogItem::Index(_)
172            | CatalogItem::Log(_)
173            | CatalogItem::Secret(_)
174            | CatalogItem::MetricSink(_) => vec![],
175            // Connection details (mz_kafka_connections, mz_ssh_tunnel_connections,
176            // mz_aws_connections, mz_aws_privatelink_connections) are now derived
177            // from the persisted create_sql by materialized views over
178            // mz_catalog_raw, so connections need no special packing here.
179            CatalogItem::Connection(_) => vec![],
180        };
181
182        updates
183    }
184
185    fn pack_type_update(
186        &self,
187        id: CatalogItemId,
188        oid: u32,
189        schema_id: &SchemaSpecifier,
190        name: &str,
191        owner_id: &RoleId,
192        privileges: Datum,
193        typ: &Type,
194        diff: Diff,
195    ) -> Vec<BuiltinTableUpdate<&'static BuiltinTable>> {
196        let mut out = vec![];
197
198        let redacted = typ.create_sql.as_ref().map(|create_sql| {
199            mz_sql::parse::parse(create_sql)
200                .unwrap_or_else(|_| panic!("create_sql cannot be invalid: {}", create_sql))
201                .into_element()
202                .ast
203                .to_ast_string_redacted()
204        });
205
206        out.push(BuiltinTableUpdate::row(
207            &*MZ_TYPES,
208            Row::pack_slice(&[
209                Datum::String(&id.to_string()),
210                Datum::UInt32(oid),
211                Datum::String(&schema_id.to_string()),
212                Datum::String(name),
213                Datum::String(&TypeCategory::from_catalog_type(&typ.details.typ).to_string()),
214                Datum::String(&owner_id.to_string()),
215                privileges,
216                if let Some(create_sql) = &typ.create_sql {
217                    Datum::String(create_sql)
218                } else {
219                    Datum::Null
220                },
221                if let Some(redacted) = &redacted {
222                    Datum::String(redacted)
223                } else {
224                    Datum::Null
225                },
226            ]),
227            diff,
228        ));
229
230        let mut row = Row::default();
231        let mut packer = row.packer();
232
233        fn append_modifier(packer: &mut RowPacker<'_>, mods: &[i64]) {
234            if mods.is_empty() {
235                packer.push(Datum::Null);
236            } else {
237                packer.push_list(mods.iter().map(|m| Datum::Int64(*m)));
238            }
239        }
240
241        let index_id = match &typ.details.typ {
242            CatalogType::Array {
243                element_reference: element_id,
244            } => {
245                packer.push(Datum::String(&id.to_string()));
246                packer.push(Datum::String(&element_id.to_string()));
247                &MZ_ARRAY_TYPES
248            }
249            CatalogType::List {
250                element_reference: element_id,
251                element_modifiers,
252            } => {
253                packer.push(Datum::String(&id.to_string()));
254                packer.push(Datum::String(&element_id.to_string()));
255                append_modifier(&mut packer, element_modifiers);
256                &MZ_LIST_TYPES
257            }
258            CatalogType::Map {
259                key_reference: key_id,
260                value_reference: value_id,
261                key_modifiers,
262                value_modifiers,
263            } => {
264                packer.push(Datum::String(&id.to_string()));
265                packer.push(Datum::String(&key_id.to_string()));
266                packer.push(Datum::String(&value_id.to_string()));
267                append_modifier(&mut packer, key_modifiers);
268                append_modifier(&mut packer, value_modifiers);
269                &MZ_MAP_TYPES
270            }
271            CatalogType::Pseudo => {
272                packer.push(Datum::String(&id.to_string()));
273                &MZ_PSEUDO_TYPES
274            }
275            _ => {
276                packer.push(Datum::String(&id.to_string()));
277                &MZ_BASE_TYPES
278            }
279        };
280        out.push(BuiltinTableUpdate::row(index_id, row, diff));
281
282        if let Some(pg_metadata) = &typ.details.pg_metadata {
283            // Resolved the same way as a column's wire type, so this matches the
284            // `type_len` pgwire sends in `RowDescription`. Pseudo-types and arrays
285            // of them (`_record`) have no scalar type, so their length is unknown.
286            let typlen =
287                match mz_sql::plan::scalar_type_from_catalog(&self.for_system_session(), id, &[]) {
288                    Ok(scalar_type) => Datum::Int16(mz_pgrepr::Type::from(&scalar_type).typlen()),
289                    Err(_) => Datum::Null,
290                };
291            out.push(BuiltinTableUpdate::row(
292                &*MZ_TYPE_PG_METADATA,
293                Row::pack_slice(&[
294                    Datum::String(&id.to_string()),
295                    Datum::UInt32(pg_metadata.typinput_oid),
296                    Datum::UInt32(pg_metadata.typreceive_oid),
297                    Datum::UInt32(pg_metadata.typsend_oid),
298                    typlen,
299                ]),
300                diff,
301            ));
302        }
303
304        out
305    }
306
307    fn pack_func_update(
308        &self,
309        id: CatalogItemId,
310        schema_id: &SchemaSpecifier,
311        name: &str,
312        owner_id: &RoleId,
313        func: &Func,
314        diff: Diff,
315    ) -> Vec<BuiltinTableUpdate<&'static BuiltinTable>> {
316        let mut updates = vec![];
317        for func_impl_details in func.inner.func_impls() {
318            let arg_type_ids = func_impl_details
319                .arg_typs
320                .iter()
321                .map(|typ| self.get_system_type(typ).id().to_string())
322                .collect::<Vec<_>>();
323
324            let mut row = Row::default();
325            row.packer()
326                .try_push_array(
327                    &[ArrayDimension {
328                        lower_bound: 1,
329                        length: arg_type_ids.len(),
330                    }],
331                    arg_type_ids.iter().map(|id| Datum::String(id)),
332                )
333                .expect(
334                    "arg_type_ids is 1 dimensional, and its length is used for the array length",
335                );
336            let arg_type_ids = row.unpack_first();
337
338            updates.push(BuiltinTableUpdate::row(
339                &*MZ_FUNCTIONS,
340                Row::pack_slice(&[
341                    Datum::String(&id.to_string()),
342                    Datum::UInt32(func_impl_details.oid),
343                    Datum::String(&schema_id.to_string()),
344                    Datum::String(name),
345                    arg_type_ids,
346                    Datum::from(
347                        func_impl_details
348                            .variadic_typ
349                            .map(|typ| self.get_system_type(typ).id().to_string())
350                            .as_deref(),
351                    ),
352                    Datum::from(
353                        func_impl_details
354                            .return_typ
355                            .map(|typ| self.get_system_type(typ).id().to_string())
356                            .as_deref(),
357                    ),
358                    func_impl_details.return_is_set.into(),
359                    Datum::String(&owner_id.to_string()),
360                ]),
361                diff,
362            ));
363
364            if let mz_sql::func::Func::Aggregate(_) = func.inner {
365                updates.push(BuiltinTableUpdate::row(
366                    &*MZ_AGGREGATES,
367                    Row::pack_slice(&[
368                        Datum::UInt32(func_impl_details.oid),
369                        // TODO(database-issues#1064): Support ordered-set aggregate functions.
370                        Datum::String("n"),
371                        Datum::Int16(0),
372                    ]),
373                    diff,
374                ));
375            }
376        }
377        updates
378    }
379
380    pub fn pack_op_update(
381        &self,
382        operator: &str,
383        func_impl_details: FuncImplCatalogDetails,
384        diff: Diff,
385    ) -> BuiltinTableUpdate<&'static BuiltinTable> {
386        let arg_type_ids = func_impl_details
387            .arg_typs
388            .iter()
389            .map(|typ| self.get_system_type(typ).id().to_string())
390            .collect::<Vec<_>>();
391
392        let mut row = Row::default();
393        row.packer()
394            .try_push_array(
395                &[ArrayDimension {
396                    lower_bound: 1,
397                    length: arg_type_ids.len(),
398                }],
399                arg_type_ids.iter().map(|id| Datum::String(id)),
400            )
401            .expect("arg_type_ids is 1 dimensional, and its length is used for the array length");
402        let arg_type_ids = row.unpack_first();
403
404        BuiltinTableUpdate::row(
405            &*MZ_OPERATORS,
406            Row::pack_slice(&[
407                Datum::UInt32(func_impl_details.oid),
408                Datum::String(operator),
409                arg_type_ids,
410                Datum::from(
411                    func_impl_details
412                        .return_typ
413                        .map(|typ| self.get_system_type(typ).id().to_string())
414                        .as_deref(),
415                ),
416            ]),
417            diff,
418        )
419    }
420
421    pub fn pack_storage_usage_update(
422        &self,
423        VersionedStorageUsage::V1(event): VersionedStorageUsage,
424        diff: Diff,
425    ) -> BuiltinTableUpdate<&'static BuiltinTable> {
426        let id = &MZ_STORAGE_USAGE_BY_SHARD;
427        let row = Row::pack_slice(&[
428            Datum::UInt64(event.id),
429            Datum::from(event.shard_id.as_deref()),
430            Datum::UInt64(event.size_bytes),
431            Datum::TimestampTz(
432                mz_ore::now::to_datetime(event.collection_timestamp)
433                    .try_into()
434                    .expect("must fit"),
435            ),
436        ]);
437        BuiltinTableUpdate::row(id, row, diff)
438    }
439
440    pub fn pack_egress_ip_update(
441        &self,
442        ip: &IpNet,
443    ) -> Result<BuiltinTableUpdate<&'static BuiltinTable>, Error> {
444        let id = &MZ_EGRESS_IPS;
445        let addr = ip.network();
446        let row = Row::pack_slice(&[
447            Datum::String(&addr.to_string()),
448            Datum::Int32(ip.prefix_len().into()),
449            Datum::String(&format!("{}/{}", addr, ip.prefix_len())),
450        ]);
451        Ok(BuiltinTableUpdate::row(id, row, Diff::ONE))
452    }
453
454    pub fn pack_license_key_update(
455        &self,
456        license_key: &ValidatedLicenseKey,
457    ) -> Result<BuiltinTableUpdate<&'static BuiltinTable>, Error> {
458        let id = &MZ_LICENSE_KEYS;
459        let row = Row::pack_slice(&[
460            Datum::String(&license_key.id),
461            Datum::String(&license_key.organization),
462            Datum::String(&license_key.environment_id),
463            Datum::TimestampTz(
464                mz_ore::now::to_datetime(license_key.expiration * 1000)
465                    .try_into()
466                    .expect("must fit"),
467            ),
468            Datum::TimestampTz(
469                mz_ore::now::to_datetime(license_key.not_before * 1000)
470                    .try_into()
471                    .expect("must fit"),
472            ),
473        ]);
474        Ok(BuiltinTableUpdate::row(id, row, Diff::ONE))
475    }
476
477    pub fn pack_replica_size_update(
478        size: &str,
479        alloc: &ReplicaAllocation,
480        diff: Diff,
481    ) -> Vec<BuiltinTableUpdate<&'static BuiltinTable>> {
482        let mut updates = Vec::new();
483        // Just invent something when the limits are `None`, which only happens in non-prod
484        // environments (tests, process orchestrator, etc.)
485        let DiskLimit(ByteSize(disk_bytes)) = (alloc.disk_limit).unwrap_or(DiskLimit::ARBITRARY);
486
487        // The disk column of mz_clusters / mz_cluster_replicas MVs needs
488        // `swap_enabled` and `disk_bytes`; expose them through a parallel
489        // internal table.
490        let internal_row = Row::pack_slice(&[
491            size.into(),
492            Datum::from(alloc.swap_enabled),
493            disk_bytes.into(),
494        ]);
495        updates.push(BuiltinTableUpdate::row(
496            &*MZ_CLUSTER_REPLICA_SIZE_INTERNAL,
497            internal_row,
498            diff,
499        ));
500
501        let cpu_limit = alloc.cpu_limit.unwrap_or(CpuLimit::MAX);
502        let MemoryLimit(ByteSize(memory_bytes)) = (alloc.memory_limit).unwrap_or(MemoryLimit::MAX);
503
504        let row = Row::pack_slice(&[
505            size.into(),
506            u64::cast_from(alloc.scale).into(),
507            u64::cast_from(alloc.workers).into(),
508            cpu_limit.as_nanocpus().into(),
509            memory_bytes.into(),
510            disk_bytes.into(),
511            (alloc.credits_per_hour).into(),
512            alloc.disabled.into(),
513        ]);
514
515        updates.push(BuiltinTableUpdate::row(
516            &*MZ_CLUSTER_REPLICA_SIZES,
517            row,
518            diff,
519        ));
520
521        updates
522    }
523
524    pub fn pack_subscribe_update(
525        &self,
526        id: GlobalId,
527        subscribe: &ActiveSubscribe,
528        session_uuid: Uuid,
529        diff: Diff,
530    ) -> BuiltinTableUpdate<&'static BuiltinTable> {
531        let mut row = Row::default();
532        let mut packer = row.packer();
533        packer.push(Datum::String(&id.to_string()));
534        packer.push(Datum::Uuid(session_uuid));
535        packer.push(Datum::String(&subscribe.cluster_id.to_string()));
536
537        let start_dt = mz_ore::now::to_datetime(subscribe.start_time);
538        packer.push(Datum::TimestampTz(start_dt.try_into().expect("must fit")));
539
540        let depends_on: Vec<_> = subscribe
541            .depends_on
542            .iter()
543            .map(|id| id.to_string())
544            .collect();
545        packer.push_list(depends_on.iter().map(|s| Datum::String(s)));
546
547        BuiltinTableUpdate::row(&*MZ_SUBSCRIPTIONS, row, diff)
548    }
549
550    pub fn pack_session_update(
551        &self,
552        conn: &ConnMeta,
553        diff: Diff,
554    ) -> BuiltinTableUpdate<&'static BuiltinTable> {
555        let connect_dt = mz_ore::now::to_datetime(conn.connected_at());
556        BuiltinTableUpdate::row(
557            &*MZ_SESSIONS,
558            Row::pack_slice(&[
559                Datum::Uuid(conn.uuid()),
560                Datum::UInt32(conn.conn_id().unhandled()),
561                Datum::String(&conn.authenticated_role_id().to_string()),
562                Datum::from(conn.client_ip().map(|ip| ip.to_string()).as_deref()),
563                Datum::TimestampTz(connect_dt.try_into().expect("must fit")),
564            ]),
565            diff,
566        )
567    }
568
569    fn pack_privilege_array_row(&self, privileges: &PrivilegeMap) -> Row {
570        let mut row = Row::default();
571        let flat_privileges: Vec<_> = privileges.all_values_owned().collect();
572        row.packer()
573            .try_push_array(
574                &[ArrayDimension {
575                    lower_bound: 1,
576                    length: flat_privileges.len(),
577                }],
578                flat_privileges
579                    .into_iter()
580                    .map(|mz_acl_item| Datum::MzAclItem(mz_acl_item.clone())),
581            )
582            .expect("privileges is 1 dimensional, and its length is used for the array length");
583        row
584    }
585
586    pub fn pack_webhook_source_update(
587        &self,
588        item_id: CatalogItemId,
589        diff: Diff,
590    ) -> BuiltinTableUpdate<&'static BuiltinTable> {
591        let url = self
592            .try_get_webhook_url(&item_id)
593            .expect("webhook source should exist");
594        let url = url.to_string();
595        let name = &self.get_entry(&item_id).name().item;
596        let id_str = item_id.to_string();
597
598        BuiltinTableUpdate::row(
599            &*MZ_WEBHOOKS_SOURCES,
600            Row::pack_slice(&[
601                Datum::String(&id_str),
602                Datum::String(name),
603                Datum::String(&url),
604            ]),
605            diff,
606        )
607    }
608}