1mod 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
43use crate::active_compute_sink::ActiveSubscribe;
45use crate::catalog::CatalogState;
46use crate::coord::ConnMeta;
47
48#[derive(Debug, Clone)]
50pub struct BuiltinTableUpdate<T = CatalogItemId> {
51 pub id: T,
53 pub data: TableData,
55}
56
57impl<T> BuiltinTableUpdate<T> {
58 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 DataSourceDesc::Ingestion { .. }
148 | DataSourceDesc::OldSyntaxIngestion { .. }
149 | DataSourceDesc::IngestionExport { .. }
150 | DataSourceDesc::Introspection(_)
151 | DataSourceDesc::Progress
152 | DataSourceDesc::Catalog => vec![],
153 }
154 }
155 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 CatalogItem::Table(_)
169 | CatalogItem::View(_)
170 | CatalogItem::MaterializedView(_)
171 | CatalogItem::Index(_)
172 | CatalogItem::Log(_)
173 | CatalogItem::Secret(_)
174 | CatalogItem::MetricSink(_) => vec![],
175 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 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 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 let DiskLimit(ByteSize(disk_bytes)) = (alloc.disk_limit).unwrap_or(DiskLimit::ARBITRARY);
486
487 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}