1use risingwave_common::catalog::FragmentTypeMask;
16use risingwave_pb::stream_plan::stream_node::NodeBody;
17
18use super::*;
19use crate::controller::fragment::FragmentTypeMaskExt;
20use crate::controller::utils::load_streaming_jobs_by_ids;
21
22pub(crate) async fn prepare_object_models_for_schema_change(
23 txn: &DatabaseTransaction,
24 object_models: &mut [PbObjectInfo],
25 database_id: DatabaseId,
26 new_schema: SchemaId,
27) -> MetaResult<()> {
28 let index_ids = object_models
31 .iter()
32 .filter_map(|object_info| match object_info {
33 PbObjectInfo::Index(index) => Some(index.id.as_object_id().as_table_id()),
34 _ => None,
35 })
36 .collect::<HashSet<_>>();
37
38 for object_info in object_models {
39 match object_info {
40 PbObjectInfo::Table(table) => table.schema_id = new_schema,
41 PbObjectInfo::Source(source) => source.schema_id = new_schema,
42 PbObjectInfo::Sink(sink) => sink.schema_id = new_schema,
43 PbObjectInfo::View(view) => view.schema_id = new_schema,
44 PbObjectInfo::Index(index) => index.schema_id = new_schema,
45 PbObjectInfo::Function(function) => function.schema_id = new_schema,
46 PbObjectInfo::Connection(connection) => connection.schema_id = new_schema,
47 PbObjectInfo::Subscription(subscription) => subscription.schema_id = new_schema,
48 PbObjectInfo::Secret(secret) => secret.schema_id = new_schema,
49 PbObjectInfo::Database(_) | PbObjectInfo::Schema(_) => {}
50 }
51
52 match object_info {
53 PbObjectInfo::Table(table) if !index_ids.contains(&table.id) => {
54 check_relation_name_duplicate(&table.name, database_id, new_schema, txn).await?;
55 }
56 PbObjectInfo::Source(source) => {
57 check_relation_name_duplicate(&source.name, database_id, new_schema, txn).await?;
58 }
59 PbObjectInfo::Sink(sink) => {
60 check_relation_name_duplicate(&sink.name, database_id, new_schema, txn).await?;
61 }
62 PbObjectInfo::Index(index) => {
63 check_relation_name_duplicate(&index.name, database_id, new_schema, txn).await?;
64 }
65 PbObjectInfo::View(view) => {
66 check_relation_name_duplicate(&view.name, database_id, new_schema, txn).await?;
67 }
68 PbObjectInfo::Subscription(subscription) => {
69 check_relation_name_duplicate(&subscription.name, database_id, new_schema, txn)
70 .await?;
71 check_subscription_name_duplicate(subscription, txn).await?;
72 }
73 PbObjectInfo::Function(function) => {
74 check_function_signature_duplicate(function, txn).await?;
75 }
76 PbObjectInfo::Connection(connection) => {
77 check_connection_name_duplicate(connection, txn).await?;
78 }
79 PbObjectInfo::Secret(secret) => {
80 check_secret_name_duplicate(secret, txn).await?;
81 }
82 PbObjectInfo::Database(_) | PbObjectInfo::Schema(_) | PbObjectInfo::Table(_) => {}
83 }
84 }
85
86 Ok(())
87}
88
89pub(crate) async fn load_object_models(
90 txn: &DatabaseTransaction,
91 objects: &[object::Model],
92) -> MetaResult<Vec<PbObjectInfo>> {
93 let mut object_infos = vec![];
94
95 let table_ids = objects
96 .iter()
97 .filter(|object| object.obj_type == ObjectType::Table)
98 .map(|object| object.oid.as_table_id())
99 .collect_vec();
100 let table_objs = Table::find()
101 .find_also_related(Object)
102 .filter(table::Column::TableId.is_in(table_ids))
103 .all(txn)
104 .await?;
105 let streaming_jobs =
106 load_streaming_jobs_by_ids(txn, table_objs.iter().map(|(table, _)| table.job_id())).await?;
107 for (table, table_obj) in table_objs {
108 let streaming_job = streaming_jobs.get(&table.job_id()).cloned();
109 object_infos.push(PbObjectInfo::Table(
110 ObjectModel(table, table_obj.unwrap(), streaming_job).into(),
111 ));
112 }
113
114 let index_ids = objects
115 .iter()
116 .filter(|object| object.obj_type == ObjectType::Index)
117 .map(|object| object.oid.as_index_id())
118 .collect_vec();
119 let index_table_objs = Table::find()
120 .find_also_related(Object)
121 .filter(
122 table::Column::TableId
123 .is_in(index_ids.iter().map(|id| id.as_object_id().as_table_id())),
124 )
125 .all(txn)
126 .await?;
127 let index_streaming_jobs = load_streaming_jobs_by_ids(
128 txn,
129 index_table_objs.iter().map(|(table, _)| table.job_id()),
130 )
131 .await?;
132 for (table, table_obj) in index_table_objs {
133 let streaming_job = index_streaming_jobs.get(&table.job_id()).cloned();
134 object_infos.push(PbObjectInfo::Table(
135 ObjectModel(table, table_obj.unwrap(), streaming_job).into(),
136 ));
137 }
138 let index_objs = Index::find()
139 .find_also_related(Object)
140 .filter(index::Column::IndexId.is_in(index_ids))
141 .all(txn)
142 .await?;
143 for (index, index_obj) in index_objs {
144 let streaming_job = index_streaming_jobs
145 .get(&index.index_id.as_job_id())
146 .cloned();
147 object_infos.push(PbObjectInfo::Index(
148 ObjectModel(index, index_obj.unwrap(), streaming_job).into(),
149 ));
150 }
151
152 let source_ids = objects
153 .iter()
154 .filter(|object| object.obj_type == ObjectType::Source)
155 .map(|object| object.oid.as_source_id())
156 .collect_vec();
157 for (source, source_obj) in Source::find()
158 .find_also_related(Object)
159 .filter(source::Column::SourceId.is_in(source_ids))
160 .all(txn)
161 .await?
162 {
163 object_infos.push(PbObjectInfo::Source(
164 ObjectModel(source, source_obj.unwrap(), None).into(),
165 ));
166 }
167
168 let sink_ids = objects
169 .iter()
170 .filter(|object| object.obj_type == ObjectType::Sink)
171 .map(|object| object.oid.as_sink_id())
172 .collect_vec();
173 let sink_objs = Sink::find()
174 .find_also_related(Object)
175 .filter(sink::Column::SinkId.is_in(sink_ids))
176 .all(txn)
177 .await?;
178 let sink_streaming_jobs = load_streaming_jobs_by_ids(
179 txn,
180 sink_objs.iter().map(|(sink, _)| sink.sink_id.as_job_id()),
181 )
182 .await?;
183 for (sink, sink_obj) in sink_objs {
184 let streaming_job = sink_streaming_jobs.get(&sink.sink_id.as_job_id()).cloned();
185 object_infos.push(PbObjectInfo::Sink(
186 ObjectModel(sink, sink_obj.unwrap(), streaming_job).into(),
187 ));
188 }
189
190 let subscription_ids = objects
191 .iter()
192 .filter(|object| object.obj_type == ObjectType::Subscription)
193 .map(|object| object.oid.as_subscription_id())
194 .collect_vec();
195 for (subscription, subscription_obj) in Subscription::find()
196 .find_also_related(Object)
197 .filter(subscription::Column::SubscriptionId.is_in(subscription_ids))
198 .all(txn)
199 .await?
200 {
201 object_infos.push(PbObjectInfo::Subscription(
202 ObjectModel(subscription, subscription_obj.unwrap(), None).into(),
203 ));
204 }
205
206 let view_ids = objects
207 .iter()
208 .filter(|object| object.obj_type == ObjectType::View)
209 .map(|object| object.oid.as_view_id())
210 .collect_vec();
211 for (view, view_obj) in View::find()
212 .find_also_related(Object)
213 .filter(view::Column::ViewId.is_in(view_ids))
214 .all(txn)
215 .await?
216 {
217 object_infos.push(PbObjectInfo::View(
218 ObjectModel(view, view_obj.unwrap(), None).into(),
219 ));
220 }
221
222 let function_ids = objects
223 .iter()
224 .filter(|object| object.obj_type == ObjectType::Function)
225 .map(|object| object.oid.as_function_id())
226 .collect_vec();
227 for (function, function_obj) in Function::find()
228 .find_also_related(Object)
229 .filter(function::Column::FunctionId.is_in(function_ids))
230 .all(txn)
231 .await?
232 {
233 object_infos.push(PbObjectInfo::Function(
234 ObjectModel(function, function_obj.unwrap(), None).into(),
235 ));
236 }
237
238 let connection_ids = objects
239 .iter()
240 .filter(|object| object.obj_type == ObjectType::Connection)
241 .map(|object| object.oid.as_connection_id())
242 .collect_vec();
243 for (connection, connection_obj) in Connection::find()
244 .find_also_related(Object)
245 .filter(connection::Column::ConnectionId.is_in(connection_ids))
246 .all(txn)
247 .await?
248 {
249 object_infos.push(PbObjectInfo::Connection(
250 ObjectModel(connection, connection_obj.unwrap(), None).into(),
251 ));
252 }
253
254 let secret_ids = objects
255 .iter()
256 .filter(|object| object.obj_type == ObjectType::Secret)
257 .map(|object| object.oid.as_secret_id())
258 .collect_vec();
259 for (secret, secret_obj) in Secret::find()
260 .find_also_related(Object)
261 .filter(secret::Column::SecretId.is_in(secret_ids))
262 .all(txn)
263 .await?
264 {
265 object_infos.push(PbObjectInfo::Secret(
266 ObjectModel(secret, secret_obj.unwrap(), None).into(),
267 ));
268 }
269
270 let database_ids = objects
271 .iter()
272 .filter(|object| object.obj_type == ObjectType::Database)
273 .map(|object| object.oid.as_database_id())
274 .collect_vec();
275 for (database, database_obj) in Database::find()
276 .find_also_related(Object)
277 .filter(database::Column::DatabaseId.is_in(database_ids))
278 .all(txn)
279 .await?
280 {
281 object_infos.push(PbObjectInfo::Database(
282 ObjectModel(database, database_obj.unwrap(), None).into(),
283 ));
284 }
285
286 let schema_ids = objects
287 .iter()
288 .filter(|object| object.obj_type == ObjectType::Schema)
289 .map(|object| object.oid.as_schema_id())
290 .collect_vec();
291 for (schema, schema_obj) in Schema::find()
292 .find_also_related(Object)
293 .filter(schema::Column::SchemaId.is_in(schema_ids))
294 .all(txn)
295 .await?
296 {
297 object_infos.push(PbObjectInfo::Schema(
298 ObjectModel(schema, schema_obj.unwrap(), None).into(),
299 ));
300 }
301
302 Ok(object_infos)
303}
304
305pub(crate) async fn update_internal_tables(
306 txn: &DatabaseTransaction,
307 object_id: ObjectId,
308 column: object::Column,
309 new_value: impl Into<Value>,
310 objects_to_notify: &mut Vec<PbObjectInfo>,
311) -> MetaResult<()> {
312 let internal_tables = get_internal_tables_by_id(object_id.as_job_id(), txn).await?;
313
314 if !internal_tables.is_empty() {
315 Object::update_many()
316 .col_expr(column, SimpleExpr::Value(new_value.into()))
317 .filter(object::Column::Oid.is_in(internal_tables.clone()))
318 .exec(txn)
319 .await?;
320
321 let table_objs = Table::find()
322 .find_also_related(Object)
323 .filter(table::Column::TableId.is_in(internal_tables))
324 .all(txn)
325 .await?;
326 let streaming_jobs =
327 load_streaming_jobs_by_ids(txn, table_objs.iter().map(|(table, _)| table.job_id()))
328 .await?;
329 for (table, table_obj) in table_objs {
330 let job_id = table.job_id();
331 let streaming_job = streaming_jobs.get(&job_id).cloned();
332 objects_to_notify.push(PbObjectInfo::Table(
333 ObjectModel(table, table_obj.unwrap(), streaming_job).into(),
334 ));
335 }
336 }
337 Ok(())
338}
339
340impl CatalogController {
341 pub(crate) async fn init(&self) -> MetaResult<()> {
342 self.table_catalog_cdc_table_id_update().await?;
343 Ok(())
344 }
345
346 pub(crate) async fn table_catalog_cdc_table_id_update(&self) -> MetaResult<()> {
349 let inner = self.inner.read().await;
350 let txn = inner.db.begin().await?;
351
352 let table_and_source_id: Vec<(TableId, String, SourceId)> = Table::find()
354 .join(JoinType::InnerJoin, table::Relation::ObjectDependency.def())
355 .join(
356 JoinType::InnerJoin,
357 object_dependency::Relation::Source.def(),
358 )
359 .select_only()
360 .columns([table::Column::TableId, table::Column::Definition])
361 .columns([source::Column::SourceId])
362 .filter(
363 table::Column::TableType.eq(TableType::Table).and(
364 table::Column::CdcTableId
365 .is_null()
366 .or(table::Column::CdcTableId.eq("")),
367 ),
368 )
369 .into_tuple()
370 .all(&txn)
371 .await?;
372
373 if table_and_source_id.is_empty() {
375 return Ok(());
376 }
377
378 info!(table_and_source_id = ?table_and_source_id, "cdc table with empty cdc_table_id");
379
380 let mut cdc_table_ids = HashMap::new();
381 for (table_id, definition, source_id) in table_and_source_id {
382 match extract_external_table_name_from_definition(&definition) {
383 None => {
384 tracing::warn!(
385 %table_id,
386 definition,
387 "failed to extract cdc table name from table definition.",
388 )
389 }
390 Some(external_table_name) => {
391 cdc_table_ids.insert(
392 table_id,
393 build_cdc_table_id(source_id, &external_table_name),
394 );
395 }
396 }
397 }
398
399 for (table_id, cdc_table_id) in cdc_table_ids {
400 Table::update(table::ActiveModel {
401 table_id: Set(table_id as _),
402 cdc_table_id: Set(Some(cdc_table_id)),
403 ..Default::default()
404 })
405 .exec(&txn)
406 .await?;
407 }
408 txn.commit().await?;
409 Ok(())
410 }
411
412 pub(crate) async fn log_cleaned_dirty_jobs(
413 &self,
414 dirty_objs: &[PartialObject],
415 txn: &DatabaseTransaction,
416 ) -> MetaResult<()> {
417 let mut dirty_table_ids = vec![];
419 let mut dirty_source_ids = vec![];
420 let mut dirty_sink_ids = vec![];
421 for dirty_job_obj in dirty_objs {
422 let job_id = dirty_job_obj.oid;
423 let job_type = dirty_job_obj.obj_type;
424 match job_type {
425 ObjectType::Table | ObjectType::Index => dirty_table_ids.push(job_id),
426 ObjectType::Source => dirty_source_ids.push(job_id),
427 ObjectType::Sink => dirty_sink_ids.push(job_id),
428 _ => unreachable!("unexpected streaming job type"),
429 }
430 }
431
432 let mut event_logs = vec![];
433 if !dirty_table_ids.is_empty() {
434 let table_info: Vec<(TableId, String, String)> = Table::find()
435 .select_only()
436 .columns([
437 table::Column::TableId,
438 table::Column::Name,
439 table::Column::Definition,
440 ])
441 .filter(table::Column::TableId.is_in(dirty_table_ids))
442 .into_tuple()
443 .all(txn)
444 .await?;
445 for (table_id, name, definition) in table_info {
446 let event = risingwave_pb::meta::event_log::EventDirtyStreamJobClear {
447 id: table_id.as_job_id(),
448 name,
449 definition,
450 error: "clear during recovery".to_owned(),
451 };
452 event_logs.push(risingwave_pb::meta::event_log::Event::DirtyStreamJobClear(
453 event,
454 ));
455 }
456 }
457 if !dirty_source_ids.is_empty() {
458 let source_info: Vec<(SourceId, String, String)> = Source::find()
459 .select_only()
460 .columns([
461 source::Column::SourceId,
462 source::Column::Name,
463 source::Column::Definition,
464 ])
465 .filter(source::Column::SourceId.is_in(dirty_source_ids))
466 .into_tuple()
467 .all(txn)
468 .await?;
469 for (source_id, name, definition) in source_info {
470 let event = risingwave_pb::meta::event_log::EventDirtyStreamJobClear {
471 id: source_id.as_share_source_job_id(),
472 name,
473 definition,
474 error: "clear during recovery".to_owned(),
475 };
476 event_logs.push(risingwave_pb::meta::event_log::Event::DirtyStreamJobClear(
477 event,
478 ));
479 }
480 }
481 if !dirty_sink_ids.is_empty() {
482 let sink_info: Vec<(SinkId, String, String)> = Sink::find()
483 .select_only()
484 .columns([
485 sink::Column::SinkId,
486 sink::Column::Name,
487 sink::Column::Definition,
488 ])
489 .filter(sink::Column::SinkId.is_in(dirty_sink_ids))
490 .into_tuple()
491 .all(txn)
492 .await?;
493 for (sink_id, name, definition) in sink_info {
494 let event = risingwave_pb::meta::event_log::EventDirtyStreamJobClear {
495 id: sink_id.as_job_id(),
496 name,
497 definition,
498 error: "clear during recovery".to_owned(),
499 };
500 event_logs.push(risingwave_pb::meta::event_log::Event::DirtyStreamJobClear(
501 event,
502 ));
503 }
504 }
505 self.env.event_log_manager_ref().add_event_logs(event_logs);
506 Ok(())
507 }
508
509 pub(crate) async fn clean_dirty_sink_downstreams(txn: &DatabaseTransaction) -> MetaResult<()> {
510 let all_fragment_ids: Vec<FragmentId> = Fragment::find()
520 .select_only()
521 .column(fragment::Column::FragmentId)
522 .into_tuple()
523 .all(txn)
524 .await?;
525
526 let all_fragment_ids: HashSet<_> = all_fragment_ids.into_iter().collect();
527
528 let all_sink_into_tables: Vec<Option<TableId>> = Sink::find()
529 .select_only()
530 .column(sink::Column::TargetTable)
531 .filter(sink::Column::TargetTable.is_not_null())
532 .into_tuple()
533 .all(txn)
534 .await?;
535
536 let mut table_with_incoming_sinks: HashSet<TableId> = HashSet::new();
537 for target_table_id in all_sink_into_tables {
538 table_with_incoming_sinks.insert(target_table_id.expect("filter by non null"));
539 }
540
541 if table_with_incoming_sinks.is_empty() {
543 return Ok(());
544 }
545
546 for table_id in table_with_incoming_sinks {
547 tracing::info!("cleaning dirty table sink downstream table {}", table_id);
548
549 let fragments: Vec<(FragmentId, StreamNode)> = Fragment::find()
550 .select_only()
551 .columns(vec![
552 fragment::Column::FragmentId,
553 fragment::Column::StreamNode,
554 ])
555 .filter(fragment::Column::JobId.eq(table_id).and(
556 FragmentTypeMask::intersects(FragmentTypeFlag::Mview),
558 ))
559 .into_tuple()
560 .all(txn)
561 .await?;
562
563 for (fragment_id, stream_node) in fragments {
564 {
565 let mut dirty_upstream_fragment_ids = HashSet::new();
566
567 let mut pb_stream_node = stream_node.to_protobuf();
568
569 visit_stream_node_cont_mut(&mut pb_stream_node, |node| {
570 if let Some(NodeBody::Union(_)) = node.node_body {
571 node.input.retain_mut(|input| match &mut input.node_body {
572 Some(NodeBody::Project(_)) => {
573 let body = Itertools::exactly_one(input.input.iter()).unwrap();
574 let Some(NodeBody::Merge(merge_node)) = &body.node_body else {
575 unreachable!("expect merge node");
576 };
577 if all_fragment_ids.contains(&(merge_node.upstream_fragment_id))
578 {
579 true
580 } else {
581 dirty_upstream_fragment_ids
582 .insert(merge_node.upstream_fragment_id);
583 false
584 }
585 }
586 Some(NodeBody::Merge(merge_node)) => {
587 if all_fragment_ids.contains(&(merge_node.upstream_fragment_id))
588 {
589 true
590 } else {
591 dirty_upstream_fragment_ids
592 .insert(merge_node.upstream_fragment_id);
593 false
594 }
595 }
596 _ => false,
597 });
598 }
599 true
600 });
601
602 tracing::info!(
603 "cleaning dirty table sink fragment {:?} from downstream fragment {}",
604 dirty_upstream_fragment_ids,
605 fragment_id
606 );
607
608 if !dirty_upstream_fragment_ids.is_empty() {
609 tracing::info!(
610 "fixing dirty stream node in downstream fragment {}",
611 fragment_id
612 );
613 Fragment::update_many()
614 .col_expr(
615 fragment::Column::StreamNode,
616 StreamNode::from(&pb_stream_node).into(),
617 )
618 .filter(fragment::Column::FragmentId.eq(fragment_id))
619 .exec(txn)
620 .await?;
621 }
622 }
623 }
624 }
625
626 Ok(())
627 }
628
629 pub async fn has_any_streaming_jobs(&self) -> MetaResult<bool> {
630 let inner = self.inner.read().await;
631 let count = streaming_job::Entity::find().count(&inner.db).await?;
632 Ok(count > 0)
633 }
634
635 pub async fn find_creating_streaming_job_ids(
636 &self,
637 infos: Vec<PbCreatingJobInfo>,
638 ) -> MetaResult<Vec<ObjectId>> {
639 let inner = self.inner.read().await;
640
641 type JobKey = (DatabaseId, SchemaId, String);
642
643 let creating_tables: Vec<(ObjectId, String, DatabaseId, SchemaId)> = Table::find()
645 .select_only()
646 .columns([table::Column::TableId, table::Column::Name])
647 .columns([object::Column::DatabaseId, object::Column::SchemaId])
648 .join(JoinType::InnerJoin, table::Relation::Object1.def())
649 .join(JoinType::InnerJoin, object::Relation::StreamingJob.def())
650 .filter(streaming_job::Column::JobStatus.eq(JobStatus::Creating))
651 .into_tuple()
652 .all(&inner.db)
653 .await?;
654 let creating_sinks: Vec<(ObjectId, String, DatabaseId, SchemaId)> = Sink::find()
655 .select_only()
656 .columns([sink::Column::SinkId, sink::Column::Name])
657 .columns([object::Column::DatabaseId, object::Column::SchemaId])
658 .join(JoinType::InnerJoin, sink::Relation::Object.def())
659 .join(JoinType::InnerJoin, object::Relation::StreamingJob.def())
660 .filter(streaming_job::Column::JobStatus.eq(JobStatus::Creating))
661 .into_tuple()
662 .all(&inner.db)
663 .await?;
664 let creating_subscriptions: Vec<(ObjectId, String, DatabaseId, SchemaId)> =
665 Subscription::find()
666 .select_only()
667 .columns([
668 subscription::Column::SubscriptionId,
669 subscription::Column::Name,
670 ])
671 .columns([object::Column::DatabaseId, object::Column::SchemaId])
672 .join(JoinType::InnerJoin, subscription::Relation::Object.def())
673 .join(JoinType::InnerJoin, object::Relation::StreamingJob.def())
674 .filter(streaming_job::Column::JobStatus.eq(JobStatus::Creating))
675 .into_tuple()
676 .all(&inner.db)
677 .await?;
678
679 let mut job_mapping: HashMap<JobKey, ObjectId> = creating_tables
680 .into_iter()
681 .chain(creating_sinks)
682 .chain(creating_subscriptions)
683 .map(|(id, name, database_id, schema_id)| ((database_id, schema_id, name), id))
684 .collect();
685
686 Ok(infos
687 .into_iter()
688 .flat_map(|info| job_mapping.remove(&(info.database_id, info.schema_id, info.name)))
689 .collect())
690 }
691}