1mod alter_op;
16mod create_op;
17mod drop_op;
18mod get_op;
19mod list_op;
20mod test;
21mod util;
22
23use std::collections::{BTreeSet, HashMap, HashSet};
24use std::iter;
25use std::mem::take;
26use std::sync::Arc;
27
28use anyhow::anyhow;
29use itertools::Itertools;
30use risingwave_common::catalog::{
31 DEFAULT_SCHEMA_NAME, FragmentTypeFlag, FragmentTypeMask, SYSTEM_SCHEMAS, TableOption,
32};
33use risingwave_common::current_cluster_version;
34use risingwave_common::id::JobId;
35use risingwave_common::secret::LocalSecretManager;
36use risingwave_common::util::stream_graph_visitor::visit_stream_node_cont_mut;
37use risingwave_connector::source::UPSTREAM_SOURCE_KEY;
38use risingwave_connector::source::cdc::build_cdc_table_id;
39use risingwave_meta_model::object::ObjectType;
40use risingwave_meta_model::prelude::*;
41use risingwave_meta_model::table::TableType;
42use risingwave_meta_model::{
43 ColumnCatalogArray, ConnectionId, CreateType, DatabaseId, FragmentId, I32Array, IndexId,
44 JobStatus, ObjectId, Property, SchemaId, SecretId, SinkFormatDesc, SinkId, SourceId,
45 StreamNode, StreamSourceInfo, StreamingParallelism, SubscriptionId, TableId, TableIdArray,
46 UserId, ViewId, connection, database, fragment, function, index, object, object_dependency,
47 pending_sink_state, schema, secret, sink, source, streaming_job, subscription, table,
48 user_privilege, view,
49};
50use risingwave_pb::catalog::connection::Info as ConnectionInfo;
51use risingwave_pb::catalog::subscription::SubscriptionState;
52use risingwave_pb::catalog::table::PbTableType;
53use risingwave_pb::catalog::{
54 PbComment, PbConnection, PbDatabase, PbFunction, PbIndex, PbSchema, PbSecret, PbSink, PbSource,
55 PbSubscription, PbTable, PbView,
56};
57use risingwave_pb::meta::cancel_creating_jobs_request::PbCreatingJobInfo;
58use risingwave_pb::meta::object::PbObjectInfo;
59use risingwave_pb::meta::subscribe_response::{
60 Info as NotificationInfo, Info, Operation as NotificationOperation, Operation,
61};
62use risingwave_pb::meta::{PbObject, PbObjectGroup};
63use risingwave_pb::stream_plan::stream_node::NodeBody;
64use risingwave_pb::telemetry::PbTelemetryEventStage;
65use risingwave_pb::user::PbUserInfo;
66use sea_orm::ActiveValue::Set;
67use sea_orm::sea_query::{Expr, Query, SimpleExpr};
68use sea_orm::{
69 ActiveModelTrait, ColumnTrait, DatabaseConnection, DatabaseTransaction, EntityTrait,
70 IntoActiveModel, JoinType, PaginatorTrait, QueryFilter, QuerySelect, RelationTrait,
71 SelectColumns, TransactionTrait, Value,
72};
73use tokio::sync::oneshot::Sender;
74use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
75use tracing::info;
76
77use super::utils::{
78 check_subscription_name_duplicate, get_internal_tables_by_id, load_streaming_jobs_by_ids,
79 rename_relation, rename_relation_refer,
80};
81use crate::controller::ObjectModel;
82use crate::controller::catalog::util::update_internal_tables;
83use crate::controller::fragment::FragmentTypeMaskExt;
84use crate::controller::utils::*;
85use crate::manager::{
86 IGNORED_NOTIFICATION_VERSION, MetaSrvEnv, NotificationVersion,
87 get_referred_connection_ids_from_source, get_referred_secret_ids_from_source,
88};
89use crate::rpc::ddl_controller::DropMode;
90use crate::telemetry::{MetaTelemetryJobDesc, report_event};
91use crate::{MetaError, MetaResult};
92
93pub type Catalog = (
94 Vec<PbDatabase>,
95 Vec<PbSchema>,
96 Vec<PbTable>,
97 Vec<PbSource>,
98 Vec<PbSink>,
99 Vec<PbSubscription>,
100 Vec<PbIndex>,
101 Vec<PbView>,
102 Vec<PbFunction>,
103 Vec<PbConnection>,
104 Vec<PbSecret>,
105);
106
107pub type CatalogControllerRef = Arc<CatalogController>;
108
109pub struct CatalogController {
111 pub(crate) env: MetaSrvEnv,
112 pub(crate) inner: RwLock<CatalogControllerInner>,
113}
114
115#[derive(Clone, Default, Debug)]
116pub struct DropTableConnectorContext {
117 pub(crate) to_change_streaming_job_id: JobId,
119 pub(crate) to_remove_state_table_id: TableId,
120 pub(crate) to_remove_source_id: SourceId,
121}
122
123#[derive(Clone, Default, Debug)]
124pub struct ReleaseContext {
125 pub(crate) database_id: DatabaseId,
126 pub(crate) removed_streaming_job_ids: Vec<JobId>,
127 pub(crate) removed_state_table_ids: Vec<TableId>,
129
130 pub(crate) removed_secret_ids: Vec<SecretId>,
132 pub(crate) removed_source_ids: Vec<SourceId>,
134 pub(crate) removed_source_fragments: HashMap<SourceId, BTreeSet<FragmentId>>,
137
138 pub(crate) removed_fragments: HashSet<FragmentId>,
139
140 pub(crate) removed_sink_fragment_by_targets: HashMap<FragmentId, Vec<FragmentId>>,
142
143 pub(crate) removed_iceberg_table_sinks: Vec<PbSink>,
145
146 pub(crate) removed_iceberg_sink_ids: Vec<SinkId>,
149
150 pub(crate) removed_iceberg_pk_index_sink_ids: Vec<SinkId>,
154}
155
156#[derive(Default)]
157pub(crate) struct CleanedDirtyStreamingJobs {
158 pub(crate) streaming_job_ids: Vec<JobId>,
159 pub(crate) dropped_table_ids: Vec<TableId>,
161 pub(crate) source_ids: Vec<SourceId>,
162 pub(crate) sink_ids: Vec<SinkId>,
164}
165
166impl CatalogController {
167 pub async fn new(env: MetaSrvEnv) -> MetaResult<Self> {
168 let meta_store = env.meta_store();
169 let catalog_controller = Self {
170 env,
171 inner: RwLock::new(CatalogControllerInner {
172 db: meta_store.conn,
173 creating_table_finish_notifier: HashMap::new(),
174 dropped_tables: HashMap::new(),
175 }),
176 };
177
178 catalog_controller.init().await?;
179 Ok(catalog_controller)
180 }
181
182 pub async fn get_inner_read_guard(&self) -> RwLockReadGuard<'_, CatalogControllerInner> {
185 self.inner.read().await
186 }
187
188 pub async fn get_inner_write_guard(&self) -> RwLockWriteGuard<'_, CatalogControllerInner> {
189 self.inner.write().await
190 }
191}
192
193pub struct CatalogControllerInner {
194 pub(crate) db: DatabaseConnection,
195 #[expect(clippy::type_complexity)]
200 pub creating_table_finish_notifier:
201 HashMap<DatabaseId, HashMap<JobId, Vec<Sender<Result<NotificationVersion, String>>>>>,
202 pub dropped_tables: HashMap<TableId, PbTable>,
204}
205
206impl CatalogController {
207 pub(crate) async fn notify_frontend(
208 &self,
209 operation: NotificationOperation,
210 info: NotificationInfo,
211 ) -> NotificationVersion {
212 self.env
213 .notification_manager()
214 .notify_frontend(operation, info)
215 .await
216 }
217
218 pub(crate) async fn notify_frontend_relation_info(
219 &self,
220 operation: NotificationOperation,
221 relation_info: PbObjectInfo,
222 ) -> NotificationVersion {
223 self.env
224 .notification_manager()
225 .notify_frontend_object_info(operation, relation_info)
226 .await
227 }
228
229 pub(crate) async fn notify_frontend_trivial(&self) -> NotificationVersion {
236 self.env
237 .notification_manager()
238 .notify_frontend(
239 NotificationOperation::Update,
240 NotificationInfo::ObjectGroup(PbObjectGroup {
241 objects: vec![],
242 dependencies: vec![],
243 }),
244 )
245 .await
246 }
247}
248
249impl CatalogController {
250 pub async fn finish_create_subscription_catalog(
251 &self,
252 subscription_id: SubscriptionId,
253 ) -> MetaResult<()> {
254 let inner = self.inner.write().await;
255 let txn = inner.db.begin().await?;
256
257 let res = Object::update_many()
259 .col_expr(object::Column::CreatedAt, Expr::current_timestamp().into())
260 .col_expr(
261 object::Column::CreatedAtClusterVersion,
262 current_cluster_version().into(),
263 )
264 .filter(object::Column::Oid.eq(subscription_id))
265 .exec(&txn)
266 .await?;
267 if res.rows_affected == 0 {
268 return Err(MetaError::catalog_id_not_found(
269 "subscription",
270 subscription_id,
271 ));
272 }
273
274 let job = subscription::ActiveModel {
276 subscription_id: Set(subscription_id),
277 subscription_state: Set(SubscriptionState::Created.into()),
278 ..Default::default()
279 };
280 Subscription::update(job).exec(&txn).await?;
281
282 let _ = grant_default_privileges_automatically(&txn, subscription_id).await?;
283
284 txn.commit().await?;
285
286 Ok(())
287 }
288
289 pub async fn notify_create_subscription(
290 &self,
291 subscription_id: SubscriptionId,
292 ) -> MetaResult<NotificationVersion> {
293 let inner = self.inner.read().await;
294 let txn = inner.db.begin().await?;
295 let (subscription, obj) = Subscription::find_by_id(subscription_id)
296 .find_also_related(Object)
297 .filter(subscription::Column::SubscriptionState.eq(SubscriptionState::Created as i32))
298 .one(&txn)
299 .await?
300 .ok_or_else(|| MetaError::catalog_id_not_found("subscription", subscription_id))?;
301
302 let dependencies =
303 list_object_dependencies_by_object_id(&txn, subscription_id.into()).await?;
304 txn.commit().await?;
305
306 let mut version = self
307 .notify_frontend(
308 NotificationOperation::Add,
309 NotificationInfo::ObjectGroup(PbObjectGroup {
310 objects: vec![PbObject {
311 object_info: PbObjectInfo::Subscription(
312 ObjectModel(subscription, obj.unwrap(), None).into(),
313 )
314 .into(),
315 }],
316 dependencies,
317 }),
318 )
319 .await;
320
321 let updated_user_ids: Vec<UserId> = UserPrivilege::find()
323 .select_only()
324 .distinct()
325 .column(user_privilege::Column::UserId)
326 .filter(user_privilege::Column::Oid.eq(subscription_id.as_object_id()))
327 .into_tuple()
328 .all(&inner.db)
329 .await?;
330
331 if !updated_user_ids.is_empty() {
332 let updated_user_infos = list_user_info_by_ids(updated_user_ids, &inner.db).await?;
333 version = self.notify_users_update(updated_user_infos).await;
334 }
335
336 Ok(version)
337 }
338
339 pub async fn get_connector_usage(&self) -> MetaResult<jsonbb::Value> {
341 let inner = self.inner.read().await;
361 let source_props_and_info: Vec<(i32, Property, Option<StreamSourceInfo>)> = Source::find()
362 .select_only()
363 .column(source::Column::SourceId)
364 .column(source::Column::WithProperties)
365 .column(source::Column::SourceInfo)
366 .into_tuple()
367 .all(&inner.db)
368 .await?;
369 let sink_props_and_info: Vec<(i32, Property, Option<SinkFormatDesc>)> = Sink::find()
370 .select_only()
371 .column(sink::Column::SinkId)
372 .column(sink::Column::Properties)
373 .column(sink::Column::SinkFormatDesc)
374 .into_tuple()
375 .all(&inner.db)
376 .await?;
377 drop(inner);
378
379 let get_connector_from_property = |property: &Property| -> String {
380 property
381 .0
382 .get(UPSTREAM_SOURCE_KEY)
383 .cloned()
384 .unwrap_or_default()
385 };
386
387 let source_report: Vec<jsonbb::Value> = source_props_and_info
388 .iter()
389 .map(|(oid, property, info)| {
390 let connector_name = get_connector_from_property(property);
391 let mut format = None;
392 let mut encode = None;
393 if let Some(info) = info {
394 let pb_info = info.to_protobuf();
395 format = Some(pb_info.format().as_str_name());
396 encode = Some(pb_info.row_encode().as_str_name());
397 }
398 jsonbb::json!({
399 oid.to_string(): {
400 "connector": connector_name,
401 "format": format,
402 "encode": encode,
403 },
404 })
405 })
406 .collect_vec();
407
408 let sink_report: Vec<jsonbb::Value> = sink_props_and_info
409 .iter()
410 .map(|(oid, property, info)| {
411 let connector_name = get_connector_from_property(property);
412 let mut format = None;
413 let mut encode = None;
414 if let Some(info) = info {
415 let pb_info = info.to_protobuf();
416 format = Some(pb_info.format().as_str_name());
417 encode = Some(pb_info.encode().as_str_name());
418 }
419 jsonbb::json!({
420 oid.to_string(): {
421 "connector": connector_name,
422 "format": format,
423 "encode": encode,
424 },
425 })
426 })
427 .collect_vec();
428
429 Ok(jsonbb::json!({
430 "source": source_report,
431 "sink": sink_report,
432 }))
433 }
434
435 pub async fn clean_dirty_subscription(
436 &self,
437 database_id: Option<DatabaseId>,
438 ) -> MetaResult<()> {
439 let inner = self.inner.write().await;
440 let txn = inner.db.begin().await?;
441 let filter_condition = object::Column::ObjType.eq(ObjectType::Subscription).and(
442 object::Column::Oid.not_in_subquery(
443 Query::select()
444 .column(subscription::Column::SubscriptionId)
445 .from(Subscription)
446 .and_where(
447 subscription::Column::SubscriptionState
448 .eq(SubscriptionState::Created as i32),
449 )
450 .take(),
451 ),
452 );
453
454 let filter_condition = if let Some(database_id) = database_id {
455 filter_condition.and(object::Column::DatabaseId.eq(database_id))
456 } else {
457 filter_condition
458 };
459 Object::delete_many()
460 .filter(filter_condition)
461 .exec(&txn)
462 .await?;
463 txn.commit().await?;
464 Ok(())
466 }
467
468 pub(crate) async fn clean_dirty_creating_jobs(
470 &self,
471 database_id: Option<DatabaseId>,
472 ) -> MetaResult<CleanedDirtyStreamingJobs> {
473 let mut inner = self.inner.write().await;
474 let txn = inner.db.begin().await?;
475
476 let filter_condition = streaming_job::Column::JobStatus.eq(JobStatus::Initial).or(
477 streaming_job::Column::JobStatus
478 .eq(JobStatus::Creating)
479 .and(streaming_job::Column::CreateType.eq(CreateType::Foreground)),
480 );
481
482 let filter_condition = if let Some(database_id) = database_id {
483 filter_condition.and(object::Column::DatabaseId.eq(database_id))
484 } else {
485 filter_condition
486 };
487
488 let mut dirty_job_objs: Vec<PartialObject> = streaming_job::Entity::find()
489 .select_only()
490 .columns([
491 object::Column::Oid,
492 object::Column::ObjType,
493 object::Column::SchemaId,
494 object::Column::DatabaseId,
495 ])
496 .join(JoinType::InnerJoin, streaming_job::Relation::Object.def())
497 .filter(filter_condition)
498 .into_partial_model()
499 .all(&txn)
500 .await?;
501
502 let dirty_iceberg_jobs = find_dirty_iceberg_table_jobs(&txn, database_id).await?;
504 if !dirty_iceberg_jobs.is_empty() {
505 dirty_job_objs.extend(dirty_iceberg_jobs);
506 }
507
508 Self::clean_dirty_sink_downstreams(&txn).await?;
509
510 if dirty_job_objs.is_empty() {
511 return Ok(CleanedDirtyStreamingJobs::default());
512 }
513
514 self.log_cleaned_dirty_jobs(&dirty_job_objs, &txn).await?;
515
516 let dirty_job_ids = dirty_job_objs
517 .iter()
518 .map(|obj| obj.oid.as_job_id())
519 .collect_vec();
520 let dirty_sink_ids = dirty_job_objs
521 .iter()
522 .filter(|obj| obj.obj_type == ObjectType::Sink)
523 .map(|obj| obj.oid.as_sink_id())
524 .collect_vec();
525 let dirty_job_table_ids = dirty_job_ids
526 .iter()
527 .map(|job_id| job_id.as_mv_table_id())
528 .collect_vec();
529
530 let all_dirty_table_ids = dirty_job_objs
533 .iter()
534 .filter(|obj| obj.obj_type == ObjectType::Table)
535 .map(|obj| obj.oid)
536 .collect_vec();
537 let dirty_table_type_map: HashMap<ObjectId, TableType> = Table::find()
538 .select_only()
539 .column(table::Column::TableId)
540 .column(table::Column::TableType)
541 .filter(table::Column::TableId.is_in(all_dirty_table_ids))
542 .into_tuple::<(ObjectId, TableType)>()
543 .all(&txn)
544 .await?
545 .into_iter()
546 .collect();
547
548 let dirty_background_jobs: HashSet<JobId> = streaming_job::Entity::find()
549 .select_only()
550 .column(streaming_job::Column::JobId)
551 .filter(
552 streaming_job::Column::JobId
553 .is_in(dirty_job_ids.iter().copied())
554 .and(streaming_job::Column::CreateType.eq(CreateType::Background)),
555 )
556 .into_tuple()
557 .all(&txn)
558 .await?
559 .into_iter()
560 .collect();
561
562 let to_notify_objs = dirty_job_objs
564 .iter()
565 .filter(|obj| {
566 matches!(
567 dirty_table_type_map.get(&obj.oid),
568 Some(TableType::MaterializedView)
569 ) || dirty_background_jobs.contains(&obj.oid.as_job_id())
570 })
571 .cloned()
572 .collect_vec();
573
574 let dirty_associated_source_ids: Vec<SourceId> = Table::find()
577 .select_only()
578 .column(table::Column::OptionalAssociatedSourceId)
579 .filter(
580 table::Column::TableId
581 .is_in(dirty_job_table_ids)
582 .and(table::Column::OptionalAssociatedSourceId.is_not_null()),
583 )
584 .into_tuple()
585 .all(&txn)
586 .await?;
587
588 let dirty_internal_state_table_ids: Vec<TableId> = Table::find()
589 .select_only()
590 .column(table::Column::TableId)
591 .filter(table::Column::BelongsToJobId.is_in(dirty_job_ids.iter().copied()))
592 .into_tuple()
593 .all(&txn)
594 .await?;
595 let dirty_state_table_ids = dirty_job_ids
596 .iter()
597 .map(|job_id| job_id.as_mv_table_id())
598 .chain(dirty_internal_state_table_ids.iter().copied())
599 .collect_vec();
600
601 let dirty_internal_table_objs = Object::find()
602 .select_only()
603 .columns([
604 object::Column::Oid,
605 object::Column::ObjType,
606 object::Column::SchemaId,
607 object::Column::DatabaseId,
608 ])
609 .join(JoinType::InnerJoin, object::Relation::Table.def())
610 .filter(table::Column::BelongsToJobId.is_in(to_notify_objs.iter().map(|obj| obj.oid)))
611 .into_partial_model()
612 .all(&txn)
613 .await?;
614
615 let to_delete_objs: HashSet<ObjectId> = dirty_job_ids
616 .iter()
617 .map(|job_id| job_id.as_object_id())
618 .chain(
619 dirty_state_table_ids
620 .iter()
621 .copied()
622 .map(|table_id| table_id.as_object_id()),
623 )
624 .chain(
625 dirty_associated_source_ids
626 .iter()
627 .map(|source_id| source_id.as_object_id()),
628 )
629 .collect();
630
631 let dropped_tables = if database_id.is_some() {
634 Table::find()
635 .find_also_related(Object)
636 .filter(table::Column::TableId.is_in(dirty_state_table_ids.iter().copied()))
637 .all(&txn)
638 .await?
639 .into_iter()
640 .map(|(table, obj)| PbTable::from(ObjectModel(table, obj.unwrap(), None)))
641 .collect_vec()
642 } else {
643 vec![]
644 };
645 let dropped_table_ids = dropped_tables.iter().map(|table| table.id).collect_vec();
646
647 let res = Object::delete_many()
648 .filter(object::Column::Oid.is_in(to_delete_objs))
649 .exec(&txn)
650 .await?;
651 assert!(res.rows_affected > 0);
652
653 txn.commit().await?;
654 inner
655 .dropped_tables
656 .extend(dropped_tables.into_iter().map(|t| (t.id, t)));
657
658 let object_group = build_object_group_for_delete(
659 to_notify_objs
660 .into_iter()
661 .chain(dirty_internal_table_objs)
662 .collect_vec(),
663 );
664
665 let _version = self
666 .notify_frontend(NotificationOperation::Delete, object_group)
667 .await;
668
669 Ok(CleanedDirtyStreamingJobs {
670 streaming_job_ids: dirty_job_ids,
671 dropped_table_ids,
672 source_ids: dirty_associated_source_ids,
673 sink_ids: dirty_sink_ids,
674 })
675 }
676
677 pub async fn comment_on(&self, comment: PbComment) -> MetaResult<NotificationVersion> {
678 let inner = self.inner.write().await;
679 let txn = inner.db.begin().await?;
680 ensure_object_id(ObjectType::Database, comment.database_id, &txn).await?;
681 ensure_object_id(ObjectType::Schema, comment.schema_id, &txn).await?;
682 let (table_obj, streaming_job) = Object::find_by_id(comment.table_id)
683 .find_also_related(StreamingJob)
684 .one(&txn)
685 .await?
686 .ok_or_else(|| MetaError::catalog_id_not_found("object", comment.table_id))?;
687
688 let table = if let Some(col_idx) = comment.column_index {
689 let columns: ColumnCatalogArray = Table::find_by_id(comment.table_id)
690 .select_only()
691 .column(table::Column::Columns)
692 .into_tuple()
693 .one(&txn)
694 .await?
695 .ok_or_else(|| MetaError::catalog_id_not_found("table", comment.table_id))?;
696 let mut pb_columns = columns.to_protobuf();
697
698 let column = pb_columns
699 .get_mut(col_idx as usize)
700 .ok_or_else(|| MetaError::catalog_id_not_found("column", col_idx))?;
701 let column_desc = column.column_desc.as_mut().ok_or_else(|| {
702 anyhow!(
703 "column desc at index {} for table id {} not found",
704 col_idx,
705 comment.table_id
706 )
707 })?;
708 column_desc.description = comment.description;
709 table::ActiveModel {
710 table_id: Set(comment.table_id),
711 columns: Set(pb_columns.into()),
712 ..Default::default()
713 }
714 .update(&txn)
715 .await?
716 } else {
717 table::ActiveModel {
718 table_id: Set(comment.table_id),
719 description: Set(comment.description),
720 ..Default::default()
721 }
722 .update(&txn)
723 .await?
724 };
725 txn.commit().await?;
726
727 let version = self
728 .notify_frontend_relation_info(
729 NotificationOperation::Update,
730 PbObjectInfo::Table(ObjectModel(table, table_obj, streaming_job).into()),
731 )
732 .await;
733
734 Ok(version)
735 }
736
737 async fn notify_hummock_dropped_tables(&self, tables: Vec<PbTable>) {
738 if tables.is_empty() {
739 return;
740 }
741 let objects = tables
742 .into_iter()
743 .map(|t| PbObject {
744 object_info: Some(PbObjectInfo::Table(t)),
745 })
746 .collect();
747 let group = NotificationInfo::ObjectGroup(PbObjectGroup {
748 objects,
749 dependencies: vec![],
750 });
751 self.env
752 .notification_manager()
753 .notify_hummock(NotificationOperation::Delete, group.clone())
754 .await;
755 self.env
756 .notification_manager()
757 .notify_compactor(NotificationOperation::Delete, group)
758 .await;
759 }
760
761 pub async fn complete_dropped_tables(&self, table_ids: impl IntoIterator<Item = TableId>) {
762 let mut inner = self.inner.write().await;
763 let tables = inner.complete_dropped_tables(table_ids);
764 self.notify_hummock_dropped_tables(tables).await;
765 }
766
767 pub async fn cleanup_dropped_tables(&self) {
768 let mut inner = self.inner.write().await;
769 let tables = inner.dropped_tables.drain().map(|(_, t)| t).collect();
770 self.notify_hummock_dropped_tables(tables).await;
771 }
772
773 pub async fn stats(&self) -> MetaResult<CatalogStats> {
774 let inner = self.inner.read().await;
775
776 let mut table_num_map: HashMap<_, _> = Table::find()
777 .select_only()
778 .column(table::Column::TableType)
779 .column_as(table::Column::TableId.count(), "num")
780 .group_by(table::Column::TableType)
781 .having(table::Column::TableType.ne(TableType::Internal))
782 .into_tuple::<(TableType, i64)>()
783 .all(&inner.db)
784 .await?
785 .into_iter()
786 .map(|(table_type, num)| (table_type, num as u64))
787 .collect();
788
789 let source_num = Source::find().count(&inner.db).await?;
790 let sink_num = Sink::find().count(&inner.db).await?;
791 let function_num = Function::find().count(&inner.db).await?;
792 let streaming_job_num = StreamingJob::find().count(&inner.db).await?;
793
794 let actor_num = {
795 let guard = self.env.shared_actor_info.read_guard();
796 guard
797 .iter_over_fragments()
798 .map(|(_, fragment)| fragment.actors.len() as u64)
799 .sum::<u64>()
800 };
801 let database_num = Database::find().count(&inner.db).await?;
802
803 Ok(CatalogStats {
804 table_num: table_num_map.remove(&TableType::Table).unwrap_or(0),
805 mview_num: table_num_map
806 .remove(&TableType::MaterializedView)
807 .unwrap_or(0),
808 index_num: table_num_map.remove(&TableType::Index).unwrap_or(0),
809 source_num,
810 sink_num,
811 function_num,
812 streaming_job_num,
813 actor_num,
814 database_num,
815 })
816 }
817
818 pub async fn fetch_sink_with_state_table_ids(
819 &self,
820 sink_ids: HashSet<SinkId>,
821 ) -> MetaResult<HashMap<SinkId, Vec<TableId>>> {
822 let inner = self.inner.read().await;
823
824 let query = Fragment::find()
825 .select_only()
826 .columns([fragment::Column::JobId, fragment::Column::StateTableIds])
827 .filter(
828 fragment::Column::JobId
829 .is_in(sink_ids)
830 .and(FragmentTypeMask::intersects(FragmentTypeFlag::Sink)),
831 );
832
833 let rows: Vec<(JobId, TableIdArray)> = query.into_tuple().all(&inner.db).await?;
834
835 debug_assert!(rows.iter().map(|(job_id, _)| job_id).all_unique());
836
837 let result = rows
838 .into_iter()
839 .map(|(job_id, table_id_array)| (job_id.as_sink_id(), table_id_array.0))
840 .collect::<HashMap<_, _>>();
841
842 Ok(result)
843 }
844
845 pub async fn list_all_pending_sinks(
846 &self,
847 database_id: Option<DatabaseId>,
848 ) -> MetaResult<HashSet<SinkId>> {
849 let inner = self.inner.read().await;
850
851 let mut query = pending_sink_state::Entity::find()
852 .select_only()
853 .columns([pending_sink_state::Column::SinkId])
854 .filter(
855 pending_sink_state::Column::SinkState.eq(pending_sink_state::SinkState::Pending),
856 )
857 .distinct();
858
859 if let Some(db_id) = database_id {
860 query = query
861 .join(
862 JoinType::InnerJoin,
863 pending_sink_state::Relation::Object.def(),
864 )
865 .filter(object::Column::DatabaseId.eq(db_id));
866 }
867
868 let result: Vec<SinkId> = query.into_tuple().all(&inner.db).await?;
869
870 Ok(result.into_iter().collect())
871 }
872
873 pub async fn abort_pending_sink_epochs(
874 &self,
875 sink_committed_epoch: HashMap<SinkId, u64>,
876 ) -> MetaResult<()> {
877 let inner = self.inner.write().await;
878 let txn = inner.db.begin().await?;
879
880 for (sink_id, committed_epoch) in sink_committed_epoch {
881 pending_sink_state::Entity::update_many()
882 .col_expr(
883 pending_sink_state::Column::SinkState,
884 Expr::value(pending_sink_state::SinkState::Aborted),
885 )
886 .filter(
887 pending_sink_state::Column::SinkId
888 .eq(sink_id)
889 .and(pending_sink_state::Column::Epoch.gt(committed_epoch as i64)),
890 )
891 .exec(&txn)
892 .await?;
893 }
894
895 txn.commit().await?;
896 Ok(())
897 }
898}
899
900pub struct CatalogStats {
902 pub table_num: u64,
903 pub mview_num: u64,
904 pub index_num: u64,
905 pub source_num: u64,
906 pub sink_num: u64,
907 pub function_num: u64,
908 pub streaming_job_num: u64,
909 pub actor_num: u64,
910 pub database_num: u64,
911}
912
913impl CatalogControllerInner {
914 pub async fn snapshot(&self) -> MetaResult<(Catalog, Vec<PbUserInfo>)> {
915 let databases = self.list_databases().await?;
916 let schemas = self.list_schemas().await?;
917 let tables = self.list_tables().await?;
918 let sources = self.list_sources().await?;
919 let sinks = self.list_sinks().await?;
920 let subscriptions = self.list_subscriptions().await?;
921 let indexes = self.list_indexes().await?;
922 let views = self.list_views().await?;
923 let functions = self.list_functions().await?;
924 let connections = self.list_connections().await?;
925 let secrets = self.list_secrets().await?;
926
927 let users = self.list_users().await?;
928
929 Ok((
930 (
931 databases,
932 schemas,
933 tables,
934 sources,
935 sinks,
936 subscriptions,
937 indexes,
938 views,
939 functions,
940 connections,
941 secrets,
942 ),
943 users,
944 ))
945 }
946
947 async fn list_databases(&self) -> MetaResult<Vec<PbDatabase>> {
948 let db_objs = Database::find()
949 .find_also_related(Object)
950 .all(&self.db)
951 .await?;
952 Ok(db_objs
953 .into_iter()
954 .map(|(db, obj)| ObjectModel(db, obj.unwrap(), None).into())
955 .collect())
956 }
957
958 async fn list_schemas(&self) -> MetaResult<Vec<PbSchema>> {
959 let schema_objs = Schema::find()
960 .find_also_related(Object)
961 .all(&self.db)
962 .await?;
963
964 Ok(schema_objs
965 .into_iter()
966 .map(|(schema, obj)| ObjectModel(schema, obj.unwrap(), None).into())
967 .collect())
968 }
969
970 async fn list_users(&self) -> MetaResult<Vec<PbUserInfo>> {
971 let mut user_infos: Vec<PbUserInfo> = User::find()
972 .all(&self.db)
973 .await?
974 .into_iter()
975 .map(Into::into)
976 .collect();
977
978 for user_info in &mut user_infos {
979 user_info.grant_privileges = get_user_privilege(user_info.id as _, &self.db).await?;
980 }
981 Ok(user_infos)
982 }
983
984 pub async fn list_all_state_tables(&self) -> MetaResult<Vec<PbTable>> {
986 let table_objs = Table::find()
987 .find_also_related(Object)
988 .all(&self.db)
989 .await?;
990 let streaming_jobs = load_streaming_jobs_by_ids(
991 &self.db,
992 table_objs.iter().map(|(table, _)| table.job_id()),
993 )
994 .await?;
995
996 Ok(table_objs
997 .into_iter()
998 .map(|(table, obj)| {
999 let job_id = table.job_id();
1000 let streaming_job = streaming_jobs.get(&job_id).cloned();
1001 ObjectModel(table, obj.unwrap(), streaming_job).into()
1002 })
1003 .collect())
1004 }
1005
1006 async fn list_tables(&self) -> MetaResult<Vec<PbTable>> {
1008 let mut table_objs = Table::find()
1009 .find_also_related(Object)
1010 .all(&self.db)
1011 .await?;
1012
1013 let all_streaming_jobs: HashMap<JobId, streaming_job::Model> = StreamingJob::find()
1014 .all(&self.db)
1015 .await?
1016 .into_iter()
1017 .map(|job| (job.job_id, job))
1018 .collect();
1019
1020 let sink_ids: HashSet<SinkId> = Sink::find()
1021 .select_only()
1022 .column(sink::Column::SinkId)
1023 .into_tuple::<SinkId>()
1024 .all(&self.db)
1025 .await?
1026 .into_iter()
1027 .collect();
1028
1029 let mview_job_ids: HashSet<JobId> = table_objs
1030 .iter()
1031 .filter_map(|(table, _)| {
1032 if table.table_type == TableType::MaterializedView {
1033 Some(table.table_id.as_job_id())
1034 } else {
1035 None
1036 }
1037 })
1038 .collect();
1039
1040 table_objs.retain(|(table, _)| {
1041 let job_id = table.job_id();
1042
1043 if sink_ids.contains(&job_id.as_sink_id()) || mview_job_ids.contains(&job_id) {
1044 return true;
1045 }
1046 if let Some(streaming_job) = all_streaming_jobs.get(&job_id) {
1047 return streaming_job.job_status == JobStatus::Created
1048 || (streaming_job.create_type == CreateType::Background);
1049 }
1050 false
1051 });
1052
1053 let mut tables = Vec::with_capacity(table_objs.len());
1054 for (table, obj) in table_objs {
1055 let job_id = table.job_id();
1056 let streaming_job = all_streaming_jobs.get(&job_id).cloned();
1057 let pb_table: PbTable = ObjectModel(table, obj.unwrap(), streaming_job).into();
1058 tables.push(pb_table);
1059 }
1060 Ok(tables)
1061 }
1062
1063 async fn list_sources(&self) -> MetaResult<Vec<PbSource>> {
1065 let mut source_objs = Source::find()
1066 .find_also_related(Object)
1067 .join(JoinType::LeftJoin, object::Relation::StreamingJob.def())
1068 .filter(
1069 streaming_job::Column::JobStatus
1070 .is_null()
1071 .or(streaming_job::Column::JobStatus.eq(JobStatus::Created)),
1072 )
1073 .all(&self.db)
1074 .await?;
1075
1076 let created_table_ids: HashSet<TableId> = Table::find()
1078 .select_only()
1079 .column(table::Column::TableId)
1080 .join(JoinType::InnerJoin, table::Relation::Object1.def())
1081 .join(JoinType::LeftJoin, object::Relation::StreamingJob.def())
1082 .filter(
1083 table::Column::OptionalAssociatedSourceId
1084 .is_not_null()
1085 .and(streaming_job::Column::JobStatus.eq(JobStatus::Created)),
1086 )
1087 .into_tuple()
1088 .all(&self.db)
1089 .await?
1090 .into_iter()
1091 .collect();
1092 source_objs.retain_mut(|(source, _)| {
1093 source.optional_associated_table_id.is_none()
1094 || created_table_ids.contains(&source.optional_associated_table_id.unwrap())
1095 });
1096
1097 Ok(source_objs
1098 .into_iter()
1099 .map(|(source, obj)| ObjectModel(source, obj.unwrap(), None).into())
1100 .collect())
1101 }
1102
1103 async fn list_sinks(&self) -> MetaResult<Vec<PbSink>> {
1105 let sink_objs = Sink::find()
1106 .find_also_related(Object)
1107 .join(JoinType::LeftJoin, object::Relation::StreamingJob.def())
1108 .all(&self.db)
1109 .await?;
1110 let streaming_jobs = load_streaming_jobs_by_ids(
1111 &self.db,
1112 sink_objs.iter().map(|(sink, _)| sink.sink_id.as_job_id()),
1113 )
1114 .await?;
1115
1116 Ok(sink_objs
1117 .into_iter()
1118 .map(|(sink, obj)| {
1119 let streaming_job = streaming_jobs.get(&sink.sink_id.as_job_id()).cloned();
1120 ObjectModel(sink, obj.unwrap(), streaming_job).into()
1121 })
1122 .collect())
1123 }
1124
1125 async fn list_subscriptions(&self) -> MetaResult<Vec<PbSubscription>> {
1127 let subscription_objs = Subscription::find()
1128 .find_also_related(Object)
1129 .filter(subscription::Column::SubscriptionState.eq(SubscriptionState::Created as i32))
1130 .all(&self.db)
1131 .await?;
1132
1133 Ok(subscription_objs
1134 .into_iter()
1135 .map(|(subscription, obj)| ObjectModel(subscription, obj.unwrap(), None).into())
1136 .collect())
1137 }
1138
1139 async fn list_views(&self) -> MetaResult<Vec<PbView>> {
1140 let view_objs = View::find().find_also_related(Object).all(&self.db).await?;
1141
1142 Ok(view_objs
1143 .into_iter()
1144 .map(|(view, obj)| ObjectModel(view, obj.unwrap(), None).into())
1145 .collect())
1146 }
1147
1148 async fn list_indexes(&self) -> MetaResult<Vec<PbIndex>> {
1150 let index_objs = Index::find()
1151 .find_also_related(Object)
1152 .join(JoinType::LeftJoin, object::Relation::StreamingJob.def())
1153 .filter(
1154 streaming_job::Column::JobStatus
1155 .eq(JobStatus::Created)
1156 .or(streaming_job::Column::CreateType.eq(CreateType::Background)),
1157 )
1158 .all(&self.db)
1159 .await?;
1160 let streaming_jobs = load_streaming_jobs_by_ids(
1161 &self.db,
1162 index_objs
1163 .iter()
1164 .map(|(index, _)| index.index_id.as_job_id()),
1165 )
1166 .await?;
1167
1168 Ok(index_objs
1169 .into_iter()
1170 .map(|(index, obj)| {
1171 let streaming_job = streaming_jobs.get(&index.index_id.as_job_id()).cloned();
1172 ObjectModel(index, obj.unwrap(), streaming_job).into()
1173 })
1174 .collect())
1175 }
1176
1177 async fn list_connections(&self) -> MetaResult<Vec<PbConnection>> {
1178 let conn_objs = Connection::find()
1179 .find_also_related(Object)
1180 .all(&self.db)
1181 .await?;
1182
1183 Ok(conn_objs
1184 .into_iter()
1185 .map(|(conn, obj)| ObjectModel(conn, obj.unwrap(), None).into())
1186 .collect())
1187 }
1188
1189 pub async fn list_secrets(&self) -> MetaResult<Vec<PbSecret>> {
1190 let secret_objs = Secret::find()
1191 .find_also_related(Object)
1192 .all(&self.db)
1193 .await?;
1194 Ok(secret_objs
1195 .into_iter()
1196 .map(|(secret, obj)| ObjectModel(secret, obj.unwrap(), None).into())
1197 .collect())
1198 }
1199
1200 async fn list_functions(&self) -> MetaResult<Vec<PbFunction>> {
1201 let func_objs = Function::find()
1202 .find_also_related(Object)
1203 .all(&self.db)
1204 .await?;
1205
1206 Ok(func_objs
1207 .into_iter()
1208 .map(|(func, obj)| ObjectModel(func, obj.unwrap(), None).into())
1209 .collect())
1210 }
1211
1212 pub(crate) fn register_finish_notifier(
1213 &mut self,
1214 database_id: DatabaseId,
1215 id: JobId,
1216 sender: Sender<Result<NotificationVersion, String>>,
1217 ) {
1218 self.creating_table_finish_notifier
1219 .entry(database_id)
1220 .or_default()
1221 .entry(id)
1222 .or_default()
1223 .push(sender);
1224 }
1225
1226 pub(crate) async fn streaming_job_is_finished(&mut self, id: JobId) -> MetaResult<bool> {
1227 let status = StreamingJob::find()
1228 .select_only()
1229 .column(streaming_job::Column::JobStatus)
1230 .filter(streaming_job::Column::JobId.eq(id))
1231 .into_tuple::<JobStatus>()
1232 .one(&self.db)
1233 .await?;
1234
1235 status
1236 .map(|status| status == JobStatus::Created)
1237 .ok_or_else(|| {
1238 MetaError::catalog_id_not_found("streaming job", "may have been cancelled/dropped")
1239 })
1240 }
1241
1242 pub(crate) fn notify_finish_failed(&mut self, database_id: Option<DatabaseId>, err: String) {
1243 if let Some(database_id) = database_id {
1244 if let Some(creating_tables) = self.creating_table_finish_notifier.remove(&database_id)
1245 {
1246 for tx in creating_tables.into_values().flatten() {
1247 let _ = tx.send(Err(err.clone()));
1248 }
1249 }
1250 } else {
1251 for tx in take(&mut self.creating_table_finish_notifier)
1252 .into_values()
1253 .flatten()
1254 .flat_map(|(_, txs)| txs.into_iter())
1255 {
1256 let _ = tx.send(Err(err.clone()));
1257 }
1258 }
1259 }
1260
1261 pub async fn list_time_travel_table_ids(&self) -> MetaResult<Vec<TableId>> {
1262 let table_ids: Vec<TableId> = Table::find()
1263 .select_only()
1264 .filter(table::Column::TableType.is_in(vec![
1265 TableType::Table,
1266 TableType::MaterializedView,
1267 TableType::Index,
1268 ]))
1269 .column(table::Column::TableId)
1270 .into_tuple()
1271 .all(&self.db)
1272 .await?;
1273 Ok(table_ids)
1274 }
1275
1276 pub(crate) fn complete_dropped_tables(
1279 &mut self,
1280 table_ids: impl IntoIterator<Item = TableId>,
1281 ) -> Vec<PbTable> {
1282 table_ids
1283 .into_iter()
1284 .filter_map(|table_id| {
1285 self.dropped_tables.remove(&table_id).map_or_else(
1286 || {
1287 tracing::warn!(%table_id, "table not found");
1288 None
1289 },
1290 Some,
1291 )
1292 })
1293 .collect()
1294 }
1295}