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